From 7a714bf57fc5c07267a3e5b07d8ceff7c579a153 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sun, 21 May 2023 23:06:17 +0530 Subject: [PATCH 01/39] Implement Transport::dial_as_listener for QUIC --- Cargo.lock | 26 +++ Cargo.toml | 2 + examples/dcutr-quic/Cargo.toml | 15 ++ examples/dcutr-quic/src/main.rs | 290 +++++++++++++++++++++++++ examples/relay-server-quic/Cargo.toml | 15 ++ examples/relay-server-quic/src/main.rs | 117 ++++++++++ protocols/dcutr/src/behaviour_impl.rs | 82 +++---- transports/quic/src/transport.rs | 202 ++++++++++++++++- 8 files changed, 692 insertions(+), 57 deletions(-) create mode 100644 examples/dcutr-quic/Cargo.toml create mode 100644 examples/dcutr-quic/src/main.rs create mode 100644 examples/relay-server-quic/Cargo.toml create mode 100644 examples/relay-server-quic/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index eaa3b29d347..ecb626c5599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1267,6 +1267,19 @@ dependencies = [ "log", ] +[[package]] +name = "dcutr-quic" +version = "0.1.0" +dependencies = [ + "clap 4.2.7", + "env_logger 0.10.0", + "futures", + "futures-timer", + "libp2p", + "libp2p-quic", + "log", +] + [[package]] name = "der" version = "0.6.1" @@ -4289,6 +4302,19 @@ dependencies = [ "libp2p", ] +[[package]] +name = "relay-server-example-quic" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "clap 4.2.7", + "env_logger 0.10.0", + "futures", + "libp2p", + "libp2p-quic", +] + [[package]] name = "rendezvous-example" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8400238368d..0a4ce5752c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "examples/chat-example", "examples/autonat", "examples/dcutr", + "examples/dcutr-quic", "examples/distributed-key-value-store", "examples/file-sharing", "examples/identify", @@ -12,6 +13,7 @@ members = [ "examples/metrics", "examples/ping-example", "examples/relay-server", + "examples/relay-server-quic", "examples/rendezvous", "identity", "interop-tests", diff --git a/examples/dcutr-quic/Cargo.toml b/examples/dcutr-quic/Cargo.toml new file mode 100644 index 00000000000..5713615ba9f --- /dev/null +++ b/examples/dcutr-quic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "dcutr-quic" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[dependencies] +clap = { version = "4.2.7", features = ["derive"] } +env_logger = "0.10.0" +futures = "0.3.28" +futures-timer = "3.0" +libp2p = { path = "../../libp2p", features = ["async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "relay", "rendezvous", "tcp", "tokio", "yamux"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } +log = "0.4" diff --git a/examples/dcutr-quic/src/main.rs b/examples/dcutr-quic/src/main.rs new file mode 100644 index 00000000000..7c90828dd87 --- /dev/null +++ b/examples/dcutr-quic/src/main.rs @@ -0,0 +1,290 @@ +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use clap::Parser; +use futures::{ + executor::{block_on, ThreadPool}, + future::{Either, FutureExt}, + stream::StreamExt, +}; +use libp2p::{ + core::{ + multiaddr::{Multiaddr, Protocol}, + muxing::StreamMuxerBox, + transport::{OrTransport, Transport}, + upgrade, + }, + dcutr, + dns::DnsConfig, + identify, identity, noise, ping, relay, + swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, + yamux, PeerId, +}; +use libp2p_quic as quic; +use log::info; +use std::error::Error; +use std::net::Ipv4Addr; +use std::str::FromStr; + +#[derive(Debug, Parser)] +#[clap(name = "libp2p DCUtR client")] +struct Opts { + /// The mode (client-listen, client-dial). + #[clap(long)] + mode: Mode, + + /// Fixed value to generate deterministic peer id. + #[clap(long)] + secret_key_seed: u8, + + /// The listening address + #[clap(long)] + relay_address: Multiaddr, + + /// Peer ID of the remote peer to hole punch to. + #[clap(long)] + remote_peer_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Parser)] +enum Mode { + Dial, + Listen, +} + +impl FromStr for Mode { + type Err = String; + fn from_str(mode: &str) -> Result { + match mode { + "dial" => Ok(Mode::Dial), + "listen" => Ok(Mode::Listen), + _ => Err("Expected either 'dial' or 'listen'".to_string()), + } + } +} + +fn main() -> Result<(), Box> { + env_logger::init(); + + let opts = Opts::parse(); + + let local_key = generate_ed25519(opts.secret_key_seed); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {:?}", local_peer_id); + + let (relay_transport, client) = relay::client::new(local_peer_id); + let relay_transport = relay_transport + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&local_key).unwrap()) + .multiplex(yamux::Config::default()); + + let transport = OrTransport::new( + relay_transport, + block_on(DnsConfig::system(quic::async_std::Transport::new( + quic::Config::new(&local_key), + ))) + .unwrap(), + ) + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) + .boxed(); + + #[derive(NetworkBehaviour)] + #[behaviour(to_swarm = "Event", event_process = false)] + struct Behaviour { + relay_client: relay::client::Behaviour, + ping: ping::Behaviour, + identify: identify::Behaviour, + dcutr: dcutr::Behaviour, + } + + #[derive(Debug)] + #[allow(clippy::large_enum_variant)] + enum Event { + Ping(ping::Event), + Identify(identify::Event), + Relay(relay::client::Event), + Dcutr(dcutr::Event), + } + + impl From for Event { + fn from(e: ping::Event) -> Self { + Event::Ping(e) + } + } + + impl From for Event { + fn from(e: identify::Event) -> Self { + Event::Identify(e) + } + } + + impl From for Event { + fn from(e: relay::client::Event) -> Self { + Event::Relay(e) + } + } + + impl From for Event { + fn from(e: dcutr::Event) -> Self { + Event::Dcutr(e) + } + } + + let behaviour = Behaviour { + relay_client: client, + ping: ping::Behaviour::new(ping::Config::new()), + identify: identify::Behaviour::new(identify::Config::new( + "/TODO/0.0.1".to_string(), + local_key.public(), + )), + dcutr: dcutr::Behaviour::new(local_peer_id), + }; + + let mut swarm = match ThreadPool::new() { + Ok(tp) => SwarmBuilder::with_executor(transport, behaviour, local_peer_id, tp), + Err(_) => SwarmBuilder::without_executor(transport, behaviour, local_peer_id), + } + .build(); + + swarm + .listen_on( + Multiaddr::empty() + .with("0.0.0.0".parse::().unwrap().into()) + .with(Protocol::Udp(0)) + .with(Protocol::QuicV1), + ) + .unwrap(); + + // Wait to listen on all interfaces. + block_on(async { + let mut delay = futures_timer::Delay::new(std::time::Duration::from_secs(1)).fuse(); + loop { + futures::select! { + event = swarm.next() => { + match event.unwrap() { + SwarmEvent::NewListenAddr { address, .. } => { + info!("Listening on {:?}", address); + } + event => panic!("{event:?}"), + } + } + _ = delay => { + // Likely listening on all interfaces now, thus continuing by breaking the loop. + break; + } + } + } + }); + + // Connect to the relay server. Not for the reservation or relayed connection, but to (a) learn + // our local public address and (b) enable a freshly started relay to learn its public address. + swarm.dial(opts.relay_address.clone()).unwrap(); + block_on(async { + let mut learned_observed_addr = false; + let mut told_relay_observed_addr = false; + + loop { + match swarm.next().await.unwrap() { + SwarmEvent::NewListenAddr { .. } => {} + SwarmEvent::Dialing { .. } => {} + SwarmEvent::ConnectionEstablished { .. } => {} + SwarmEvent::Behaviour(Event::Ping(_)) => {} + SwarmEvent::Behaviour(Event::Identify(identify::Event::Sent { .. })) => { + info!("Told relay its public address."); + told_relay_observed_addr = true; + } + SwarmEvent::Behaviour(Event::Identify(identify::Event::Received { + info: identify::Info { observed_addr, .. }, + .. + })) => { + info!("Relay told us our public address: {:?}", observed_addr); + learned_observed_addr = true; + } + event => panic!("{event:?}"), + } + + if learned_observed_addr && told_relay_observed_addr { + break; + } + } + }); + + match opts.mode { + Mode::Dial => { + swarm + .dial( + opts.relay_address + .with(Protocol::P2pCircuit) + .with(Protocol::P2p(opts.remote_peer_id.unwrap().into())), + ) + .unwrap(); + } + Mode::Listen => { + swarm + .listen_on(opts.relay_address.with(Protocol::P2pCircuit)) + .unwrap(); + } + } + + block_on(async { + loop { + match swarm.next().await.unwrap() { + SwarmEvent::NewListenAddr { address, .. } => { + info!("Listening on {:?}", address); + } + SwarmEvent::Behaviour(Event::Relay( + relay::client::Event::ReservationReqAccepted { .. }, + )) => { + assert!(opts.mode == Mode::Listen); + info!("Relay accepted our reservation request."); + } + SwarmEvent::Behaviour(Event::Relay(event)) => { + info!("{:?}", event) + } + SwarmEvent::Behaviour(Event::Dcutr(event)) => { + info!("{:?}", event) + } + SwarmEvent::Behaviour(Event::Identify(event)) => { + info!("{:?}", event) + } + SwarmEvent::Behaviour(Event::Ping(_)) => {} + SwarmEvent::ConnectionEstablished { + peer_id, endpoint, .. + } => { + info!("Established connection to {:?} via {:?}", peer_id, endpoint); + } + SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => { + info!("Outgoing connection error to {:?}: {:?}", peer_id, error); + } + _ => {} + } + } + }) +} + +fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") +} diff --git a/examples/relay-server-quic/Cargo.toml b/examples/relay-server-quic/Cargo.toml new file mode 100644 index 00000000000..16394d6b32a --- /dev/null +++ b/examples/relay-server-quic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "relay-server-example-quic" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[dependencies] +clap = { version = "4.2.7", features = ["derive"] } +async-std = { version = "1.12", features = ["attributes"] } +async-trait = "0.1" +env_logger = "0.10.0" +futures = "0.3.28" +libp2p = { path = "../../libp2p", features = ["async-std", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } diff --git a/examples/relay-server-quic/src/main.rs b/examples/relay-server-quic/src/main.rs new file mode 100644 index 00000000000..0086586d4a0 --- /dev/null +++ b/examples/relay-server-quic/src/main.rs @@ -0,0 +1,117 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2021 Protocol Labs. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use clap::Parser; +use futures::executor::block_on; +use futures::stream::StreamExt; +use libp2p::{ + core::multiaddr::Protocol, + core::muxing::StreamMuxerBox, + core::{Multiaddr, Transport}, + identify, identity, + identity::PeerId, + ping, relay, + swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, +}; +use libp2p_quic as quic; +use std::error::Error; +use std::net::{Ipv4Addr, Ipv6Addr}; + +fn main() -> Result<(), Box> { + env_logger::init(); + + let opt = Opt::parse(); + println!("opt: {opt:?}"); + + // Create a static known PeerId based on given secret + let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); + let local_peer_id = PeerId::from(local_key.public()); + println!("Local peer id: {local_peer_id:?}"); + + let transport = quic::async_std::Transport::new(quic::Config::new(&local_key)) + .map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer))) + .boxed(); + + let behaviour = Behaviour { + relay: relay::Behaviour::new(local_peer_id, Default::default()), + ping: ping::Behaviour::new(ping::Config::new()), + identify: identify::Behaviour::new(identify::Config::new( + "/TODO/0.0.1".to_string(), + local_key.public(), + )), + }; + + let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build(); + + // Listen on all interfaces + let listen_addr = Multiaddr::empty() + .with(match opt.use_ipv6 { + Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), + _ => Protocol::from(Ipv4Addr::UNSPECIFIED), + }) + .with(Protocol::Udp(opt.port)) + .with(Protocol::QuicV1); + swarm.listen_on(listen_addr)?; + + block_on(async { + loop { + match swarm.next().await.expect("Infinite Stream.") { + SwarmEvent::Behaviour(event) => { + println!("{event:?}") + } + SwarmEvent::NewListenAddr { address, .. } => { + println!("Listening on {address:?}"); + } + _ => {} + } + } + }) +} + +#[derive(NetworkBehaviour)] +struct Behaviour { + relay: relay::Behaviour, + ping: ping::Behaviour, + identify: identify::Behaviour, +} + +fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = secret_key_seed; + + identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") +} + +#[derive(Debug, Parser)] +#[clap(name = "libp2p relay")] +struct Opt { + /// Determine if the relay listen on ipv6 or ipv4 loopback address. the default is ipv4 + #[clap(long)] + use_ipv6: Option, + + /// Fixed value to generate deterministic peer id + #[clap(long)] + secret_key_seed: u8, + + /// The port used to listen on all interfaces + #[clap(long)] + port: u16, +} diff --git a/protocols/dcutr/src/behaviour_impl.rs b/protocols/dcutr/src/behaviour_impl.rs index 57692d38898..c3a4c6df347 100644 --- a/protocols/dcutr/src/behaviour_impl.rs +++ b/protocols/dcutr/src/behaviour_impl.rs @@ -246,32 +246,23 @@ impl NetworkBehaviour for Behaviour { local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - match self - .outgoing_direct_connection_attempts - .remove(&(connection_id, peer)) + if is_relayed(local_addr) { + Ok(Either::Left(handler::relayed::Handler::new( + ConnectedPoint::Listener { + local_addr: local_addr.clone(), + send_back_addr: remote_addr.clone(), + }, + ))) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + } else if let Some(&relayed_connection_id) = + self.direct_to_relayed_connections.get(&connection_id) { - None => { - let handler = if is_relayed(local_addr) { - Either::Left(handler::relayed::Handler::new(ConnectedPoint::Listener { - local_addr: local_addr.clone(), - send_back_addr: remote_addr.clone(), - })) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } else { - Either::Right(Either::Right(dummy::ConnectionHandler)) - }; - - Ok(handler) - } - Some(_) => { - assert!( - !is_relayed(local_addr), - "`Prototype::DirectConnection` is never created for relayed connection." - ); - - Ok(Either::Right(Either::Left( - handler::direct::Handler::default(), - ))) - } + self.outgoing_direct_connection_attempts + .remove(&(relayed_connection_id, peer)); + Ok(Either::Right(Either::Left( + handler::direct::Handler::default(), + ))) + } else { + Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) } } @@ -282,32 +273,23 @@ impl NetworkBehaviour for Behaviour { addr: &Multiaddr, role_override: Endpoint, ) -> Result, ConnectionDenied> { - match self - .outgoing_direct_connection_attempts - .remove(&(connection_id, peer)) + if is_relayed(addr) { + Ok(Either::Left(handler::relayed::Handler::new( + ConnectedPoint::Dialer { + address: addr.clone(), + role_override, + }, + ))) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + } else if let Some(&relayed_connection_id) = + self.direct_to_relayed_connections.get(&connection_id) { - None => { - let handler = if is_relayed(addr) { - Either::Left(handler::relayed::Handler::new(ConnectedPoint::Dialer { - address: addr.clone(), - role_override, - })) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } else { - Either::Right(Either::Right(dummy::ConnectionHandler)) - }; - - Ok(handler) - } - Some(_) => { - assert!( - !is_relayed(addr), - "`Prototype::DirectConnection` is never created for relayed connection." - ); - - Ok(Either::Right(Either::Left( - handler::direct::Handler::default(), - ))) - } + self.outgoing_direct_connection_attempts + .remove(&(relayed_connection_id, peer)); + Ok(Either::Right(Either::Left( + handler::direct::Handler::default(), + ))) + } else { + Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) } } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 668034ed147..5f528cc1769 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -22,11 +22,13 @@ use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; -use futures::channel::{mpsc, oneshot}; +use futures::channel::mpsc; +use futures::channel::oneshot::{self, Receiver}; use futures::future::BoxFuture; use futures::ready; use futures::stream::StreamExt; use futures::{prelude::*, stream::SelectAll}; +use futures_timer::Delay; use if_watch::IfEvent; @@ -36,6 +38,7 @@ use libp2p_core::{ Transport, }; use libp2p_identity::PeerId; +use rand::{distributions, Rng}; use std::collections::hash_map::{DefaultHasher, Entry}; use std::collections::{HashMap, VecDeque}; use std::fmt; @@ -73,6 +76,19 @@ pub struct GenTransport { dialer: HashMap, /// Waker to poll the transport again when a new dialer or listener is added. waker: Option, + /// Holepunching attempts + hole_punching: HashMap, +} + +#[derive(Debug, PartialEq, Eq, Hash)] +struct HolePunchKey { + addr: SocketAddr, + // peer_id: PeerId, +} + +#[derive(Debug)] +struct ActiveHolePunch { + sender: Option>, } impl GenTransport

{ @@ -88,6 +104,7 @@ impl GenTransport

{ dialer: HashMap::new(), waker: None, support_draft_29, + hole_punching: HashMap::new(), } } } @@ -198,11 +215,66 @@ impl Transport for GenTransport

{ &mut self, addr: Multiaddr, ) -> Result> { - // TODO: As the listener of a QUIC hole punch, we need to send a random UDP packet to the - // `addr`. See DCUtR specification below. - // - // https://github.com/libp2p/specs/blob/master/relay/DCUtR.md#the-protocol - Err(TransportError::MultiaddrNotSupported(addr)) + let (socket_addr, _version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + + let listeners = self + .listeners + .iter() + .filter(|l| { + if l.is_closed { + return false; + } + let listen_addr = l.endpoint_channel.socket_addr(); + SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) + && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() + }) + .collect::>(); + + // which listener to use to send packets? go-libp2p uses routing table with random port + let endpoint_channel = match listeners.len() { + 0 => { + // No listener. Get or create an explicit dialer. + let socket_family = socket_addr.ip().into(); + let dialer = match self.dialer.entry(socket_family) { + Entry::Occupied(occupied) => occupied.into_mut(), + Entry::Vacant(vacant) => { + if let Some(waker) = self.waker.take() { + waker.wake(); + } + vacant.insert(Dialer::new::

(self.quinn_config.clone(), socket_family)?) + } + }; + dialer.endpoint_channel.clone() + } + 1 => listeners[0].endpoint_channel.clone(), + _ => { + // Pick any listener to use for dialing. + // We hash the socket address to achieve determinism. + let mut hasher = DefaultHasher::new(); + socket_addr.hash(&mut hasher); + let index = hasher.finish() as usize % listeners.len(); + listeners[index].endpoint_channel.clone() + } + }; + + let (sender, receiver) = oneshot::channel(); + self.hole_punching.insert( + HolePunchKey { addr: socket_addr }, + ActiveHolePunch { + sender: Some(sender), + }, + ); + Ok(HolePuncher::new( + endpoint_channel, + socket_addr, + self.handshake_timeout, + receiver, + ) + .boxed()) } fn poll( @@ -223,7 +295,41 @@ impl Transport for GenTransport

{ } if let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { - return Poll::Ready(ev); + match ev { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + let hole_punch_key = HolePunchKey { + addr: multiaddr_to_socketaddr(&send_back_addr, self.support_draft_29) + .unwrap() + .0, + }; + + if let Some(mut hole_punch) = self.hole_punching.remove(&hole_punch_key) { + if let Some(sender) = hole_punch.sender.take() { + sender.send(upgrade).unwrap(); + } else { + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); + } + } else { + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); + } + } + _ => return Poll::Ready(ev), + } } self.waker = Some(cx.waker().clone()); @@ -231,6 +337,88 @@ impl Transport for GenTransport

{ } } +struct HolePuncher { + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout: Delay, + interval_timeout: Delay, + receiver: Receiver, + connecting: Option, +} + +impl HolePuncher { + fn new( + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout: Duration, + receiver: Receiver, + ) -> Self { + Self { + endpoint_channel, + remote_addr, + timeout: Delay::new(timeout), + interval_timeout: Delay::new(Duration::from_secs(0)), + receiver, + connecting: None, + } + } +} + +impl Future for HolePuncher { + type Output = Result<(PeerId, Connection), Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(connecting) = &mut self.connecting { + match connecting.poll_unpin(cx) { + Poll::Pending => {} + ready => return ready, + } + } + + match self.receiver.poll_unpin(cx) { + Poll::Ready(Ok(upgrade)) => { + self.connecting = Some(upgrade); + } + Poll::Ready(Err(_)) => {} + Poll::Pending => {} + } + + match self.timeout.poll_unpin(cx) { + Poll::Ready(_) => return Poll::Ready(Err(Error::HandshakeTimedOut)), + Poll::Pending => {} + } + + match self.interval_timeout.poll_unpin(cx) { + Poll::Ready(_) => { + let message = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { + destination: self.remote_addr, + ecn: None, + contents: rand::thread_rng() + .sample_iter(distributions::Standard) + .take(64) + .collect(), + segment_size: None, + src_ip: None, + }); + + match self.endpoint_channel.try_send(message, cx) { + Ok(_) => {} + Err(endpoint::Disconnected {}) => { + return Poll::Ready(Err(Error::EndpointDriverCrashed)) + } + } + + self.interval_timeout.reset(Duration::from_millis( + rand::thread_rng().gen_range(10..=200), + )); + } + Poll::Pending => {} + } + + Poll::Pending + } +} + impl From for TransportError { fn from(err: Error) -> Self { TransportError::Other(err) From 145bf119c09fb7abdfab492f47a108c078294508 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 23 May 2023 17:48:52 +0530 Subject: [PATCH 02/39] Merge QUIC dcutr and relay examples --- Cargo.lock | 24 -- Cargo.toml | 2 - examples/dcutr-quic/Cargo.toml | 15 -- examples/dcutr-quic/src/main.rs | 290 ------------------------- examples/dcutr/Cargo.toml | 1 + examples/dcutr/src/main.rs | 65 ++++-- examples/relay-server-quic/Cargo.toml | 15 -- examples/relay-server-quic/src/main.rs | 117 ---------- examples/relay-server/Cargo.toml | 1 + examples/relay-server/src/main.rs | 30 ++- 10 files changed, 71 insertions(+), 489 deletions(-) delete mode 100644 examples/dcutr-quic/Cargo.toml delete mode 100644 examples/dcutr-quic/src/main.rs delete mode 100644 examples/relay-server-quic/Cargo.toml delete mode 100644 examples/relay-server-quic/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index ecb626c5599..dd7af22d447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,18 +1258,6 @@ dependencies = [ [[package]] name = "dcutr" version = "0.1.0" -dependencies = [ - "clap 4.2.7", - "env_logger 0.10.0", - "futures", - "futures-timer", - "libp2p", - "log", -] - -[[package]] -name = "dcutr-quic" -version = "0.1.0" dependencies = [ "clap 4.2.7", "env_logger 0.10.0", @@ -4293,18 +4281,6 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "relay-server-example" version = "0.1.0" -dependencies = [ - "async-std", - "async-trait", - "clap 4.2.7", - "env_logger 0.10.0", - "futures", - "libp2p", -] - -[[package]] -name = "relay-server-example-quic" -version = "0.1.0" dependencies = [ "async-std", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 0a4ce5752c1..8400238368d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "examples/chat-example", "examples/autonat", "examples/dcutr", - "examples/dcutr-quic", "examples/distributed-key-value-store", "examples/file-sharing", "examples/identify", @@ -13,7 +12,6 @@ members = [ "examples/metrics", "examples/ping-example", "examples/relay-server", - "examples/relay-server-quic", "examples/rendezvous", "identity", "interop-tests", diff --git a/examples/dcutr-quic/Cargo.toml b/examples/dcutr-quic/Cargo.toml deleted file mode 100644 index 5713615ba9f..00000000000 --- a/examples/dcutr-quic/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "dcutr-quic" -version = "0.1.0" -edition = "2021" -publish = false -license = "MIT" - -[dependencies] -clap = { version = "4.2.7", features = ["derive"] } -env_logger = "0.10.0" -futures = "0.3.28" -futures-timer = "3.0" -libp2p = { path = "../../libp2p", features = ["async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "relay", "rendezvous", "tcp", "tokio", "yamux"] } -libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } -log = "0.4" diff --git a/examples/dcutr-quic/src/main.rs b/examples/dcutr-quic/src/main.rs deleted file mode 100644 index 7c90828dd87..00000000000 --- a/examples/dcutr-quic/src/main.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2021 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use clap::Parser; -use futures::{ - executor::{block_on, ThreadPool}, - future::{Either, FutureExt}, - stream::StreamExt, -}; -use libp2p::{ - core::{ - multiaddr::{Multiaddr, Protocol}, - muxing::StreamMuxerBox, - transport::{OrTransport, Transport}, - upgrade, - }, - dcutr, - dns::DnsConfig, - identify, identity, noise, ping, relay, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, - yamux, PeerId, -}; -use libp2p_quic as quic; -use log::info; -use std::error::Error; -use std::net::Ipv4Addr; -use std::str::FromStr; - -#[derive(Debug, Parser)] -#[clap(name = "libp2p DCUtR client")] -struct Opts { - /// The mode (client-listen, client-dial). - #[clap(long)] - mode: Mode, - - /// Fixed value to generate deterministic peer id. - #[clap(long)] - secret_key_seed: u8, - - /// The listening address - #[clap(long)] - relay_address: Multiaddr, - - /// Peer ID of the remote peer to hole punch to. - #[clap(long)] - remote_peer_id: Option, -} - -#[derive(Clone, Debug, PartialEq, Parser)] -enum Mode { - Dial, - Listen, -} - -impl FromStr for Mode { - type Err = String; - fn from_str(mode: &str) -> Result { - match mode { - "dial" => Ok(Mode::Dial), - "listen" => Ok(Mode::Listen), - _ => Err("Expected either 'dial' or 'listen'".to_string()), - } - } -} - -fn main() -> Result<(), Box> { - env_logger::init(); - - let opts = Opts::parse(); - - let local_key = generate_ed25519(opts.secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - info!("Local peer id: {:?}", local_peer_id); - - let (relay_transport, client) = relay::client::new(local_peer_id); - let relay_transport = relay_transport - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&local_key).unwrap()) - .multiplex(yamux::Config::default()); - - let transport = OrTransport::new( - relay_transport, - block_on(DnsConfig::system(quic::async_std::Transport::new( - quic::Config::new(&local_key), - ))) - .unwrap(), - ) - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - }) - .boxed(); - - #[derive(NetworkBehaviour)] - #[behaviour(to_swarm = "Event", event_process = false)] - struct Behaviour { - relay_client: relay::client::Behaviour, - ping: ping::Behaviour, - identify: identify::Behaviour, - dcutr: dcutr::Behaviour, - } - - #[derive(Debug)] - #[allow(clippy::large_enum_variant)] - enum Event { - Ping(ping::Event), - Identify(identify::Event), - Relay(relay::client::Event), - Dcutr(dcutr::Event), - } - - impl From for Event { - fn from(e: ping::Event) -> Self { - Event::Ping(e) - } - } - - impl From for Event { - fn from(e: identify::Event) -> Self { - Event::Identify(e) - } - } - - impl From for Event { - fn from(e: relay::client::Event) -> Self { - Event::Relay(e) - } - } - - impl From for Event { - fn from(e: dcutr::Event) -> Self { - Event::Dcutr(e) - } - } - - let behaviour = Behaviour { - relay_client: client, - ping: ping::Behaviour::new(ping::Config::new()), - identify: identify::Behaviour::new(identify::Config::new( - "/TODO/0.0.1".to_string(), - local_key.public(), - )), - dcutr: dcutr::Behaviour::new(local_peer_id), - }; - - let mut swarm = match ThreadPool::new() { - Ok(tp) => SwarmBuilder::with_executor(transport, behaviour, local_peer_id, tp), - Err(_) => SwarmBuilder::without_executor(transport, behaviour, local_peer_id), - } - .build(); - - swarm - .listen_on( - Multiaddr::empty() - .with("0.0.0.0".parse::().unwrap().into()) - .with(Protocol::Udp(0)) - .with(Protocol::QuicV1), - ) - .unwrap(); - - // Wait to listen on all interfaces. - block_on(async { - let mut delay = futures_timer::Delay::new(std::time::Duration::from_secs(1)).fuse(); - loop { - futures::select! { - event = swarm.next() => { - match event.unwrap() { - SwarmEvent::NewListenAddr { address, .. } => { - info!("Listening on {:?}", address); - } - event => panic!("{event:?}"), - } - } - _ = delay => { - // Likely listening on all interfaces now, thus continuing by breaking the loop. - break; - } - } - } - }); - - // Connect to the relay server. Not for the reservation or relayed connection, but to (a) learn - // our local public address and (b) enable a freshly started relay to learn its public address. - swarm.dial(opts.relay_address.clone()).unwrap(); - block_on(async { - let mut learned_observed_addr = false; - let mut told_relay_observed_addr = false; - - loop { - match swarm.next().await.unwrap() { - SwarmEvent::NewListenAddr { .. } => {} - SwarmEvent::Dialing { .. } => {} - SwarmEvent::ConnectionEstablished { .. } => {} - SwarmEvent::Behaviour(Event::Ping(_)) => {} - SwarmEvent::Behaviour(Event::Identify(identify::Event::Sent { .. })) => { - info!("Told relay its public address."); - told_relay_observed_addr = true; - } - SwarmEvent::Behaviour(Event::Identify(identify::Event::Received { - info: identify::Info { observed_addr, .. }, - .. - })) => { - info!("Relay told us our public address: {:?}", observed_addr); - learned_observed_addr = true; - } - event => panic!("{event:?}"), - } - - if learned_observed_addr && told_relay_observed_addr { - break; - } - } - }); - - match opts.mode { - Mode::Dial => { - swarm - .dial( - opts.relay_address - .with(Protocol::P2pCircuit) - .with(Protocol::P2p(opts.remote_peer_id.unwrap().into())), - ) - .unwrap(); - } - Mode::Listen => { - swarm - .listen_on(opts.relay_address.with(Protocol::P2pCircuit)) - .unwrap(); - } - } - - block_on(async { - loop { - match swarm.next().await.unwrap() { - SwarmEvent::NewListenAddr { address, .. } => { - info!("Listening on {:?}", address); - } - SwarmEvent::Behaviour(Event::Relay( - relay::client::Event::ReservationReqAccepted { .. }, - )) => { - assert!(opts.mode == Mode::Listen); - info!("Relay accepted our reservation request."); - } - SwarmEvent::Behaviour(Event::Relay(event)) => { - info!("{:?}", event) - } - SwarmEvent::Behaviour(Event::Dcutr(event)) => { - info!("{:?}", event) - } - SwarmEvent::Behaviour(Event::Identify(event)) => { - info!("{:?}", event) - } - SwarmEvent::Behaviour(Event::Ping(_)) => {} - SwarmEvent::ConnectionEstablished { - peer_id, endpoint, .. - } => { - info!("Established connection to {:?} via {:?}", peer_id, endpoint); - } - SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => { - info!("Outgoing connection error to {:?}: {:?}", peer_id, error); - } - _ => {} - } - } - }) -} - -fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { - let mut bytes = [0u8; 32]; - bytes[0] = secret_key_seed; - - identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") -} diff --git a/examples/dcutr/Cargo.toml b/examples/dcutr/Cargo.toml index ea3b8fb2a9d..c72c26752db 100644 --- a/examples/dcutr/Cargo.toml +++ b/examples/dcutr/Cargo.toml @@ -11,4 +11,5 @@ env_logger = "0.10.0" futures = "0.3.28" futures-timer = "3.0" libp2p = { path = "../../libp2p", features = ["async-std", "dns", "dcutr", "identify", "macros", "noise", "ping", "relay", "rendezvous", "tcp", "tokio", "yamux"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } log = "0.4" diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index 06269f3c444..b6e9186f772 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -21,12 +21,13 @@ use clap::Parser; use futures::{ executor::{block_on, ThreadPool}, - future::FutureExt, + future::{Either, FutureExt}, stream::StreamExt, }; use libp2p::{ core::{ multiaddr::{Multiaddr, Protocol}, + muxing::StreamMuxerBox, transport::{OrTransport, Transport}, upgrade, }, @@ -36,6 +37,7 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, tcp, yamux, PeerId, }; +use libp2p_quic as quic; use log::info; use std::error::Error; use std::net::Ipv4Addr; @@ -89,19 +91,40 @@ fn main() -> Result<(), Box> { let (relay_transport, client) = relay::client::new(local_peer_id); - let transport = OrTransport::new( - relay_transport, - block_on(DnsConfig::system(tcp::async_io::Transport::new( - tcp::Config::default().port_reuse(true), - ))) - .unwrap(), - ) - .upgrade(upgrade::Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), - ) - .multiplex(yamux::Config::default()) - .boxed(); + let is_quic_relay = opts.relay_address.iter().any(|p| p == Protocol::QuicV1); + let transport = if is_quic_relay { + let relay_transport = relay_transport + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&local_key).unwrap()) + .multiplex(yamux::Config::default()); + + OrTransport::new( + relay_transport, + block_on(DnsConfig::system(quic::async_std::Transport::new( + quic::Config::new(&local_key), + ))) + .unwrap(), + ) + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) + .boxed() + } else { + OrTransport::new( + relay_transport, + block_on(DnsConfig::system(tcp::async_io::Transport::new( + tcp::Config::default().port_reuse(true), + ))) + .unwrap(), + ) + .upgrade(upgrade::Version::V1Lazy) + .authenticate( + noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), + ) + .multiplex(yamux::Config::default()) + .boxed() + }; #[derive(NetworkBehaviour)] #[behaviour(to_swarm = "Event", event_process = false)] @@ -161,13 +184,13 @@ fn main() -> Result<(), Box> { } .build(); - swarm - .listen_on( - Multiaddr::empty() - .with("0.0.0.0".parse::().unwrap().into()) - .with(Protocol::Tcp(0)), - ) - .unwrap(); + let listen_addr = Multiaddr::empty().with("0.0.0.0".parse::().unwrap().into()); + let listen_addr = if is_quic_relay { + listen_addr.with(Protocol::Udp(0)).with(Protocol::QuicV1) + } else { + listen_addr.with(Protocol::Tcp(0)) + }; + swarm.listen_on(listen_addr).unwrap(); // Wait to listen on all interfaces. block_on(async { diff --git a/examples/relay-server-quic/Cargo.toml b/examples/relay-server-quic/Cargo.toml deleted file mode 100644 index 16394d6b32a..00000000000 --- a/examples/relay-server-quic/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "relay-server-example-quic" -version = "0.1.0" -edition = "2021" -publish = false -license = "MIT" - -[dependencies] -clap = { version = "4.2.7", features = ["derive"] } -async-std = { version = "1.12", features = ["attributes"] } -async-trait = "0.1" -env_logger = "0.10.0" -futures = "0.3.28" -libp2p = { path = "../../libp2p", features = ["async-std", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay"] } -libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } diff --git a/examples/relay-server-quic/src/main.rs b/examples/relay-server-quic/src/main.rs deleted file mode 100644 index 0086586d4a0..00000000000 --- a/examples/relay-server-quic/src/main.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// Copyright 2021 Protocol Labs. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use clap::Parser; -use futures::executor::block_on; -use futures::stream::StreamExt; -use libp2p::{ - core::multiaddr::Protocol, - core::muxing::StreamMuxerBox, - core::{Multiaddr, Transport}, - identify, identity, - identity::PeerId, - ping, relay, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, -}; -use libp2p_quic as quic; -use std::error::Error; -use std::net::{Ipv4Addr, Ipv6Addr}; - -fn main() -> Result<(), Box> { - env_logger::init(); - - let opt = Opt::parse(); - println!("opt: {opt:?}"); - - // Create a static known PeerId based on given secret - let local_key: identity::Keypair = generate_ed25519(opt.secret_key_seed); - let local_peer_id = PeerId::from(local_key.public()); - println!("Local peer id: {local_peer_id:?}"); - - let transport = quic::async_std::Transport::new(quic::Config::new(&local_key)) - .map(|(peer_id, muxer), _| (peer_id, StreamMuxerBox::new(muxer))) - .boxed(); - - let behaviour = Behaviour { - relay: relay::Behaviour::new(local_peer_id, Default::default()), - ping: ping::Behaviour::new(ping::Config::new()), - identify: identify::Behaviour::new(identify::Config::new( - "/TODO/0.0.1".to_string(), - local_key.public(), - )), - }; - - let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build(); - - // Listen on all interfaces - let listen_addr = Multiaddr::empty() - .with(match opt.use_ipv6 { - Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), - _ => Protocol::from(Ipv4Addr::UNSPECIFIED), - }) - .with(Protocol::Udp(opt.port)) - .with(Protocol::QuicV1); - swarm.listen_on(listen_addr)?; - - block_on(async { - loop { - match swarm.next().await.expect("Infinite Stream.") { - SwarmEvent::Behaviour(event) => { - println!("{event:?}") - } - SwarmEvent::NewListenAddr { address, .. } => { - println!("Listening on {address:?}"); - } - _ => {} - } - } - }) -} - -#[derive(NetworkBehaviour)] -struct Behaviour { - relay: relay::Behaviour, - ping: ping::Behaviour, - identify: identify::Behaviour, -} - -fn generate_ed25519(secret_key_seed: u8) -> identity::Keypair { - let mut bytes = [0u8; 32]; - bytes[0] = secret_key_seed; - - identity::Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") -} - -#[derive(Debug, Parser)] -#[clap(name = "libp2p relay")] -struct Opt { - /// Determine if the relay listen on ipv6 or ipv4 loopback address. the default is ipv4 - #[clap(long)] - use_ipv6: Option, - - /// Fixed value to generate deterministic peer id - #[clap(long)] - secret_key_seed: u8, - - /// The port used to listen on all interfaces - #[clap(long)] - port: u16, -} diff --git a/examples/relay-server/Cargo.toml b/examples/relay-server/Cargo.toml index 97bf64f21a8..8158d5f518e 100644 --- a/examples/relay-server/Cargo.toml +++ b/examples/relay-server/Cargo.toml @@ -12,3 +12,4 @@ async-trait = "0.1" env_logger = "0.10.0" futures = "0.3.28" libp2p = { path = "../../libp2p", features = ["async-std", "noise", "macros", "ping", "tcp", "identify", "yamux", "relay"] } +libp2p-quic = { path = "../../transports/quic", features = ["async-std"] } diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index 416d4e7c111..ad382a1ae8e 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -20,10 +20,11 @@ // DEALINGS IN THE SOFTWARE. use clap::Parser; -use futures::executor::block_on; use futures::stream::StreamExt; +use futures::{executor::block_on, future::Either}; use libp2p::{ core::multiaddr::Protocol, + core::muxing::StreamMuxerBox, core::upgrade, core::{Multiaddr, Transport}, identify, identity, @@ -32,6 +33,7 @@ use libp2p::{ swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, tcp, }; +use libp2p_quic as quic; use std::error::Error; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -48,12 +50,21 @@ fn main() -> Result<(), Box> { let tcp_transport = tcp::async_io::Transport::default(); - let transport = tcp_transport + let tcp_transport = tcp_transport .upgrade(upgrade::Version::V1Lazy) .authenticate( noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), ) - .multiplex(libp2p::yamux::Config::default()) + .multiplex(libp2p::yamux::Config::default()); + + let quic_transport = quic::async_std::Transport::new(quic::Config::new(&local_key)); + + let transport = quic_transport + .or_transport(tcp_transport) + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) .boxed(); let behaviour = Behaviour { @@ -68,13 +79,22 @@ fn main() -> Result<(), Box> { let mut swarm = SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build(); // Listen on all interfaces - let listen_addr = Multiaddr::empty() + let listen_addr_tcp = Multiaddr::empty() .with(match opt.use_ipv6 { Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), _ => Protocol::from(Ipv4Addr::UNSPECIFIED), }) .with(Protocol::Tcp(opt.port)); - swarm.listen_on(listen_addr)?; + swarm.listen_on(listen_addr_tcp)?; + + let listen_addr_quic = Multiaddr::empty() + .with(match opt.use_ipv6 { + Some(true) => Protocol::from(Ipv6Addr::UNSPECIFIED), + _ => Protocol::from(Ipv4Addr::UNSPECIFIED), + }) + .with(Protocol::Udp(opt.port)) + .with(Protocol::QuicV1); + swarm.listen_on(listen_addr_quic)?; block_on(async { loop { From 0eafaf482516840153f95be2601fe621d57bfe9e Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 23 May 2023 18:25:16 +0530 Subject: [PATCH 03/39] Simplify nested if let --- transports/quic/src/transport.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 5f528cc1769..4a319fbbfef 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -308,17 +308,12 @@ impl Transport for GenTransport

{ .0, }; - if let Some(mut hole_punch) = self.hole_punching.remove(&hole_punch_key) { - if let Some(sender) = hole_punch.sender.take() { - sender.send(upgrade).unwrap(); - } else { - return Poll::Ready(TransportEvent::Incoming { - listener_id, - upgrade, - local_addr, - send_back_addr, - }); - } + if let Some(sender) = self + .hole_punching + .remove(&hole_punch_key) + .and_then(|mut h| h.sender.take()) + { + sender.send(upgrade).unwrap(); } else { return Poll::Ready(TransportEvent::Incoming { listener_id, From fd70f734f3fe5063446ff203c15c3270ee2feefd Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 23 May 2023 18:54:48 +0530 Subject: [PATCH 04/39] Return error when dial_as_listener is called without an active listener --- transports/quic/src/lib.rs | 4 ++++ transports/quic/src/transport.rs | 15 +++------------ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 594ba0b6108..1871275f0b7 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -94,6 +94,10 @@ pub enum Error { /// The [`Connecting`] future timed out. #[error("Handshake with the remote timed out.")] HandshakeTimedOut, + + /// Error when `Transport::dial_as_listener` is called without an active listener. + #[error("Tried to dial as listener without an active listener.")] + NoActiveListenerForDialAsListener, } /// Dialing a remote peer failed. diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 4a319fbbfef..a8214a1bfb5 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -237,18 +237,9 @@ impl Transport for GenTransport

{ // which listener to use to send packets? go-libp2p uses routing table with random port let endpoint_channel = match listeners.len() { 0 => { - // No listener. Get or create an explicit dialer. - let socket_family = socket_addr.ip().into(); - let dialer = match self.dialer.entry(socket_family) { - Entry::Occupied(occupied) => occupied.into_mut(), - Entry::Vacant(vacant) => { - if let Some(waker) = self.waker.take() { - waker.wake(); - } - vacant.insert(Dialer::new::

(self.quinn_config.clone(), socket_family)?) - } - }; - dialer.endpoint_channel.clone() + return Err(TransportError::Other( + Error::NoActiveListenerForDialAsListener, + )); } 1 => listeners[0].endpoint_channel.clone(), _ => { From ddacaa00bbaee93569f22a117c4ba376f72f8099 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 25 May 2023 13:51:45 +0530 Subject: [PATCH 05/39] Extract peer_id in multiaddr_to_socketaddr --- transports/quic/src/transport.rs | 43 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index a8214a1bfb5..be0ccb6f144 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -120,8 +120,9 @@ impl Transport for GenTransport

{ listener_id: ListenerId, addr: Multiaddr, ) -> Result<(), TransportError> { - let (socket_addr, version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or(TransportError::MultiaddrNotSupported(addr))?; + let (socket_addr, version, _peer_id) = + multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or(TransportError::MultiaddrNotSupported(addr))?; let listener = Listener::new( listener_id, socket_addr, @@ -164,8 +165,9 @@ impl Transport for GenTransport

{ } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (socket_addr, version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + let (socket_addr, version, _peer_id) = + multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } @@ -215,8 +217,9 @@ impl Transport for GenTransport

{ &mut self, addr: Multiaddr, ) -> Result> { - let (socket_addr, _version) = multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + let (socket_addr, _version, _peer_id) = + multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } @@ -768,15 +771,18 @@ fn ip_to_listenaddr( fn multiaddr_to_socketaddr( addr: &Multiaddr, support_draft_29: bool, -) -> Option<(SocketAddr, ProtocolVersion)> { +) -> Option<(SocketAddr, ProtocolVersion, Option)> { let mut iter = addr.iter(); let proto1 = iter.next()?; let proto2 = iter.next()?; let proto3 = iter.next()?; + let mut peer_id = None; for proto in iter { match proto { - Protocol::P2p(_) => {} // Ignore a `/p2p/...` prefix of possibly outer protocols, if present. + Protocol::P2p(id) => { + peer_id = Some(PeerId::from_multihash(id).ok()?); + } _ => return None, } } @@ -788,10 +794,10 @@ fn multiaddr_to_socketaddr( match (proto1, proto2) { (Protocol::Ip4(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version)) + Some((SocketAddr::new(ip.into(), port), version, peer_id)) } (Protocol::Ip6(ip), Protocol::Udp(port)) => { - Some((SocketAddr::new(ip.into(), port), version)) + Some((SocketAddr::new(ip.into(), port), version, peer_id)) } _ => None, } @@ -865,7 +871,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12345,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -877,7 +884,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255)), 8080,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -889,7 +897,7 @@ mod test { Some((SocketAddr::new( IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 55148, - ), ProtocolVersion::V1)) + ), ProtocolVersion::V1, Some("12D3KooW9xk7Zp1gejwfwNpfm6L9zH5NL4Bx5rm94LRYJJHJuARZ".parse().unwrap()))) ); assert_eq!( multiaddr_to_socketaddr( @@ -898,7 +906,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 12345,), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); assert_eq!( @@ -915,7 +924,8 @@ mod test { )), 8080, ), - ProtocolVersion::V1 + ProtocolVersion::V1, + None )) ); @@ -931,7 +941,8 @@ mod test { ), Some(( SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1234,), - ProtocolVersion::Draft29 + ProtocolVersion::Draft29, + None )) ); } From 3325dbe18326e9aca77dc4ac76a72419fa0c9766 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 25 May 2023 18:52:38 +0530 Subject: [PATCH 06/39] Add peerid to holepunching key Co-authored-by: Roman --- transports/quic/src/hole_punching.rs | 126 +++++++++++++++++ transports/quic/src/lib.rs | 1 + transports/quic/src/transport.rs | 194 +++++++-------------------- 3 files changed, 174 insertions(+), 147 deletions(-) create mode 100644 transports/quic/src/hole_punching.rs diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs new file mode 100644 index 00000000000..8d400d70f72 --- /dev/null +++ b/transports/quic/src/hole_punching.rs @@ -0,0 +1,126 @@ +use std::{ + collections::HashMap, + net::SocketAddr, + pin::Pin, + sync::{Arc, Mutex}, + task::{Context, Poll}, + time::Duration, +}; + +use futures::{ + channel::oneshot, + future::{Fuse, FusedFuture}, + prelude::*, +}; +use futures_timer::Delay; +use libp2p_identity::PeerId; +use rand::{distributions, Rng}; + +use crate::{ + endpoint::{self, ToEndpoint}, + Connecting, Connection, Error, +}; + +pub(crate) type HolePunchMap = + Arc>>>; + +pub(crate) struct MaybeHolePunchedConnection { + hole_punch_map: HolePunchMap, + addr: SocketAddr, + upgrade: Fuse, +} + +impl MaybeHolePunchedConnection { + pub(crate) fn new(hole_punch_map: HolePunchMap, addr: SocketAddr, upgrade: Connecting) -> Self { + Self { + hole_punch_map, + addr, + upgrade: upgrade.fuse(), + } + } +} + +impl Future for MaybeHolePunchedConnection { + type Output = Result<(PeerId, Connection), Error>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let (peer_id, connection) = futures::ready!(self.upgrade.poll_unpin(cx))?; + let addr = self.addr; + let mut hole_punch_map = self.hole_punch_map.lock().unwrap(); + if let Some(sender) = hole_punch_map.remove(&(addr, peer_id)) { + if let Err(connection) = sender.send((peer_id, connection)) { + Poll::Ready(Ok(connection)) + } else { + Poll::Pending + } + } else { + Poll::Ready(Ok((peer_id, connection))) + } + } +} + +impl FusedFuture for MaybeHolePunchedConnection { + fn is_terminated(&self) -> bool { + self.upgrade.is_terminated() + } +} + +pub(crate) struct HolePuncher { + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout: Delay, + interval_timeout: Delay, +} + +impl HolePuncher { + pub(crate) fn new( + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout: Duration, + ) -> Self { + Self { + endpoint_channel, + remote_addr, + timeout: Delay::new(timeout), + interval_timeout: Delay::new(Duration::from_secs(0)), + } + } +} + +/// Never finishes successfully, only with an Err (timeout) +impl Future for HolePuncher { + type Output = Error; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.timeout.poll_unpin(cx) { + Poll::Ready(_) => return Poll::Ready(Error::HandshakeTimedOut), + Poll::Pending => {} + } + + futures::ready!(self.interval_timeout.poll_unpin(cx)); + + let message = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { + destination: self.remote_addr, + ecn: None, + contents: rand::thread_rng() + .sample_iter(distributions::Standard) + .take(64) + .collect(), + segment_size: None, + src_ip: None, + }); + + match self.endpoint_channel.try_send(message, cx) { + Ok(_) => {} + Err(endpoint::Disconnected {}) => { + return Poll::Ready(Error::EndpointDriverCrashed); + } + } + + self.interval_timeout.reset(Duration::from_millis( + rand::thread_rng().gen_range(10..=200), + )); + + Poll::Pending + } +} diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 1871275f0b7..2d92ccb835e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -59,6 +59,7 @@ mod connection; mod endpoint; +mod hole_punching; mod provider; mod transport; diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index be0ccb6f144..a820807cf01 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -19,16 +19,16 @@ // DEALINGS IN THE SOFTWARE. use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; +use crate::hole_punching::{HolePunchMap, HolePuncher, MaybeHolePunchedConnection}; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; use futures::channel::mpsc; -use futures::channel::oneshot::{self, Receiver}; +use futures::channel::oneshot; use futures::future::BoxFuture; use futures::ready; use futures::stream::StreamExt; use futures::{prelude::*, stream::SelectAll}; -use futures_timer::Delay; use if_watch::IfEvent; @@ -38,7 +38,6 @@ use libp2p_core::{ Transport, }; use libp2p_identity::PeerId; -use rand::{distributions, Rng}; use std::collections::hash_map::{DefaultHasher, Entry}; use std::collections::{HashMap, VecDeque}; use std::fmt; @@ -77,18 +76,7 @@ pub struct GenTransport { /// Waker to poll the transport again when a new dialer or listener is added. waker: Option, /// Holepunching attempts - hole_punching: HashMap, -} - -#[derive(Debug, PartialEq, Eq, Hash)] -struct HolePunchKey { - addr: SocketAddr, - // peer_id: PeerId, -} - -#[derive(Debug)] -struct ActiveHolePunch { - sender: Option>, + hole_punch_map: HolePunchMap, } impl GenTransport

{ @@ -104,7 +92,7 @@ impl GenTransport

{ dialer: HashMap::new(), waker: None, support_draft_29, - hole_punching: HashMap::new(), + hole_punch_map: Default::default(), } } } @@ -112,7 +100,7 @@ impl GenTransport

{ impl Transport for GenTransport

{ type Output = (PeerId, Connection); type Error = Error; - type ListenerUpgrade = Connecting; + type ListenerUpgrade = BoxFuture<'static, Result>; type Dial = BoxFuture<'static, Result>; fn listen_on( @@ -127,6 +115,7 @@ impl Transport for GenTransport

{ listener_id, socket_addr, self.quinn_config.clone(), + self.hole_punch_map.clone(), self.handshake_timeout, version, )?; @@ -217,13 +206,17 @@ impl Transport for GenTransport

{ &mut self, addr: Multiaddr, ) -> Result> { - let (socket_addr, _version, _peer_id) = + let (socket_addr, _version, peer_id) = multiaddr_to_socketaddr(&addr, self.support_draft_29) .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { return Err(TransportError::MultiaddrNotSupported(addr)); } + let Some(peer_id) = peer_id else { + return Err(TransportError::MultiaddrNotSupported(addr)); + }; + let listeners = self .listeners .iter() @@ -255,20 +248,25 @@ impl Transport for GenTransport

{ } }; + let hole_puncher = HolePuncher::new(endpoint_channel, socket_addr, self.handshake_timeout); + let (sender, receiver) = oneshot::channel(); - self.hole_punching.insert( - HolePunchKey { addr: socket_addr }, - ActiveHolePunch { - sender: Some(sender), - }, - ); - Ok(HolePuncher::new( - endpoint_channel, - socket_addr, - self.handshake_timeout, - receiver, - ) - .boxed()) + + self.hole_punch_map + .lock() + .unwrap() + .insert((socket_addr, peer_id), sender); + + Ok(Box::pin(async { + futures::select! { + hole_punched = receiver.fuse() => { + Ok(hole_punched.unwrap()) + }, + err = hole_puncher.fuse() => { + Err(err) + } + } + })) } fn poll( @@ -289,36 +287,7 @@ impl Transport for GenTransport

{ } if let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { - match ev { - TransportEvent::Incoming { - listener_id, - upgrade, - local_addr, - send_back_addr, - } => { - let hole_punch_key = HolePunchKey { - addr: multiaddr_to_socketaddr(&send_back_addr, self.support_draft_29) - .unwrap() - .0, - }; - - if let Some(sender) = self - .hole_punching - .remove(&hole_punch_key) - .and_then(|mut h| h.sender.take()) - { - sender.send(upgrade).unwrap(); - } else { - return Poll::Ready(TransportEvent::Incoming { - listener_id, - upgrade, - local_addr, - send_back_addr, - }); - } - } - _ => return Poll::Ready(ev), - } + return Poll::Ready(ev); } self.waker = Some(cx.waker().clone()); @@ -326,88 +295,6 @@ impl Transport for GenTransport

{ } } -struct HolePuncher { - endpoint_channel: endpoint::Channel, - remote_addr: SocketAddr, - timeout: Delay, - interval_timeout: Delay, - receiver: Receiver, - connecting: Option, -} - -impl HolePuncher { - fn new( - endpoint_channel: endpoint::Channel, - remote_addr: SocketAddr, - timeout: Duration, - receiver: Receiver, - ) -> Self { - Self { - endpoint_channel, - remote_addr, - timeout: Delay::new(timeout), - interval_timeout: Delay::new(Duration::from_secs(0)), - receiver, - connecting: None, - } - } -} - -impl Future for HolePuncher { - type Output = Result<(PeerId, Connection), Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(connecting) = &mut self.connecting { - match connecting.poll_unpin(cx) { - Poll::Pending => {} - ready => return ready, - } - } - - match self.receiver.poll_unpin(cx) { - Poll::Ready(Ok(upgrade)) => { - self.connecting = Some(upgrade); - } - Poll::Ready(Err(_)) => {} - Poll::Pending => {} - } - - match self.timeout.poll_unpin(cx) { - Poll::Ready(_) => return Poll::Ready(Err(Error::HandshakeTimedOut)), - Poll::Pending => {} - } - - match self.interval_timeout.poll_unpin(cx) { - Poll::Ready(_) => { - let message = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { - destination: self.remote_addr, - ecn: None, - contents: rand::thread_rng() - .sample_iter(distributions::Standard) - .take(64) - .collect(), - segment_size: None, - src_ip: None, - }); - - match self.endpoint_channel.try_send(message, cx) { - Ok(_) => {} - Err(endpoint::Disconnected {}) => { - return Poll::Ready(Err(Error::EndpointDriverCrashed)) - } - } - - self.interval_timeout.reset(Duration::from_millis( - rand::thread_rng().gen_range(10..=200), - )); - } - Poll::Pending => {} - } - - Poll::Pending - } -} - impl From for TransportError { fn from(err: Error) -> Self { TransportError::Other(err) @@ -528,6 +415,9 @@ struct Listener { /// None if we are only listening on a single interface. if_watcher: Option, + /// Hole punching attempts + hole_punch_map: HolePunchMap, + /// Whether the listener was closed and the stream should terminate. is_closed: bool, @@ -543,6 +433,7 @@ impl Listener

{ listener_id: ListenerId, socket_addr: SocketAddr, config: QuinnConfig, + hole_punch_map: HolePunchMap, handshake_timeout: Duration, version: ProtocolVersion, ) -> Result { @@ -568,6 +459,7 @@ impl Listener

{ listener_id, version, new_connections_rx, + hole_punch_map, handshake_timeout, if_watcher, is_closed: false, @@ -652,7 +544,7 @@ impl Listener

{ } impl Stream for Listener

{ - type Item = TransportEvent; + type Item = TransportEvent< as Transport>::ListenerUpgrade, Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if let Some(event) = self.pending_event.take() { @@ -671,10 +563,18 @@ impl Stream for Listener

{ match self.new_connections_rx.poll_next_unpin(cx) { Poll::Ready(Some(connection)) => { let local_addr = socketaddr_to_multiaddr(connection.local_addr(), self.version); - let send_back_addr = - socketaddr_to_multiaddr(&connection.remote_addr(), self.version); + let remote_addr = connection.remote_addr(); + let send_back_addr = socketaddr_to_multiaddr(&remote_addr, self.version); + + let upgrade = MaybeHolePunchedConnection::new( + self.hole_punch_map.clone(), + remote_addr, + Connecting::new(connection, self.handshake_timeout), + ) + .boxed(); + let event = TransportEvent::Incoming { - upgrade: Connecting::new(connection, self.handshake_timeout), + upgrade, local_addr, send_back_addr, listener_id: self.listener_id, From 271f234a878c45e4e239434b51323fc75647b10d Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Fri, 26 May 2023 17:55:46 +0530 Subject: [PATCH 07/39] Update dcutr example --- examples/dcutr/src/main.rs | 62 ++++++++++++++------------------------ 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index b6e9186f772..ab0e735c922 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -28,7 +28,7 @@ use libp2p::{ core::{ multiaddr::{Multiaddr, Protocol}, muxing::StreamMuxerBox, - transport::{OrTransport, Transport}, + transport::Transport, upgrade, }, dcutr, @@ -40,7 +40,6 @@ use libp2p::{ use libp2p_quic as quic; use log::info; use std::error::Error; -use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Parser)] @@ -91,39 +90,25 @@ fn main() -> Result<(), Box> { let (relay_transport, client) = relay::client::new(local_peer_id); - let is_quic_relay = opts.relay_address.iter().any(|p| p == Protocol::QuicV1); - let transport = if is_quic_relay { - let relay_transport = relay_transport + let transport = { + let relay_tcp_quic_transport = relay_transport + .or_transport(tcp::async_io::Transport::new( + tcp::Config::default().port_reuse(true), + )) .upgrade(upgrade::Version::V1) .authenticate(noise::Config::new(&local_key).unwrap()) - .multiplex(yamux::Config::default()); + .multiplex(yamux::Config::default()) + .or_transport(quic::async_std::Transport::new(quic::Config::new( + &local_key, + ))); - OrTransport::new( - relay_transport, - block_on(DnsConfig::system(quic::async_std::Transport::new( - quic::Config::new(&local_key), - ))) - .unwrap(), - ) - .map(|either_output, _| match either_output { - Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), - }) - .boxed() - } else { - OrTransport::new( - relay_transport, - block_on(DnsConfig::system(tcp::async_io::Transport::new( - tcp::Config::default().port_reuse(true), - ))) - .unwrap(), - ) - .upgrade(upgrade::Version::V1Lazy) - .authenticate( - noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."), - ) - .multiplex(yamux::Config::default()) - .boxed() + block_on(DnsConfig::system(relay_tcp_quic_transport)) + .unwrap() + .map(|either_output, _| match either_output { + Either::Left((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + Either::Right((peer_id, muxer)) => (peer_id, StreamMuxerBox::new(muxer)), + }) + .boxed() }; #[derive(NetworkBehaviour)] @@ -184,13 +169,12 @@ fn main() -> Result<(), Box> { } .build(); - let listen_addr = Multiaddr::empty().with("0.0.0.0".parse::().unwrap().into()); - let listen_addr = if is_quic_relay { - listen_addr.with(Protocol::Udp(0)).with(Protocol::QuicV1) - } else { - listen_addr.with(Protocol::Tcp(0)) - }; - swarm.listen_on(listen_addr).unwrap(); + swarm + .listen_on("/ip4/0.0.0.0/udp/0/quic-v1".parse().unwrap()) + .unwrap(); + swarm + .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) + .unwrap(); // Wait to listen on all interfaces. block_on(async { From 8b9d778b270362e6bc429d4b08ea2e8a6a34c469 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Fri, 26 May 2023 22:24:37 +0530 Subject: [PATCH 08/39] Check if holepunching is already in progress --- transports/quic/src/lib.rs | 7 +++++++ transports/quic/src/transport.rs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 2d92ccb835e..a641567dbcc 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -63,8 +63,11 @@ mod hole_punching; mod provider; mod transport; +use std::net::SocketAddr; + pub use connection::{Connecting, Connection, Substream}; pub use endpoint::Config; +use libp2p_identity::PeerId; #[cfg(feature = "async-std")] pub use provider::async_std; #[cfg(feature = "tokio")] @@ -99,6 +102,10 @@ pub enum Error { /// Error when `Transport::dial_as_listener` is called without an active listener. #[error("Tried to dial as listener without an active listener.")] NoActiveListenerForDialAsListener, + + /// Error when holepunching for a remote is already in progress + #[error("Already punching hole for ({0:?}, {1:?}).")] + HolePunchInProgress(SocketAddr, PeerId), } /// Dialing a remote peer failed. diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index a820807cf01..f48b49e4a7c 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -252,10 +252,20 @@ impl Transport for GenTransport

{ let (sender, receiver) = oneshot::channel(); - self.hole_punch_map + match self + .hole_punch_map .lock() .unwrap() - .insert((socket_addr, peer_id), sender); + .entry((socket_addr, peer_id)) + { + Entry::Vacant(entry) => entry.insert(sender), + Entry::Occupied(_) => { + return Err(TransportError::Other(Error::HolePunchInProgress( + socket_addr, + peer_id, + ))); + } + }; Ok(Box::pin(async { futures::select! { From 22908114c2397b6b903f204bf3e259a6dc77113a Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Fri, 26 May 2023 22:58:25 +0530 Subject: [PATCH 09/39] Dedup dial and dial_as_listener --- transports/quic/src/transport.rs | 78 +++++++++++++++----------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index f48b49e4a7c..d3cbb0bb692 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -95,6 +95,35 @@ impl GenTransport

{ hole_punch_map: Default::default(), } } + + fn remote_multiaddr_to_socketaddr( + &self, + addr: Multiaddr, + ) -> Result< + (SocketAddr, ProtocolVersion, Option), + TransportError<::Error>, + > { + let (socket_addr, version, peer_id) = multiaddr_to_socketaddr(&addr, self.support_draft_29) + .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; + if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { + return Err(TransportError::MultiaddrNotSupported(addr)); + } + Ok((socket_addr, version, peer_id)) + } + + fn eligible_listeners(&mut self, socket_addr: &SocketAddr) -> Vec<&mut Listener

> { + self.listeners + .iter_mut() + .filter(|l| { + if l.is_closed { + return false; + } + let listen_addr = l.endpoint_channel.socket_addr(); + SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) + && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() + }) + .collect() + } } impl Transport for GenTransport

{ @@ -154,25 +183,11 @@ impl Transport for GenTransport

{ } fn dial(&mut self, addr: Multiaddr) -> Result> { - let (socket_addr, version, _peer_id) = - multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - return Err(TransportError::MultiaddrNotSupported(addr)); - } + let (socket_addr, version, _peer_id) = self.remote_multiaddr_to_socketaddr(addr)?; - let mut listeners = self - .listeners - .iter_mut() - .filter(|l| { - if l.is_closed { - return false; - } - let listen_addr = l.endpoint_channel.socket_addr(); - SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) - && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() - }) - .collect::>(); + let handshake_timeout = self.handshake_timeout; + + let mut listeners = self.eligible_listeners(&socket_addr); let dialer_state = match listeners.len() { 0 => { @@ -199,38 +214,19 @@ impl Transport for GenTransport

{ &mut listeners[index].dialer_state } }; - Ok(dialer_state.new_dial(socket_addr, self.handshake_timeout, version)) + Ok(dialer_state.new_dial(socket_addr, handshake_timeout, version)) } fn dial_as_listener( &mut self, addr: Multiaddr, ) -> Result> { - let (socket_addr, _version, peer_id) = - multiaddr_to_socketaddr(&addr, self.support_draft_29) - .ok_or_else(|| TransportError::MultiaddrNotSupported(addr.clone()))?; - if socket_addr.port() == 0 || socket_addr.ip().is_unspecified() { - return Err(TransportError::MultiaddrNotSupported(addr)); - } + let (socket_addr, _version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone())?; - let Some(peer_id) = peer_id else { - return Err(TransportError::MultiaddrNotSupported(addr)); - }; + let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr))?; - let listeners = self - .listeners - .iter() - .filter(|l| { - if l.is_closed { - return false; - } - let listen_addr = l.endpoint_channel.socket_addr(); - SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) - && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() - }) - .collect::>(); + let listeners = self.eligible_listeners(&socket_addr); - // which listener to use to send packets? go-libp2p uses routing table with random port let endpoint_channel = match listeners.len() { 0 => { return Err(TransportError::Other( From 2621c134e74d684b8018988b830df33cfbae70c2 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 30 May 2023 15:43:49 +0530 Subject: [PATCH 10/39] Store random packet on failure --- transports/quic/src/hole_punching.rs | 75 +++++++++++++++++----------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 8d400d70f72..8a1fe2efb8c 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -24,6 +24,9 @@ use crate::{ pub(crate) type HolePunchMap = Arc>>>; +/// An upgrading inbound QUIC connection that is either +/// - a normal inbound connection or +/// - an inbound connection corresponding to an in-progress outbound hole punching connection. pub(crate) struct MaybeHolePunchedConnection { hole_punch_map: HolePunchMap, addr: SocketAddr, @@ -69,7 +72,8 @@ pub(crate) struct HolePuncher { endpoint_channel: endpoint::Channel, remote_addr: SocketAddr, timeout: Delay, - interval_timeout: Delay, + interval: Delay, + message: Option, } impl HolePuncher { @@ -82,7 +86,8 @@ impl HolePuncher { endpoint_channel, remote_addr, timeout: Delay::new(timeout), - interval_timeout: Delay::new(Duration::from_secs(0)), + interval: Delay::new(Duration::from_secs(0)), + message: None, } } } @@ -92,35 +97,47 @@ impl Future for HolePuncher { type Output = Error; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.timeout.poll_unpin(cx) { - Poll::Ready(_) => return Poll::Ready(Error::HandshakeTimedOut), - Poll::Pending => {} - } - - futures::ready!(self.interval_timeout.poll_unpin(cx)); - - let message = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { - destination: self.remote_addr, - ecn: None, - contents: rand::thread_rng() - .sample_iter(distributions::Standard) - .take(64) - .collect(), - segment_size: None, - src_ip: None, - }); - - match self.endpoint_channel.try_send(message, cx) { - Ok(_) => {} - Err(endpoint::Disconnected {}) => { - return Poll::Ready(Error::EndpointDriverCrashed); + loop { + match self.timeout.poll_unpin(cx) { + Poll::Ready(_) => return Poll::Ready(Error::HandshakeTimedOut), + Poll::Pending => {} } - } - self.interval_timeout.reset(Duration::from_millis( - rand::thread_rng().gen_range(10..=200), - )); + let message = match self.message.take() { + Some(m) => m, + None => { + futures::ready!(self.interval.poll_unpin(cx)); + self.interval.reset(Duration::from_millis( + rand::thread_rng().gen_range(10..=200), + )); + ToEndpoint::SendUdpPacket(quinn_proto::Transmit { + destination: self.remote_addr, + ecn: None, + contents: rand::thread_rng() + .sample_iter(distributions::Standard) + .take(64) + .collect(), + segment_size: None, + src_ip: None, + }) + } + }; + + match self.endpoint_channel.try_send(message, cx) { + Ok(Ok(())) => { + // Message sent. Continue to register waker on `self.interval`. + continue; + } + Ok(Err(m)) => { + // Endpoint is busy. Try again later. + self.message = Some(m); + } + Err(endpoint::Disconnected {}) => { + return Poll::Ready(Error::EndpointDriverCrashed); + } + } - Poll::Pending + return Poll::Pending; + } } } From 3b17e389ae3c69c19c05e270b7b858d9278bd31d Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 30 May 2023 20:35:47 +0530 Subject: [PATCH 11/39] Use `Display` formatting instead of `Debug` --- transports/quic/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index a641567dbcc..c9178a5e18a 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -104,7 +104,7 @@ pub enum Error { NoActiveListenerForDialAsListener, /// Error when holepunching for a remote is already in progress - #[error("Already punching hole for ({0:?}, {1:?}).")] + #[error("Already punching hole for ({0}, {1}).")] HolePunchInProgress(SocketAddr, PeerId), } From 45446bb3ee9607136806c36d8b8f892319ca1161 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 30 May 2023 21:55:25 +0530 Subject: [PATCH 12/39] Use slice pattern --- transports/quic/src/transport.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index d3cbb0bb692..e05e5bbb37a 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -189,8 +189,8 @@ impl Transport for GenTransport

{ let mut listeners = self.eligible_listeners(&socket_addr); - let dialer_state = match listeners.len() { - 0 => { + let dialer_state = match listeners.as_mut_slice() { + [] => { // No listener. Get or create an explicit dialer. let socket_family = socket_addr.ip().into(); let dialer = match self.dialer.entry(socket_family) { @@ -204,8 +204,8 @@ impl Transport for GenTransport

{ }; &mut dialer.state } - 1 => &mut listeners[0].dialer_state, - _ => { + [listener] => &mut listener.dialer_state, + listeners => { // Pick any listener to use for dialing. // We hash the socket address to achieve determinism. let mut hasher = DefaultHasher::new(); @@ -227,14 +227,14 @@ impl Transport for GenTransport

{ let listeners = self.eligible_listeners(&socket_addr); - let endpoint_channel = match listeners.len() { - 0 => { + let endpoint_channel = match listeners.as_slice() { + [] => { return Err(TransportError::Other( Error::NoActiveListenerForDialAsListener, )); } - 1 => listeners[0].endpoint_channel.clone(), - _ => { + [listener] => listener.endpoint_channel.clone(), + listeners => { // Pick any listener to use for dialing. // We hash the socket address to achieve determinism. let mut hasher = DefaultHasher::new(); From 8a344c75137d27b3ea83cc43e9a843ead973dd13 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 1 Jun 2023 05:18:42 +0530 Subject: [PATCH 13/39] Resolve merge conflict --- protocols/dcutr/src/behaviour_impl.rs | 59 +++++++++++++++------------ 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/protocols/dcutr/src/behaviour_impl.rs b/protocols/dcutr/src/behaviour_impl.rs index c3a4c6df347..2a48637267a 100644 --- a/protocols/dcutr/src/behaviour_impl.rs +++ b/protocols/dcutr/src/behaviour_impl.rs @@ -242,28 +242,27 @@ impl NetworkBehaviour for Behaviour { fn handle_established_inbound_connection( &mut self, connection_id: ConnectionId, - peer: PeerId, + _peer: PeerId, local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { if is_relayed(local_addr) { - Ok(Either::Left(handler::relayed::Handler::new( + return Ok(Either::Left(handler::relayed::Handler::new( ConnectedPoint::Listener { local_addr: local_addr.clone(), send_back_addr: remote_addr.clone(), }, - ))) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } else if let Some(&relayed_connection_id) = - self.direct_to_relayed_connections.get(&connection_id) - { - self.outgoing_direct_connection_attempts - .remove(&(relayed_connection_id, peer)); - Ok(Either::Right(Either::Left( - handler::direct::Handler::default(), - ))) - } else { - Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) + ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. } + + assert!( + self.direct_to_relayed_connections + .get(&connection_id) + .is_none(), + "state mismatch" + ); + + Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) } fn handle_established_outbound_connection( @@ -274,23 +273,32 @@ impl NetworkBehaviour for Behaviour { role_override: Endpoint, ) -> Result, ConnectionDenied> { if is_relayed(addr) { - Ok(Either::Left(handler::relayed::Handler::new( + return Ok(Either::Left(handler::relayed::Handler::new( ConnectedPoint::Dialer { address: addr.clone(), role_override, }, - ))) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } else if let Some(&relayed_connection_id) = - self.direct_to_relayed_connections.get(&connection_id) + ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + } + + // Whether this is a connection requested by this behaviour. + if let Some(&relayed_connection_id) = self.direct_to_relayed_connections.get(&connection_id) { - self.outgoing_direct_connection_attempts - .remove(&(relayed_connection_id, peer)); - Ok(Either::Right(Either::Left( + if role_override == Endpoint::Listener { + assert!( + self.outgoing_direct_connection_attempts + .remove(&(relayed_connection_id, peer)) + .is_some(), + "state mismatch" + ); + } + + return Ok(Either::Right(Either::Left( handler::direct::Handler::default(), - ))) - } else { - Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) + ))); } + + Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) } fn on_connection_handler_event( @@ -423,8 +431,9 @@ impl NetworkBehaviour for Behaviour { | FromSwarm::ExpiredListenAddr(_) | FromSwarm::ListenerError(_) | FromSwarm::ListenerClosed(_) - | FromSwarm::NewExternalAddr(_) - | FromSwarm::ExpiredExternalAddr(_) => {} + | FromSwarm::NewExternalAddrCandidate(_) + | FromSwarm::ExternalAddrExpired(_) + | FromSwarm::ExternalAddrConfirmed(_) => {} } } } From 81988c2075b97615203ceee3cc9c180f35d91271 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 1 Jun 2023 05:23:48 +0530 Subject: [PATCH 14/39] Resolve merge conflict --- protocols/dcutr/src/behaviour_impl.rs | 93 +++++++++++++++------------ 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/protocols/dcutr/src/behaviour_impl.rs b/protocols/dcutr/src/behaviour_impl.rs index 2a48637267a..57692d38898 100644 --- a/protocols/dcutr/src/behaviour_impl.rs +++ b/protocols/dcutr/src/behaviour_impl.rs @@ -242,27 +242,37 @@ impl NetworkBehaviour for Behaviour { fn handle_established_inbound_connection( &mut self, connection_id: ConnectionId, - _peer: PeerId, + peer: PeerId, local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - if is_relayed(local_addr) { - return Ok(Either::Left(handler::relayed::Handler::new( - ConnectedPoint::Listener { - local_addr: local_addr.clone(), - send_back_addr: remote_addr.clone(), - }, - ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } - - assert!( - self.direct_to_relayed_connections - .get(&connection_id) - .is_none(), - "state mismatch" - ); + match self + .outgoing_direct_connection_attempts + .remove(&(connection_id, peer)) + { + None => { + let handler = if is_relayed(local_addr) { + Either::Left(handler::relayed::Handler::new(ConnectedPoint::Listener { + local_addr: local_addr.clone(), + send_back_addr: remote_addr.clone(), + })) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + } else { + Either::Right(Either::Right(dummy::ConnectionHandler)) + }; + + Ok(handler) + } + Some(_) => { + assert!( + !is_relayed(local_addr), + "`Prototype::DirectConnection` is never created for relayed connection." + ); - Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) + Ok(Either::Right(Either::Left( + handler::direct::Handler::default(), + ))) + } + } } fn handle_established_outbound_connection( @@ -272,33 +282,33 @@ impl NetworkBehaviour for Behaviour { addr: &Multiaddr, role_override: Endpoint, ) -> Result, ConnectionDenied> { - if is_relayed(addr) { - return Ok(Either::Left(handler::relayed::Handler::new( - ConnectedPoint::Dialer { - address: addr.clone(), - role_override, - }, - ))); // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. - } - - // Whether this is a connection requested by this behaviour. - if let Some(&relayed_connection_id) = self.direct_to_relayed_connections.get(&connection_id) + match self + .outgoing_direct_connection_attempts + .remove(&(connection_id, peer)) { - if role_override == Endpoint::Listener { + None => { + let handler = if is_relayed(addr) { + Either::Left(handler::relayed::Handler::new(ConnectedPoint::Dialer { + address: addr.clone(), + role_override, + })) // TODO: We could make two `handler::relayed::Handler` here, one inbound one outbound. + } else { + Either::Right(Either::Right(dummy::ConnectionHandler)) + }; + + Ok(handler) + } + Some(_) => { assert!( - self.outgoing_direct_connection_attempts - .remove(&(relayed_connection_id, peer)) - .is_some(), - "state mismatch" + !is_relayed(addr), + "`Prototype::DirectConnection` is never created for relayed connection." ); - } - return Ok(Either::Right(Either::Left( - handler::direct::Handler::default(), - ))); + Ok(Either::Right(Either::Left( + handler::direct::Handler::default(), + ))) + } } - - Ok(Either::Right(Either::Right(dummy::ConnectionHandler))) } fn on_connection_handler_event( @@ -431,9 +441,8 @@ impl NetworkBehaviour for Behaviour { | FromSwarm::ExpiredListenAddr(_) | FromSwarm::ListenerError(_) | FromSwarm::ListenerClosed(_) - | FromSwarm::NewExternalAddrCandidate(_) - | FromSwarm::ExternalAddrExpired(_) - | FromSwarm::ExternalAddrConfirmed(_) => {} + | FromSwarm::NewExternalAddr(_) + | FromSwarm::ExpiredExternalAddr(_) => {} } } } From 28ece0322960a9be1d5e4b4088e12c57884f2783 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 1 Jun 2023 16:59:14 +0530 Subject: [PATCH 15/39] Use futures::future::select instead of select! macro --- transports/quic/src/hole_punching.rs | 18 ++++-------------- transports/quic/src/transport.rs | 12 ++++-------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 8a1fe2efb8c..9086eebaaf9 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -7,11 +7,7 @@ use std::{ time::Duration, }; -use futures::{ - channel::oneshot, - future::{Fuse, FusedFuture}, - prelude::*, -}; +use futures::{channel::oneshot, prelude::*}; use futures_timer::Delay; use libp2p_identity::PeerId; use rand::{distributions, Rng}; @@ -30,7 +26,7 @@ pub(crate) type HolePunchMap = pub(crate) struct MaybeHolePunchedConnection { hole_punch_map: HolePunchMap, addr: SocketAddr, - upgrade: Fuse, + upgrade: Connecting, } impl MaybeHolePunchedConnection { @@ -38,7 +34,7 @@ impl MaybeHolePunchedConnection { Self { hole_punch_map, addr, - upgrade: upgrade.fuse(), + upgrade, } } } @@ -54,7 +50,7 @@ impl Future for MaybeHolePunchedConnection { if let Err(connection) = sender.send((peer_id, connection)) { Poll::Ready(Ok(connection)) } else { - Poll::Pending + Poll::Ready(Err(Error::HandshakeTimedOut)) } } else { Poll::Ready(Ok((peer_id, connection))) @@ -62,12 +58,6 @@ impl Future for MaybeHolePunchedConnection { } } -impl FusedFuture for MaybeHolePunchedConnection { - fn is_terminated(&self) -> bool { - self.upgrade.is_terminated() - } -} - pub(crate) struct HolePuncher { endpoint_channel: endpoint::Channel, remote_addr: SocketAddr, diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index e05e5bbb37a..e4989eee04b 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -25,7 +25,7 @@ use crate::{endpoint, Connecting, Connection, Error}; use futures::channel::mpsc; use futures::channel::oneshot; -use futures::future::BoxFuture; +use futures::future::{BoxFuture, Either}; use futures::ready; use futures::stream::StreamExt; use futures::{prelude::*, stream::SelectAll}; @@ -264,13 +264,9 @@ impl Transport for GenTransport

{ }; Ok(Box::pin(async { - futures::select! { - hole_punched = receiver.fuse() => { - Ok(hole_punched.unwrap()) - }, - err = hole_puncher.fuse() => { - Err(err) - } + match futures::future::select(receiver, hole_puncher).await { + Either::Left((connection, _)) => Ok(connection.unwrap()), + Either::Right((hole_punch_err, _)) => Err(hole_punch_err), } })) } From 794cabc1ee184a0b24fb8840a02b2726bfbdde0f Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 5 Jun 2023 13:29:51 +0530 Subject: [PATCH 16/39] Implement HolePuncher as an async function --- transports/quic/Cargo.toml | 2 +- transports/quic/src/endpoint.rs | 13 ++++ transports/quic/src/hole_punching.rs | 102 +++++++++------------------ transports/quic/src/transport.rs | 7 +- 4 files changed, 53 insertions(+), 71 deletions(-) diff --git a/transports/quic/Cargo.toml b/transports/quic/Cargo.toml index 79bd83f59f7..2005208c734 100644 --- a/transports/quic/Cargo.toml +++ b/transports/quic/Cargo.toml @@ -23,7 +23,7 @@ quinn-proto = { version = "0.10.1", default-features = false, features = ["tls-r rand = "0.8.5" rustls = { version = "0.21.1", default-features = false } thiserror = "1.0.40" -tokio = { version = "1.28.2", default-features = false, features = ["net", "rt"], optional = true } +tokio = { version = "1.28.2", default-features = false, features = ["net", "rt", "time"], optional = true } [features] tokio = ["dep:tokio", "if-watch/tokio"] diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index cef062a0d7e..8d01b5da2c1 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -279,6 +279,19 @@ impl Channel { Ok(Ok(())) } + pub(crate) async fn send(&mut self, to_endpoint: ToEndpoint) -> Result<(), Disconnected> { + futures::future::poll_fn(|cx| { + self.to_endpoint + .poll_ready_unpin(cx) + .map_err(|_| Disconnected {}) + }) + .await?; + + self.to_endpoint + .start_send(to_endpoint) + .map_err(|_| Disconnected {}) + } + /// Send a message to inform the [`Driver`] about an /// event caused by the owner of this [`Channel`] dropping. /// This clones the sender to the endpoint to guarantee delivery. diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 9086eebaaf9..ada3a44f47d 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -8,10 +8,14 @@ use std::{ }; use futures::{channel::oneshot, prelude::*}; -use futures_timer::Delay; use libp2p_identity::PeerId; use rand::{distributions, Rng}; +#[cfg(feature = "async-std")] +use async_std::{future::timeout, task::sleep}; +#[cfg(feature = "tokio")] +use tokio::time::{sleep, timeout}; + use crate::{ endpoint::{self, ToEndpoint}, Connecting, Connection, Error, @@ -58,76 +62,40 @@ impl Future for MaybeHolePunchedConnection { } } -pub(crate) struct HolePuncher { - endpoint_channel: endpoint::Channel, +async fn punch_holes( + mut endpoint_channel: endpoint::Channel, remote_addr: SocketAddr, - timeout: Delay, - interval: Delay, - message: Option, -} +) -> Error { + loop { + let sleep_duration = Duration::from_millis(rand::thread_rng().gen_range(10..=200)); + sleep(sleep_duration).await; -impl HolePuncher { - pub(crate) fn new( - endpoint_channel: endpoint::Channel, - remote_addr: SocketAddr, - timeout: Duration, - ) -> Self { - Self { - endpoint_channel, - remote_addr, - timeout: Delay::new(timeout), - interval: Delay::new(Duration::from_secs(0)), - message: None, + let random_udp_packet = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { + destination: remote_addr, + ecn: None, + contents: rand::thread_rng() + .sample_iter(distributions::Standard) + .take(64) + .collect(), + segment_size: None, + src_ip: None, + }); + + if endpoint_channel.send(random_udp_packet).await.is_err() { + return Error::EndpointDriverCrashed; } } } -/// Never finishes successfully, only with an Err (timeout) -impl Future for HolePuncher { - type Output = Error; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match self.timeout.poll_unpin(cx) { - Poll::Ready(_) => return Poll::Ready(Error::HandshakeTimedOut), - Poll::Pending => {} - } - - let message = match self.message.take() { - Some(m) => m, - None => { - futures::ready!(self.interval.poll_unpin(cx)); - self.interval.reset(Duration::from_millis( - rand::thread_rng().gen_range(10..=200), - )); - ToEndpoint::SendUdpPacket(quinn_proto::Transmit { - destination: self.remote_addr, - ecn: None, - contents: rand::thread_rng() - .sample_iter(distributions::Standard) - .take(64) - .collect(), - segment_size: None, - src_ip: None, - }) - } - }; - - match self.endpoint_channel.try_send(message, cx) { - Ok(Ok(())) => { - // Message sent. Continue to register waker on `self.interval`. - continue; - } - Ok(Err(m)) => { - // Endpoint is busy. Try again later. - self.message = Some(m); - } - Err(endpoint::Disconnected {}) => { - return Poll::Ready(Error::EndpointDriverCrashed); - } - } - - return Poll::Pending; - } - } +pub(crate) async fn hole_puncher( + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout_duration: Duration, +) -> Error { + timeout( + timeout_duration, + punch_holes(endpoint_channel, remote_addr), + ) + .await + .unwrap_or(Error::HandshakeTimedOut) } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index e4989eee04b..ad954f97d16 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; -use crate::hole_punching::{HolePunchMap, HolePuncher, MaybeHolePunchedConnection}; +use crate::hole_punching::{hole_puncher, HolePunchMap, MaybeHolePunchedConnection}; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; @@ -43,6 +43,7 @@ use std::collections::{HashMap, VecDeque}; use std::fmt; use std::hash::{Hash, Hasher}; use std::net::IpAddr; +use std::pin::pin; use std::time::Duration; use std::{ net::SocketAddr, @@ -244,7 +245,7 @@ impl Transport for GenTransport

{ } }; - let hole_puncher = HolePuncher::new(endpoint_channel, socket_addr, self.handshake_timeout); + let hole_puncher = hole_puncher(endpoint_channel, socket_addr, self.handshake_timeout); let (sender, receiver) = oneshot::channel(); @@ -264,7 +265,7 @@ impl Transport for GenTransport

{ }; Ok(Box::pin(async { - match futures::future::select(receiver, hole_puncher).await { + match futures::future::select(receiver, pin!(hole_puncher)).await { Either::Left((connection, _)) => Ok(connection.unwrap()), Either::Right((hole_punch_err, _)) => Err(hole_punch_err), } From e5011756b23817f748f5003cabd8c955b0a4e32e Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 5 Jun 2023 13:30:31 +0530 Subject: [PATCH 17/39] Remove entry from HolePunchMap on timeout --- transports/quic/src/transport.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index ad954f97d16..e9bd0acb1d7 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -249,12 +249,9 @@ impl Transport for GenTransport

{ let (sender, receiver) = oneshot::channel(); - match self - .hole_punch_map - .lock() - .unwrap() - .entry((socket_addr, peer_id)) - { + let hole_punch_map = self.hole_punch_map.clone(); + + match hole_punch_map.lock().unwrap().entry((socket_addr, peer_id)) { Entry::Vacant(entry) => entry.insert(sender), Entry::Occupied(_) => { return Err(TransportError::Other(Error::HolePunchInProgress( @@ -264,10 +261,16 @@ impl Transport for GenTransport

{ } }; - Ok(Box::pin(async { + Ok(Box::pin(async move { match futures::future::select(receiver, pin!(hole_puncher)).await { Either::Left((connection, _)) => Ok(connection.unwrap()), - Either::Right((hole_punch_err, _)) => Err(hole_punch_err), + Either::Right((hole_punch_err, _)) => { + hole_punch_map + .lock() + .unwrap() + .remove(&(socket_addr, peer_id)); + Err(hole_punch_err) + } } })) } From ad2b64420b16a9d2125514bd81894fba3e337989 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 5 Jun 2023 13:34:41 +0530 Subject: [PATCH 18/39] Run rustfmt --- transports/quic/src/hole_punching.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index ada3a44f47d..8400797d68b 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -62,10 +62,7 @@ impl Future for MaybeHolePunchedConnection { } } -async fn punch_holes( - mut endpoint_channel: endpoint::Channel, - remote_addr: SocketAddr, -) -> Error { +async fn punch_holes(mut endpoint_channel: endpoint::Channel, remote_addr: SocketAddr) -> Error { loop { let sleep_duration = Duration::from_millis(rand::thread_rng().gen_range(10..=200)); sleep(sleep_duration).await; @@ -92,10 +89,7 @@ pub(crate) async fn hole_puncher( remote_addr: SocketAddr, timeout_duration: Duration, ) -> Error { - timeout( - timeout_duration, - punch_holes(endpoint_channel, remote_addr), - ) - .await - .unwrap_or(Error::HandshakeTimedOut) + timeout(timeout_duration, punch_holes(endpoint_channel, remote_addr)) + .await + .unwrap_or(Error::HandshakeTimedOut) } From ba60bbabefd0b0dd0eba6ca004b209310279e7b4 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 5 Jun 2023 20:40:05 +0530 Subject: [PATCH 19/39] Implement MaybeHolePunchedConnection as an async function --- transports/quic/src/hole_punching.rs | 50 +++++++++++----------------- transports/quic/src/transport.rs | 8 ++--- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 8400797d68b..cfa2ef91d81 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -1,9 +1,7 @@ use std::{ collections::HashMap, net::SocketAddr, - pin::Pin, sync::{Arc, Mutex}, - task::{Context, Poll}, time::Duration, }; @@ -27,39 +25,29 @@ pub(crate) type HolePunchMap = /// An upgrading inbound QUIC connection that is either /// - a normal inbound connection or /// - an inbound connection corresponding to an in-progress outbound hole punching connection. -pub(crate) struct MaybeHolePunchedConnection { +pub(crate) async fn maybe_hole_punched_connection( hole_punch_map: HolePunchMap, - addr: SocketAddr, + remote_addr: SocketAddr, upgrade: Connecting, -} - -impl MaybeHolePunchedConnection { - pub(crate) fn new(hole_punch_map: HolePunchMap, addr: SocketAddr, upgrade: Connecting) -> Self { - Self { - hole_punch_map, - addr, - upgrade, - } - } -} - -impl Future for MaybeHolePunchedConnection { - type Output = Result<(PeerId, Connection), Error>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (peer_id, connection) = futures::ready!(self.upgrade.poll_unpin(cx))?; - let addr = self.addr; - let mut hole_punch_map = self.hole_punch_map.lock().unwrap(); - if let Some(sender) = hole_punch_map.remove(&(addr, peer_id)) { - if let Err(connection) = sender.send((peer_id, connection)) { - Poll::Ready(Ok(connection)) +) -> Result<(PeerId, Connection), Error> { + upgrade + .map(|res| { + let (peer_id, connection) = res?; + if let Some(sender) = hole_punch_map + .lock() + .unwrap() + .remove(&(remote_addr, peer_id)) + { + if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { + Ok((peer_id, connection)) + } else { + Err(Error::HandshakeTimedOut) + } } else { - Poll::Ready(Err(Error::HandshakeTimedOut)) + Ok((peer_id, connection)) } - } else { - Poll::Ready(Ok((peer_id, connection))) - } - } + }) + .await } async fn punch_holes(mut endpoint_channel: endpoint::Channel, remote_addr: SocketAddr) -> Error { diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index e9bd0acb1d7..db52785d538 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; -use crate::hole_punching::{hole_puncher, HolePunchMap, MaybeHolePunchedConnection}; +use crate::hole_punching::{hole_puncher, maybe_hole_punched_connection, HolePunchMap}; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; @@ -43,7 +43,6 @@ use std::collections::{HashMap, VecDeque}; use std::fmt; use std::hash::{Hash, Hasher}; use std::net::IpAddr; -use std::pin::pin; use std::time::Duration; use std::{ net::SocketAddr, @@ -262,7 +261,8 @@ impl Transport for GenTransport

{ }; Ok(Box::pin(async move { - match futures::future::select(receiver, pin!(hole_puncher)).await { + futures::pin_mut!(hole_puncher); + match futures::future::select(receiver, hole_puncher).await { Either::Left((connection, _)) => Ok(connection.unwrap()), Either::Right((hole_punch_err, _)) => { hole_punch_map @@ -572,7 +572,7 @@ impl Stream for Listener

{ let remote_addr = connection.remote_addr(); let send_back_addr = socketaddr_to_multiaddr(&remote_addr, self.version); - let upgrade = MaybeHolePunchedConnection::new( + let upgrade = maybe_hole_punched_connection( self.hole_punch_map.clone(), remote_addr, Connecting::new(connection, self.handshake_timeout), From 6c378ef60d8bb6ca935cff9debc09d5ee4881fe8 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 5 Jun 2023 20:40:31 +0530 Subject: [PATCH 20/39] Use `SendExt::send` instead of `poll_ready` and `start_send` --- transports/quic/src/endpoint.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/transports/quic/src/endpoint.rs b/transports/quic/src/endpoint.rs index 8d01b5da2c1..bf69df50b62 100644 --- a/transports/quic/src/endpoint.rs +++ b/transports/quic/src/endpoint.rs @@ -280,15 +280,9 @@ impl Channel { } pub(crate) async fn send(&mut self, to_endpoint: ToEndpoint) -> Result<(), Disconnected> { - futures::future::poll_fn(|cx| { - self.to_endpoint - .poll_ready_unpin(cx) - .map_err(|_| Disconnected {}) - }) - .await?; - self.to_endpoint - .start_send(to_endpoint) + .send(to_endpoint) + .await .map_err(|_| Disconnected {}) } From 349094069fba2f0a57ba8ec7efe507509b69759b Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 6 Jun 2023 16:27:59 +0530 Subject: [PATCH 21/39] Swap position of hole_puncher and punch_holes --- transports/quic/src/hole_punching.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index cfa2ef91d81..d20065f15c2 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -50,6 +50,16 @@ pub(crate) async fn maybe_hole_punched_connection( .await } +pub(crate) async fn hole_puncher( + endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, + timeout_duration: Duration, +) -> Error { + timeout(timeout_duration, punch_holes(endpoint_channel, remote_addr)) + .await + .unwrap_or(Error::HandshakeTimedOut) +} + async fn punch_holes(mut endpoint_channel: endpoint::Channel, remote_addr: SocketAddr) -> Error { loop { let sleep_duration = Duration::from_millis(rand::thread_rng().gen_range(10..=200)); @@ -71,13 +81,3 @@ async fn punch_holes(mut endpoint_channel: endpoint::Channel, remote_addr: Socke } } } - -pub(crate) async fn hole_puncher( - endpoint_channel: endpoint::Channel, - remote_addr: SocketAddr, - timeout_duration: Duration, -) -> Error { - timeout(timeout_duration, punch_holes(endpoint_channel, remote_addr)) - .await - .unwrap_or(Error::HandshakeTimedOut) -} From 26cd3280c1da5e7d05607109bc537dcf2ce98071 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 6 Jun 2023 16:32:12 +0530 Subject: [PATCH 22/39] Clean up maybe_hole_punched_connection --- transports/quic/src/hole_punching.rs | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index d20065f15c2..7dbe05c38b0 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -5,7 +5,7 @@ use std::{ time::Duration, }; -use futures::{channel::oneshot, prelude::*}; +use futures::channel::oneshot; use libp2p_identity::PeerId; use rand::{distributions, Rng}; @@ -30,24 +30,21 @@ pub(crate) async fn maybe_hole_punched_connection( remote_addr: SocketAddr, upgrade: Connecting, ) -> Result<(PeerId, Connection), Error> { - upgrade - .map(|res| { - let (peer_id, connection) = res?; - if let Some(sender) = hole_punch_map - .lock() - .unwrap() - .remove(&(remote_addr, peer_id)) - { - if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { - Ok((peer_id, connection)) - } else { - Err(Error::HandshakeTimedOut) - } - } else { - Ok((peer_id, connection)) - } - }) - .await + let (peer_id, connection) = upgrade.await?; + + if let Some(sender) = hole_punch_map + .lock() + .unwrap() + .remove(&(remote_addr, peer_id)) + { + if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { + Ok((peer_id, connection)) + } else { + Err(Error::HandshakeTimedOut) + } + } else { + Ok((peer_id, connection)) + } } pub(crate) async fn hole_puncher( From 0b1eaa5a5e540319c08cc14efc7a70e13194c07c Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 6 Jun 2023 16:36:26 +0530 Subject: [PATCH 23/39] Rename hole_punch_map to hole_punch_attempts --- transports/quic/src/hole_punching.rs | 4 ++-- transports/quic/src/transport.rs | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 7dbe05c38b0..c9987e177ed 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -26,13 +26,13 @@ pub(crate) type HolePunchMap = /// - a normal inbound connection or /// - an inbound connection corresponding to an in-progress outbound hole punching connection. pub(crate) async fn maybe_hole_punched_connection( - hole_punch_map: HolePunchMap, + hole_punch_attempts: HolePunchMap, remote_addr: SocketAddr, upgrade: Connecting, ) -> Result<(PeerId, Connection), Error> { let (peer_id, connection) = upgrade.await?; - if let Some(sender) = hole_punch_map + if let Some(sender) = hole_punch_attempts .lock() .unwrap() .remove(&(remote_addr, peer_id)) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index db52785d538..5506946649f 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -76,7 +76,7 @@ pub struct GenTransport { /// Waker to poll the transport again when a new dialer or listener is added. waker: Option, /// Holepunching attempts - hole_punch_map: HolePunchMap, + hole_punch_attempts: HolePunchMap, } impl GenTransport

{ @@ -92,7 +92,7 @@ impl GenTransport

{ dialer: HashMap::new(), waker: None, support_draft_29, - hole_punch_map: Default::default(), + hole_punch_attempts: Default::default(), } } @@ -144,7 +144,7 @@ impl Transport for GenTransport

{ listener_id, socket_addr, self.quinn_config.clone(), - self.hole_punch_map.clone(), + self.hole_punch_attempts.clone(), self.handshake_timeout, version, )?; @@ -248,9 +248,13 @@ impl Transport for GenTransport

{ let (sender, receiver) = oneshot::channel(); - let hole_punch_map = self.hole_punch_map.clone(); + let hole_punch_attempts = self.hole_punch_attempts.clone(); - match hole_punch_map.lock().unwrap().entry((socket_addr, peer_id)) { + match hole_punch_attempts + .lock() + .unwrap() + .entry((socket_addr, peer_id)) + { Entry::Vacant(entry) => entry.insert(sender), Entry::Occupied(_) => { return Err(TransportError::Other(Error::HolePunchInProgress( @@ -265,7 +269,7 @@ impl Transport for GenTransport

{ match futures::future::select(receiver, hole_puncher).await { Either::Left((connection, _)) => Ok(connection.unwrap()), Either::Right((hole_punch_err, _)) => { - hole_punch_map + hole_punch_attempts .lock() .unwrap() .remove(&(socket_addr, peer_id)); @@ -422,7 +426,7 @@ struct Listener { if_watcher: Option, /// Hole punching attempts - hole_punch_map: HolePunchMap, + hole_punch_attempts: HolePunchMap, /// Whether the listener was closed and the stream should terminate. is_closed: bool, @@ -439,7 +443,7 @@ impl Listener

{ listener_id: ListenerId, socket_addr: SocketAddr, config: QuinnConfig, - hole_punch_map: HolePunchMap, + hole_punch_attempts: HolePunchMap, handshake_timeout: Duration, version: ProtocolVersion, ) -> Result { @@ -465,7 +469,7 @@ impl Listener

{ listener_id, version, new_connections_rx, - hole_punch_map, + hole_punch_attempts, handshake_timeout, if_watcher, is_closed: false, @@ -573,7 +577,7 @@ impl Stream for Listener

{ let send_back_addr = socketaddr_to_multiaddr(&remote_addr, self.version); let upgrade = maybe_hole_punched_connection( - self.hole_punch_map.clone(), + self.hole_punch_attempts.clone(), remote_addr, Connecting::new(connection, self.handshake_timeout), ) From fbbd1a1edde1349b4845f5c3ec6c2b4ab0cc222e Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 6 Jun 2023 17:12:24 +0530 Subject: [PATCH 24/39] Create newtype HolePunchMap --- transports/quic/src/hole_punching.rs | 31 +++++++++++++++++++++------- transports/quic/src/transport.rs | 25 +++++++--------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index c9987e177ed..5a3994ab98e 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{hash_map::Entry, HashMap}, net::SocketAddr, sync::{Arc, Mutex}, time::Duration, @@ -19,8 +19,27 @@ use crate::{ Connecting, Connection, Error, }; -pub(crate) type HolePunchMap = - Arc>>>; +type HolePunchKey = (SocketAddr, PeerId); +type HolePunchValue = oneshot::Sender<(PeerId, Connection)>; + +#[derive(Clone, Debug, Default)] +pub(crate) struct HolePunchMap(Arc>>); + +impl HolePunchMap { + pub(crate) fn remove(&self, key: &HolePunchKey) -> Option { + self.0.lock().unwrap().remove(key) + } + + pub(crate) fn try_insert(&self, key: HolePunchKey, value: HolePunchValue) -> bool { + match self.0.lock().unwrap().entry(key) { + Entry::Vacant(entry) => { + entry.insert(value); + true + } + Entry::Occupied(_) => false, + } + } +} /// An upgrading inbound QUIC connection that is either /// - a normal inbound connection or @@ -32,11 +51,7 @@ pub(crate) async fn maybe_hole_punched_connection( ) -> Result<(PeerId, Connection), Error> { let (peer_id, connection) = upgrade.await?; - if let Some(sender) = hole_punch_attempts - .lock() - .unwrap() - .remove(&(remote_addr, peer_id)) - { + if let Some(sender) = hole_punch_attempts.remove(&(remote_addr, peer_id)) { if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { Ok((peer_id, connection)) } else { diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 5506946649f..1879473f25a 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -222,7 +222,6 @@ impl Transport for GenTransport

{ addr: Multiaddr, ) -> Result> { let (socket_addr, _version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone())?; - let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr))?; let listeners = self.eligible_listeners(&socket_addr); @@ -250,29 +249,19 @@ impl Transport for GenTransport

{ let hole_punch_attempts = self.hole_punch_attempts.clone(); - match hole_punch_attempts - .lock() - .unwrap() - .entry((socket_addr, peer_id)) - { - Entry::Vacant(entry) => entry.insert(sender), - Entry::Occupied(_) => { - return Err(TransportError::Other(Error::HolePunchInProgress( - socket_addr, - peer_id, - ))); - } - }; + if !hole_punch_attempts.try_insert((socket_addr, peer_id), sender) { + return Err(TransportError::Other(Error::HolePunchInProgress( + socket_addr, + peer_id, + ))); + } Ok(Box::pin(async move { futures::pin_mut!(hole_puncher); match futures::future::select(receiver, hole_puncher).await { Either::Left((connection, _)) => Ok(connection.unwrap()), Either::Right((hole_punch_err, _)) => { - hole_punch_attempts - .lock() - .unwrap() - .remove(&(socket_addr, peer_id)); + hole_punch_attempts.remove(&(socket_addr, peer_id)); Err(hole_punch_err) } } From 2ad9255b14e64746f7f9602dec55a8bfd091910b Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Tue, 6 Jun 2023 17:14:51 +0530 Subject: [PATCH 25/39] Don't mix private/public imports --- transports/quic/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index c9178a5e18a..69c03402086 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -64,10 +64,10 @@ mod provider; mod transport; use std::net::SocketAddr; +use libp2p_identity::PeerId; pub use connection::{Connecting, Connection, Substream}; pub use endpoint::Config; -use libp2p_identity::PeerId; #[cfg(feature = "async-std")] pub use provider::async_std; #[cfg(feature = "tokio")] From bbbf7a11d78335b06ca7d4f4565e1e52405e3ca3 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Wed, 7 Jun 2023 15:11:05 +0530 Subject: [PATCH 26/39] Return single eligible listener --- transports/quic/src/lib.rs | 2 +- transports/quic/src/transport.rs | 51 ++++++++++++++------------------ 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 69c03402086..9b93cb8cca0 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -63,8 +63,8 @@ mod hole_punching; mod provider; mod transport; -use std::net::SocketAddr; use libp2p_identity::PeerId; +use std::net::SocketAddr; pub use connection::{Connecting, Connection, Substream}; pub use endpoint::Config; diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 1879473f25a..692f3af5854 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -111,8 +111,9 @@ impl GenTransport

{ Ok((socket_addr, version, peer_id)) } - fn eligible_listeners(&mut self, socket_addr: &SocketAddr) -> Vec<&mut Listener

> { - self.listeners + fn eligible_listener(&mut self, socket_addr: &SocketAddr) -> Option<&mut Listener

> { + let mut listeners: Vec<_> = self + .listeners .iter_mut() .filter(|l| { if l.is_closed { @@ -122,7 +123,19 @@ impl GenTransport

{ SocketFamily::is_same(&listen_addr.ip(), &socket_addr.ip()) && listen_addr.ip().is_loopback() == socket_addr.ip().is_loopback() }) - .collect() + .collect(); + match listeners.len() { + 0 => None, + 1 => listeners.pop(), + _ => { + // Pick any listener to use for dialing. + // We hash the socket address to achieve determinism. + let mut hasher = DefaultHasher::new(); + socket_addr.hash(&mut hasher); + let index = hasher.finish() as usize % listeners.len(); + Some(listeners.swap_remove(index)) + } + } } } @@ -187,10 +200,8 @@ impl Transport for GenTransport

{ let handshake_timeout = self.handshake_timeout; - let mut listeners = self.eligible_listeners(&socket_addr); - - let dialer_state = match listeners.as_mut_slice() { - [] => { + let dialer_state = match self.eligible_listener(&socket_addr) { + None => { // No listener. Get or create an explicit dialer. let socket_family = socket_addr.ip().into(); let dialer = match self.dialer.entry(socket_family) { @@ -204,15 +215,7 @@ impl Transport for GenTransport

{ }; &mut dialer.state } - [listener] => &mut listener.dialer_state, - listeners => { - // Pick any listener to use for dialing. - // We hash the socket address to achieve determinism. - let mut hasher = DefaultHasher::new(); - socket_addr.hash(&mut hasher); - let index = hasher.finish() as usize % listeners.len(); - &mut listeners[index].dialer_state - } + Some(listener) => &mut listener.dialer_state, }; Ok(dialer_state.new_dial(socket_addr, handshake_timeout, version)) } @@ -224,23 +227,13 @@ impl Transport for GenTransport

{ let (socket_addr, _version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone())?; let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr))?; - let listeners = self.eligible_listeners(&socket_addr); - - let endpoint_channel = match listeners.as_slice() { - [] => { + let endpoint_channel = match self.eligible_listener(&socket_addr) { + None => { return Err(TransportError::Other( Error::NoActiveListenerForDialAsListener, )); } - [listener] => listener.endpoint_channel.clone(), - listeners => { - // Pick any listener to use for dialing. - // We hash the socket address to achieve determinism. - let mut hasher = DefaultHasher::new(); - socket_addr.hash(&mut hasher); - let index = hasher.finish() as usize % listeners.len(); - listeners[index].endpoint_channel.clone() - } + Some(listener) => listener.endpoint_channel.clone(), }; let hole_puncher = hole_puncher(endpoint_channel, socket_addr, self.handshake_timeout); From 26da28d1486b619dc025240c7598b73ab7c14642 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Wed, 7 Jun 2023 16:00:15 +0530 Subject: [PATCH 27/39] Return different error on successful holepunch --- transports/quic/src/hole_punching.rs | 2 +- transports/quic/src/lib.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 5a3994ab98e..87e358be95d 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -55,7 +55,7 @@ pub(crate) async fn maybe_hole_punched_connection( if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { Ok((peer_id, connection)) } else { - Err(Error::HandshakeTimedOut) + Err(Error::SuccessfulHolePunchRedirectingConnToDialer) } } else { Ok((peer_id, connection)) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 9b93cb8cca0..0c9b64745a1 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -106,6 +106,10 @@ pub enum Error { /// Error when holepunching for a remote is already in progress #[error("Already punching hole for ({0}, {1}).")] HolePunchInProgress(SocketAddr, PeerId), + + /// Error when an incoming connection is passed to the `diaL_as_listener` dialer + #[error("Incoming connection redirected to holepunching dialer")] + SuccessfulHolePunchRedirectingConnToDialer } /// Dialing a remote peer failed. From 8eda5a76899a3355b8164e56ac6e9d110b7085ee Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 8 Jun 2023 20:25:18 +0530 Subject: [PATCH 28/39] Update external address --- examples/dcutr/src/main.rs | 1 + examples/relay-server/src/main.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/examples/dcutr/src/main.rs b/examples/dcutr/src/main.rs index 346bfde11c1..5ba7b4e01c3 100644 --- a/examples/dcutr/src/main.rs +++ b/examples/dcutr/src/main.rs @@ -221,6 +221,7 @@ fn main() -> Result<(), Box> { .. })) => { info!("Relay told us our public address: {:?}", observed_addr); + swarm.add_external_address(observed_addr); learned_observed_addr = true; } event => panic!("{event:?}"), diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index bc80d546596..1e1d7efbdaf 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -101,6 +101,12 @@ fn main() -> Result<(), Box> { block_on(async { loop { match swarm.next().await.expect("Infinite Stream.") { + SwarmEvent::Behaviour(BehaviourEvent::Identify(identify::Event::Received { + info: identify::Info { observed_addr, .. }, + .. + })) => { + swarm.add_external_address(observed_addr); + } SwarmEvent::Behaviour(event) => { println!("{event:?}") } From 5f58955dcf819f4b96fb4531d4038c8836b01e54 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 8 Jun 2023 21:51:10 +0530 Subject: [PATCH 29/39] Resolve issue after merge --- transports/quic/src/lib.rs | 2 +- transports/quic/src/transport.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index 0c9b64745a1..dec0599b45f 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -109,7 +109,7 @@ pub enum Error { /// Error when an incoming connection is passed to the `diaL_as_listener` dialer #[error("Incoming connection redirected to holepunching dialer")] - SuccessfulHolePunchRedirectingConnToDialer + SuccessfulHolePunchRedirectingConnToDialer, } /// Dialing a remote peer failed. diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 692f3af5854..d154918b4c5 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -673,7 +673,7 @@ fn multiaddr_to_socketaddr( for proto in iter { match proto { Protocol::P2p(id) => { - peer_id = Some(PeerId::from_multihash(id).ok()?); + peer_id = Some(id); } _ => return None, } From 928314f1c88e6130045e9847b962a03cb07b5ca1 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sat, 10 Jun 2023 17:39:24 +0530 Subject: [PATCH 30/39] Remove peer_id from hole punching key --- transports/quic/src/hole_punching.rs | 54 +--------------- transports/quic/src/lib.rs | 9 +-- transports/quic/src/transport.rs | 94 ++++++++++++++++++---------- 3 files changed, 64 insertions(+), 93 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 87e358be95d..4d809010486 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -1,12 +1,5 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - net::SocketAddr, - sync::{Arc, Mutex}, - time::Duration, -}; +use std::{net::SocketAddr, time::Duration}; -use futures::channel::oneshot; -use libp2p_identity::PeerId; use rand::{distributions, Rng}; #[cfg(feature = "async-std")] @@ -16,52 +9,9 @@ use tokio::time::{sleep, timeout}; use crate::{ endpoint::{self, ToEndpoint}, - Connecting, Connection, Error, + Error, }; -type HolePunchKey = (SocketAddr, PeerId); -type HolePunchValue = oneshot::Sender<(PeerId, Connection)>; - -#[derive(Clone, Debug, Default)] -pub(crate) struct HolePunchMap(Arc>>); - -impl HolePunchMap { - pub(crate) fn remove(&self, key: &HolePunchKey) -> Option { - self.0.lock().unwrap().remove(key) - } - - pub(crate) fn try_insert(&self, key: HolePunchKey, value: HolePunchValue) -> bool { - match self.0.lock().unwrap().entry(key) { - Entry::Vacant(entry) => { - entry.insert(value); - true - } - Entry::Occupied(_) => false, - } - } -} - -/// An upgrading inbound QUIC connection that is either -/// - a normal inbound connection or -/// - an inbound connection corresponding to an in-progress outbound hole punching connection. -pub(crate) async fn maybe_hole_punched_connection( - hole_punch_attempts: HolePunchMap, - remote_addr: SocketAddr, - upgrade: Connecting, -) -> Result<(PeerId, Connection), Error> { - let (peer_id, connection) = upgrade.await?; - - if let Some(sender) = hole_punch_attempts.remove(&(remote_addr, peer_id)) { - if let Err((peer_id, connection)) = sender.send((peer_id, connection)) { - Ok((peer_id, connection)) - } else { - Err(Error::SuccessfulHolePunchRedirectingConnToDialer) - } - } else { - Ok((peer_id, connection)) - } -} - pub(crate) async fn hole_puncher( endpoint_channel: endpoint::Channel, remote_addr: SocketAddr, diff --git a/transports/quic/src/lib.rs b/transports/quic/src/lib.rs index dec0599b45f..945f5119c6e 100644 --- a/transports/quic/src/lib.rs +++ b/transports/quic/src/lib.rs @@ -63,7 +63,6 @@ mod hole_punching; mod provider; mod transport; -use libp2p_identity::PeerId; use std::net::SocketAddr; pub use connection::{Connecting, Connection, Substream}; @@ -104,12 +103,8 @@ pub enum Error { NoActiveListenerForDialAsListener, /// Error when holepunching for a remote is already in progress - #[error("Already punching hole for ({0}, {1}).")] - HolePunchInProgress(SocketAddr, PeerId), - - /// Error when an incoming connection is passed to the `diaL_as_listener` dialer - #[error("Incoming connection redirected to holepunching dialer")] - SuccessfulHolePunchRedirectingConnToDialer, + #[error("Already punching hole for {0}).")] + HolePunchInProgress(SocketAddr), } /// Dialing a remote peer failed. diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index d154918b4c5..b92729429a9 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::endpoint::{Config, QuinnConfig, ToEndpoint}; -use crate::hole_punching::{hole_puncher, maybe_hole_punched_connection, HolePunchMap}; +use crate::hole_punching::hole_puncher; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; @@ -76,7 +76,7 @@ pub struct GenTransport { /// Waker to poll the transport again when a new dialer or listener is added. waker: Option, /// Holepunching attempts - hole_punch_attempts: HolePunchMap, + hole_punch_attempts: HashMap>, } impl GenTransport

{ @@ -142,7 +142,7 @@ impl GenTransport

{ impl Transport for GenTransport

{ type Output = (PeerId, Connection); type Error = Error; - type ListenerUpgrade = BoxFuture<'static, Result>; + type ListenerUpgrade = Connecting; type Dial = BoxFuture<'static, Result>; fn listen_on( @@ -157,7 +157,6 @@ impl Transport for GenTransport

{ listener_id, socket_addr, self.quinn_config.clone(), - self.hole_punch_attempts.clone(), self.handshake_timeout, version, )?; @@ -240,23 +239,32 @@ impl Transport for GenTransport

{ let (sender, receiver) = oneshot::channel(); - let hole_punch_attempts = self.hole_punch_attempts.clone(); - - if !hole_punch_attempts.try_insert((socket_addr, peer_id), sender) { - return Err(TransportError::Other(Error::HolePunchInProgress( - socket_addr, - peer_id, - ))); - } + match self.hole_punch_attempts.entry(socket_addr) { + Entry::Occupied(mut sender_entry) => { + if sender_entry.get().is_canceled() { + sender_entry.insert(sender); + } else { + return Err(TransportError::Other(Error::HolePunchInProgress( + socket_addr, + ))); + } + } + Entry::Vacant(entry) => { + entry.insert(sender); + } + }; Ok(Box::pin(async move { futures::pin_mut!(hole_puncher); match futures::future::select(receiver, hole_puncher).await { - Either::Left((connection, _)) => Ok(connection.unwrap()), - Either::Right((hole_punch_err, _)) => { - hole_punch_attempts.remove(&(socket_addr, peer_id)); - Err(hole_punch_err) + Either::Left((message, _)) => { + let (inbound_peer_id, connection) = message.unwrap().await?; + if inbound_peer_id != peer_id { + log::warn!("inbound connection from ({socket_addr}, {inbound_peer_id} incorrectly matched with outbound hole punch for ({socket_addr}, {peer_id})."); + } + Ok((inbound_peer_id, connection)) } + Either::Right((hole_punch_err, _)) => Err(hole_punch_err), } })) } @@ -279,7 +287,38 @@ impl Transport for GenTransport

{ } if let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { - return Poll::Ready(ev); + match ev { + TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + } => { + let socket_addr = + multiaddr_to_socketaddr(&send_back_addr, self.support_draft_29) + .unwrap() + .0; + + if let Some(sender) = self.hole_punch_attempts.remove(&socket_addr) { + if let Err(upgrade) = sender.send(upgrade) { + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); + } + } else { + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); + } + } + _ => return Poll::Ready(ev), + } } self.waker = Some(cx.waker().clone()); @@ -407,9 +446,6 @@ struct Listener { /// None if we are only listening on a single interface. if_watcher: Option, - /// Hole punching attempts - hole_punch_attempts: HolePunchMap, - /// Whether the listener was closed and the stream should terminate. is_closed: bool, @@ -425,7 +461,6 @@ impl Listener

{ listener_id: ListenerId, socket_addr: SocketAddr, config: QuinnConfig, - hole_punch_attempts: HolePunchMap, handshake_timeout: Duration, version: ProtocolVersion, ) -> Result { @@ -451,7 +486,6 @@ impl Listener

{ listener_id, version, new_connections_rx, - hole_punch_attempts, handshake_timeout, if_watcher, is_closed: false, @@ -536,7 +570,7 @@ impl Listener

{ } impl Stream for Listener

{ - type Item = TransportEvent< as Transport>::ListenerUpgrade, Error>; + type Item = TransportEvent; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if let Some(event) = self.pending_event.take() { @@ -555,18 +589,10 @@ impl Stream for Listener

{ match self.new_connections_rx.poll_next_unpin(cx) { Poll::Ready(Some(connection)) => { let local_addr = socketaddr_to_multiaddr(connection.local_addr(), self.version); - let remote_addr = connection.remote_addr(); - let send_back_addr = socketaddr_to_multiaddr(&remote_addr, self.version); - - let upgrade = maybe_hole_punched_connection( - self.hole_punch_attempts.clone(), - remote_addr, - Connecting::new(connection, self.handshake_timeout), - ) - .boxed(); - + let send_back_addr = + socketaddr_to_multiaddr(&connection.remote_addr(), self.version); let event = TransportEvent::Incoming { - upgrade, + upgrade: Connecting::new(connection, self.handshake_timeout), local_addr, send_back_addr, listener_id: self.listener_id, From 4f4f5282232f48e97714650361287444d4b04e75 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sat, 10 Jun 2023 17:44:36 +0530 Subject: [PATCH 31/39] Remove redudant match arm from relay example --- examples/relay-server/src/main.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/relay-server/src/main.rs b/examples/relay-server/src/main.rs index 7d21761d3d9..6a1d956b5a5 100644 --- a/examples/relay-server/src/main.rs +++ b/examples/relay-server/src/main.rs @@ -101,12 +101,6 @@ fn main() -> Result<(), Box> { block_on(async { loop { match swarm.next().await.expect("Infinite Stream.") { - SwarmEvent::Behaviour(BehaviourEvent::Identify(identify::Event::Received { - info: identify::Info { observed_addr, .. }, - .. - })) => { - swarm.add_external_address(observed_addr); - } SwarmEvent::Behaviour(event) => { if let BehaviourEvent::Identify(identify::Event::Received { info: identify::Info { observed_addr, .. }, From 3f6bb590679021fd9dd84938cf2ca9f81bfa95f7 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sat, 10 Jun 2023 17:52:38 +0530 Subject: [PATCH 32/39] Update CHANGELOG --- transports/quic/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transports/quic/CHANGELOG.md b/transports/quic/CHANGELOG.md index db50b7b8a31..7002a9c2f73 100644 --- a/transports/quic/CHANGELOG.md +++ b/transports/quic/CHANGELOG.md @@ -3,7 +3,10 @@ - Raise MSRV to 1.65. See [PR 3715]. +- Add hole punching support by implementing `Transport::dial_as_listener`. See [PR 3964]. + [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 +[PR 3964]: https://github.com/libp2p/rust-libp2p/pull/3964 ## 0.7.0-alpha.3 From 02b12682abc497cd2fdff904cb55cd29147eb139 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sat, 10 Jun 2023 18:06:33 +0530 Subject: [PATCH 33/39] Fix imports --- transports/quic/src/transport.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index b92729429a9..e5d50a2af8f 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -23,8 +23,7 @@ use crate::hole_punching::hole_puncher; use crate::provider::Provider; use crate::{endpoint, Connecting, Connection, Error}; -use futures::channel::mpsc; -use futures::channel::oneshot; +use futures::channel::{mpsc, oneshot}; use futures::future::{BoxFuture, Either}; use futures::ready; use futures::stream::StreamExt; From 35de8500b880adf2b7326141aa546e7c3470ca20 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sat, 10 Jun 2023 21:08:36 +0530 Subject: [PATCH 34/39] Review fixes --- transports/quic/src/transport.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index e5d50a2af8f..54723a2a693 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -225,14 +225,13 @@ impl Transport for GenTransport

{ let (socket_addr, _version, peer_id) = self.remote_multiaddr_to_socketaddr(addr.clone())?; let peer_id = peer_id.ok_or(TransportError::MultiaddrNotSupported(addr))?; - let endpoint_channel = match self.eligible_listener(&socket_addr) { - None => { - return Err(TransportError::Other( - Error::NoActiveListenerForDialAsListener, - )); - } - Some(listener) => listener.endpoint_channel.clone(), - }; + let endpoint_channel = self + .eligible_listener(&socket_addr) + .ok_or(TransportError::Other( + Error::NoActiveListenerForDialAsListener, + ))? + .endpoint_channel + .clone(); let hole_puncher = hole_puncher(endpoint_channel, socket_addr, self.handshake_timeout); @@ -240,13 +239,12 @@ impl Transport for GenTransport

{ match self.hole_punch_attempts.entry(socket_addr) { Entry::Occupied(mut sender_entry) => { - if sender_entry.get().is_canceled() { - sender_entry.insert(sender); - } else { + if !sender_entry.get().is_canceled() { return Err(TransportError::Other(Error::HolePunchInProgress( socket_addr, ))); } + sender_entry.insert(sender); } Entry::Vacant(entry) => { entry.insert(sender); @@ -259,7 +257,7 @@ impl Transport for GenTransport

{ Either::Left((message, _)) => { let (inbound_peer_id, connection) = message.unwrap().await?; if inbound_peer_id != peer_id { - log::warn!("inbound connection from ({socket_addr}, {inbound_peer_id} incorrectly matched with outbound hole punch for ({socket_addr}, {peer_id})."); + log::warn!("inbound connection from ({socket_addr}, {inbound_peer_id}) incorrectly matched with outbound hole punch for ({socket_addr}, {peer_id})."); } Ok((inbound_peer_id, connection)) } From e46c170f2d449e1c9a11d0c8341c9bc1a20ddf34 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sun, 11 Jun 2023 16:16:00 +0530 Subject: [PATCH 35/39] More review fixes --- transports/quic/src/transport.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index 54723a2a693..bf112958bf0 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -239,6 +239,8 @@ impl Transport for GenTransport

{ match self.hole_punch_attempts.entry(socket_addr) { Entry::Occupied(mut sender_entry) => { + // Stale senders, i.e. from failed hole punches are not removed. + // Thus, we can just overwrite a stale sender. if !sender_entry.get().is_canceled() { return Err(TransportError::Other(Error::HolePunchInProgress( socket_addr, @@ -255,9 +257,11 @@ impl Transport for GenTransport

{ futures::pin_mut!(hole_puncher); match futures::future::select(receiver, hole_puncher).await { Either::Left((message, _)) => { - let (inbound_peer_id, connection) = message.unwrap().await?; + let (inbound_peer_id, connection) = message + .expect("hole punch connection sender is never dropped before receiver") + .await?; if inbound_peer_id != peer_id { - log::warn!("inbound connection from ({socket_addr}, {inbound_peer_id}) incorrectly matched with outbound hole punch for ({socket_addr}, {peer_id})."); + log::warn!("expected inbound connection from {socket_addr} to resolve to {peer_id} but got {inbound_peer_id}"); } Ok((inbound_peer_id, connection)) } From 36655ee7befadfbfff7b0767e048bbea0e1c5d50 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Sun, 11 Jun 2023 22:52:38 +0530 Subject: [PATCH 36/39] Move timeout and sleep to Provider trait --- transports/quic/src/hole_punching.rs | 25 ++++++++++++----------- transports/quic/src/provider.rs | 15 +++++++++++++- transports/quic/src/provider/async_std.rs | 16 +++++++++++++++ transports/quic/src/provider/tokio.rs | 18 +++++++++++++++- transports/quic/src/transport.rs | 2 +- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index 4d809010486..aff54b4b5b3 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -2,30 +2,31 @@ use std::{net::SocketAddr, time::Duration}; use rand::{distributions, Rng}; -#[cfg(feature = "async-std")] -use async_std::{future::timeout, task::sleep}; -#[cfg(feature = "tokio")] -use tokio::time::{sleep, timeout}; - use crate::{ endpoint::{self, ToEndpoint}, - Error, + Error, Provider, }; -pub(crate) async fn hole_puncher( +pub(crate) async fn hole_puncher( endpoint_channel: endpoint::Channel, remote_addr: SocketAddr, timeout_duration: Duration, ) -> Error { - timeout(timeout_duration, punch_holes(endpoint_channel, remote_addr)) - .await - .unwrap_or(Error::HandshakeTimedOut) + P::timeout( + timeout_duration, + punch_holes::

(endpoint_channel, remote_addr), + ) + .await + .unwrap_or(Error::HandshakeTimedOut) } -async fn punch_holes(mut endpoint_channel: endpoint::Channel, remote_addr: SocketAddr) -> Error { +async fn punch_holes( + mut endpoint_channel: endpoint::Channel, + remote_addr: SocketAddr, +) -> Error { loop { let sleep_duration = Duration::from_millis(rand::thread_rng().gen_range(10..=200)); - sleep(sleep_duration).await; + P::sleep(sleep_duration).await; let random_udp_packet = ToEndpoint::SendUdpPacket(quinn_proto::Transmit { destination: remote_addr, diff --git a/transports/quic/src/provider.rs b/transports/quic/src/provider.rs index c38f77fd1b9..d78e5a45651 100644 --- a/transports/quic/src/provider.rs +++ b/transports/quic/src/provider.rs @@ -18,12 +18,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::Future; +use futures::{future::BoxFuture, Future}; use if_watch::IfEvent; use std::{ io, net::SocketAddr, task::{Context, Poll}, + time::Duration, }; #[cfg(feature = "async-std")] @@ -39,6 +40,7 @@ const RECEIVE_BUFFER_SIZE: usize = 65536; /// and spawning tasks. pub trait Provider: Unpin + Send + Sized + 'static { type IfWatcher: Unpin + Send; + type TimeoutError; /// Create a new providing that is wrapping the socket. /// @@ -74,4 +76,15 @@ pub trait Provider: Unpin + Send + Sized + 'static { watcher: &mut Self::IfWatcher, cx: &mut Context<'_>, ) -> Poll>; + + /// Awaits a future or times out after a duration of time. + fn timeout( + duration: Duration, + future: F, + ) -> BoxFuture<'static, Result> + where + F: Future + Send + 'static; + + /// Sleep for specified amount of time. + fn sleep(duration: Duration) -> BoxFuture<'static, ()>; } diff --git a/transports/quic/src/provider/async_std.rs b/transports/quic/src/provider/async_std.rs index 222c8e55e90..0dbffc1140f 100644 --- a/transports/quic/src/provider/async_std.rs +++ b/transports/quic/src/provider/async_std.rs @@ -26,6 +26,7 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, + time::Duration, }; use crate::GenTransport; @@ -46,6 +47,7 @@ pub struct Provider { impl super::Provider for Provider { type IfWatcher = if_watch::smol::IfWatcher; + type TimeoutError = async_std::future::TimeoutError; fn from_socket(socket: std::net::UdpSocket) -> io::Result { let socket = Arc::new(socket.into()); @@ -104,6 +106,20 @@ impl super::Provider for Provider { ) -> Poll> { watcher.poll_if_event(cx) } + + fn timeout( + duration: Duration, + future: F, + ) -> BoxFuture<'static, Result> + where + F: Future + Send + 'static, + { + async_std::future::timeout(duration, future).boxed() + } + + fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + async_std::task::sleep(duration).boxed() + } } type ReceiveStreamItem = ( diff --git a/transports/quic/src/provider/tokio.rs b/transports/quic/src/provider/tokio.rs index 07e23f8813c..f348b8bb25b 100644 --- a/transports/quic/src/provider/tokio.rs +++ b/transports/quic/src/provider/tokio.rs @@ -18,11 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use futures::{ready, Future}; +use futures::{future::BoxFuture, ready, Future, FutureExt}; use std::{ io, net::SocketAddr, task::{Context, Poll}, + time::Duration, }; use tokio::{io::ReadBuf, net::UdpSocket}; @@ -41,6 +42,7 @@ pub struct Provider { impl super::Provider for Provider { type IfWatcher = if_watch::tokio::IfWatcher; + type TimeoutError = tokio::time::error::Elapsed; fn from_socket(socket: std::net::UdpSocket) -> std::io::Result { let socket = UdpSocket::from_std(socket)?; @@ -95,4 +97,18 @@ impl super::Provider for Provider { ) -> Poll> { watcher.poll_if_event(cx) } + + fn timeout( + duration: Duration, + future: F, + ) -> BoxFuture<'static, Result> + where + F: Future + Send + 'static, + { + tokio::time::timeout(duration, future).boxed() + } + + fn sleep(duration: Duration) -> BoxFuture<'static, ()> { + tokio::time::sleep(duration).boxed() + } } diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index bf112958bf0..fb4fc3edcb5 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -233,7 +233,7 @@ impl Transport for GenTransport

{ .endpoint_channel .clone(); - let hole_puncher = hole_puncher(endpoint_channel, socket_addr, self.handshake_timeout); + let hole_puncher = hole_puncher::

(endpoint_channel, socket_addr, self.handshake_timeout); let (sender, receiver) = oneshot::channel(); From 49873f0702094cc02bc4f15f91744930963cfe82 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 12 Jun 2023 00:37:34 +0530 Subject: [PATCH 37/39] Implement timeout using sleep --- transports/quic/src/hole_punching.rs | 13 +++++++------ transports/quic/src/provider.rs | 8 -------- transports/quic/src/provider/async_std.rs | 10 ---------- transports/quic/src/provider/tokio.rs | 10 ---------- 4 files changed, 7 insertions(+), 34 deletions(-) diff --git a/transports/quic/src/hole_punching.rs b/transports/quic/src/hole_punching.rs index aff54b4b5b3..b9589dd17a0 100644 --- a/transports/quic/src/hole_punching.rs +++ b/transports/quic/src/hole_punching.rs @@ -1,5 +1,6 @@ use std::{net::SocketAddr, time::Duration}; +use futures::future::Either; use rand::{distributions, Rng}; use crate::{ @@ -12,12 +13,12 @@ pub(crate) async fn hole_puncher( remote_addr: SocketAddr, timeout_duration: Duration, ) -> Error { - P::timeout( - timeout_duration, - punch_holes::

(endpoint_channel, remote_addr), - ) - .await - .unwrap_or(Error::HandshakeTimedOut) + let punch_holes_future = punch_holes::

(endpoint_channel, remote_addr); + futures::pin_mut!(punch_holes_future); + match futures::future::select(P::sleep(timeout_duration), punch_holes_future).await { + Either::Left(_) => Error::HandshakeTimedOut, + Either::Right((hole_punch_err, _)) => hole_punch_err, + } } async fn punch_holes( diff --git a/transports/quic/src/provider.rs b/transports/quic/src/provider.rs index d78e5a45651..7334e563ca1 100644 --- a/transports/quic/src/provider.rs +++ b/transports/quic/src/provider.rs @@ -77,14 +77,6 @@ pub trait Provider: Unpin + Send + Sized + 'static { cx: &mut Context<'_>, ) -> Poll>; - /// Awaits a future or times out after a duration of time. - fn timeout( - duration: Duration, - future: F, - ) -> BoxFuture<'static, Result> - where - F: Future + Send + 'static; - /// Sleep for specified amount of time. fn sleep(duration: Duration) -> BoxFuture<'static, ()>; } diff --git a/transports/quic/src/provider/async_std.rs b/transports/quic/src/provider/async_std.rs index 0dbffc1140f..fcaa8d25d74 100644 --- a/transports/quic/src/provider/async_std.rs +++ b/transports/quic/src/provider/async_std.rs @@ -107,16 +107,6 @@ impl super::Provider for Provider { watcher.poll_if_event(cx) } - fn timeout( - duration: Duration, - future: F, - ) -> BoxFuture<'static, Result> - where - F: Future + Send + 'static, - { - async_std::future::timeout(duration, future).boxed() - } - fn sleep(duration: Duration) -> BoxFuture<'static, ()> { async_std::task::sleep(duration).boxed() } diff --git a/transports/quic/src/provider/tokio.rs b/transports/quic/src/provider/tokio.rs index f348b8bb25b..b80ead03d05 100644 --- a/transports/quic/src/provider/tokio.rs +++ b/transports/quic/src/provider/tokio.rs @@ -98,16 +98,6 @@ impl super::Provider for Provider { watcher.poll_if_event(cx) } - fn timeout( - duration: Duration, - future: F, - ) -> BoxFuture<'static, Result> - where - F: Future + Send + 'static, - { - tokio::time::timeout(duration, future).boxed() - } - fn sleep(duration: Duration) -> BoxFuture<'static, ()> { tokio::time::sleep(duration).boxed() } From 0eb92466c28b2a7f72b58b0db52829fe6707970a Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 12 Jun 2023 00:55:29 +0530 Subject: [PATCH 38/39] Loop through listener events --- transports/quic/src/transport.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/transports/quic/src/transport.rs b/transports/quic/src/transport.rs index fb4fc3edcb5..afdaf86cdf4 100644 --- a/transports/quic/src/transport.rs +++ b/transports/quic/src/transport.rs @@ -287,11 +287,11 @@ impl Transport for GenTransport

{ self.dialer.remove(&key); } - if let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { + while let Poll::Ready(Some(ev)) = self.listeners.poll_next_unpin(cx) { match ev { TransportEvent::Incoming { listener_id, - upgrade, + mut upgrade, local_addr, send_back_addr, } => { @@ -301,22 +301,20 @@ impl Transport for GenTransport

{ .0; if let Some(sender) = self.hole_punch_attempts.remove(&socket_addr) { - if let Err(upgrade) = sender.send(upgrade) { - return Poll::Ready(TransportEvent::Incoming { - listener_id, - upgrade, - local_addr, - send_back_addr, - }); + match sender.send(upgrade) { + Ok(()) => continue, + Err(timed_out_holepunch) => { + upgrade = timed_out_holepunch; + } } - } else { - return Poll::Ready(TransportEvent::Incoming { - listener_id, - upgrade, - local_addr, - send_back_addr, - }); } + + return Poll::Ready(TransportEvent::Incoming { + listener_id, + upgrade, + local_addr, + send_back_addr, + }); } _ => return Poll::Ready(ev), } From 05b7a9f280a6917199eb168f409288aeedfd57a8 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 12 Jun 2023 00:58:02 +0530 Subject: [PATCH 39/39] Remove unused TimeoutError --- transports/quic/src/provider.rs | 1 - transports/quic/src/provider/async_std.rs | 1 - transports/quic/src/provider/tokio.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/transports/quic/src/provider.rs b/transports/quic/src/provider.rs index 7334e563ca1..c9401e9b99f 100644 --- a/transports/quic/src/provider.rs +++ b/transports/quic/src/provider.rs @@ -40,7 +40,6 @@ const RECEIVE_BUFFER_SIZE: usize = 65536; /// and spawning tasks. pub trait Provider: Unpin + Send + Sized + 'static { type IfWatcher: Unpin + Send; - type TimeoutError; /// Create a new providing that is wrapping the socket. /// diff --git a/transports/quic/src/provider/async_std.rs b/transports/quic/src/provider/async_std.rs index fcaa8d25d74..e593b2ed4f4 100644 --- a/transports/quic/src/provider/async_std.rs +++ b/transports/quic/src/provider/async_std.rs @@ -47,7 +47,6 @@ pub struct Provider { impl super::Provider for Provider { type IfWatcher = if_watch::smol::IfWatcher; - type TimeoutError = async_std::future::TimeoutError; fn from_socket(socket: std::net::UdpSocket) -> io::Result { let socket = Arc::new(socket.into()); diff --git a/transports/quic/src/provider/tokio.rs b/transports/quic/src/provider/tokio.rs index b80ead03d05..77c9060e3c1 100644 --- a/transports/quic/src/provider/tokio.rs +++ b/transports/quic/src/provider/tokio.rs @@ -42,7 +42,6 @@ pub struct Provider { impl super::Provider for Provider { type IfWatcher = if_watch::tokio::IfWatcher; - type TimeoutError = tokio::time::error::Elapsed; fn from_socket(socket: std::net::UdpSocket) -> std::io::Result { let socket = UdpSocket::from_std(socket)?;