feat(quic): implement hole punching

Implement `Transport::dial_as_listener` for QUIC as specified by the [DCUtR spec](https://github.com/libp2p/specs/blob/master/relay/DCUtR.md).

To facilitate hole punching in QUIC, one side needs to send random UDP packets to establish a mapping in the routing table of the NAT device. If successful, our listener will emit a new inbound connection. This connection needs to then be sent to the dialing task. We achieve this by storing a `HashMap` of hole punch attempts indexed by the remote's `SocketAddr`. A matching incoming connection is then sent via a oneshot channel to the dialing task which continues with upgrading the connection.

Related #2883.

Pull-Request: #3964.
This commit is contained in:
Arpan Kapoor
2023-06-13 17:12:18 +05:30
committed by GitHub
parent 2a6311f473
commit cf3e4c6860
14 changed files with 293 additions and 78 deletions

View File

@@ -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"

View File

@@ -23,13 +23,14 @@
use clap::Parser;
use futures::{
executor::{block_on, ThreadPool},
future::FutureExt,
future::{Either, FutureExt},
stream::StreamExt,
};
use libp2p::{
core::{
multiaddr::{Multiaddr, Protocol},
transport::{OrTransport, Transport},
muxing::StreamMuxerBox,
transport::Transport,
upgrade,
},
dcutr,
@@ -38,9 +39,9 @@ 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;
use std::str::FromStr;
#[derive(Debug, Parser)]
@@ -91,19 +92,26 @@ fn main() -> Result<(), Box<dyn Error>> {
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 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())
.or_transport(quic::async_std::Transport::new(quic::Config::new(
&local_key,
)));
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)]
#[behaviour(to_swarm = "Event")]
@@ -164,11 +172,10 @@ fn main() -> Result<(), Box<dyn Error>> {
.build();
swarm
.listen_on(
Multiaddr::empty()
.with("0.0.0.0".parse::<Ipv4Addr>().unwrap().into())
.with(Protocol::Tcp(0)),
)
.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.

View File

@@ -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"] }

View File

@@ -22,10 +22,11 @@
#![doc = include_str!("../README.md")]
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,
@@ -34,6 +35,7 @@ use libp2p::{
swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent},
tcp,
};
use libp2p_quic as quic;
use std::error::Error;
use std::net::{Ipv4Addr, Ipv6Addr};
@@ -50,12 +52,21 @@ fn main() -> Result<(), Box<dyn Error>> {
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 {
@@ -70,13 +81,22 @@ fn main() -> Result<(), Box<dyn Error>> {
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 {