diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/transport/libp2p.rs | 160 |
1 files changed, 156 insertions, 4 deletions
diff --git a/src/transport/libp2p.rs b/src/transport/libp2p.rs index 9f83295..67ee6b5 100644 --- a/src/transport/libp2p.rs +++ b/src/transport/libp2p.rs @@ -22,8 +22,8 @@ //! connect directly (libp2p's DCUtR, hole punching); the relay carries the traffic until //! that works, or for good if it doesn't, within its limits (`RELAY_CIRCUIT_DURATION`, //! `RELAY_CIRCUIT_BYTES`; a circuit that ends is made again on the next send). Hole -//! punching needs both sides to listen; where only one does, the other dials it -//! directly once it knows where (`upgrade`). +//! punching needs both sides to listen (`HolePunching`: at their LAN addresses too); +//! where only one does, the other dials it directly once it knows where (`upgrade`). //! //! Of several connections to one peer (both dialled at once, or AutoNAT dialled back), //! all but one are closed a few seconds later (`DUPLICATE_GRACE`, `Book::duplicates`), @@ -925,7 +925,7 @@ struct Behaviour { relay_client: ::libp2p::relay::client::Behaviour, /// Only with `Libp2pConfig::relay`. relay: ::libp2p::swarm::behaviour::toggle::Toggle<::libp2p::relay::Behaviour>, - dcutr: ::libp2p::dcutr::Behaviour, + dcutr: HolePunching, /// Each connection's round trip (`Book::rtts`): of several to one peer, the fastest /// is kept. ping: ::libp2p::ping::Behaviour, @@ -938,6 +938,145 @@ struct Behaviour { >, } +/// libp2p's hole punching (DCUtR), offering our listen addresses too. DCUtR offers only +/// where others saw us (external address candidates, from identify): seen by a relay +/// out on the internet, two nodes on one LAN are both at their router's public address, +/// and punching through it needs the router to hairpin, which many don't. Their LAN +/// addresses work, the firewalls on the way being stateful. Only DCUtR is told: +/// AutoNAT would test every candidate, and fail on all of these. +struct HolePunching { + inner: ::libp2p::dcutr::Behaviour, + /// Where we listen (`offered`). + listening: Vec<Multiaddr>, +} + +/// Whether DCUtR offers our listen address `address` too: neither loopback nor +/// link-local (as for publishing), nor through a relay. +fn offered(address: &Multiaddr) -> bool { + !is_local(address) && !is_relayed(address) +} + +impl HolePunching { + fn new(local: PeerId) -> Self { + Self { + inner: ::libp2p::dcutr::Behaviour::new(local), + listening: Vec::new(), + } + } + + /// Tells DCUtR where we listen, as if others had seen us there. Again after every + /// other candidate, as DCUtR keeps the latest 20. + fn offer_listening(&mut self) { + use ::libp2p::swarm::{FromSwarm, NetworkBehaviour, NewExternalAddrCandidate}; + for addr in &self.listening { + self.inner + .on_swarm_event(FromSwarm::NewExternalAddrCandidate( + NewExternalAddrCandidate { addr }, + )); + } + } +} + +impl ::libp2p::swarm::NetworkBehaviour for HolePunching { + type ConnectionHandler = + <::libp2p::dcutr::Behaviour as ::libp2p::swarm::NetworkBehaviour>::ConnectionHandler; + type ToSwarm = ::libp2p::dcutr::Event; + + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), ::libp2p::swarm::ConnectionDenied> { + self.inner + .handle_pending_inbound_connection(connection_id, local_addr, remote_addr) + } + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<::libp2p::swarm::THandler<Self>, ::libp2p::swarm::ConnectionDenied> { + self.inner.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) + } + + fn handle_pending_outbound_connection( + &mut self, + connection_id: ConnectionId, + maybe_peer: Option<PeerId>, + addresses: &[Multiaddr], + effective_role: ::libp2p::core::Endpoint, + ) -> Result<Vec<Multiaddr>, ::libp2p::swarm::ConnectionDenied> { + self.inner.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + ) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: ::libp2p::core::Endpoint, + port_use: ::libp2p::core::transport::PortUse, + ) -> Result<::libp2p::swarm::THandler<Self>, ::libp2p::swarm::ConnectionDenied> { + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) + } + + fn on_swarm_event(&mut self, event: ::libp2p::swarm::FromSwarm) { + use ::libp2p::swarm::FromSwarm; + self.inner.on_swarm_event(event); + match event { + FromSwarm::NewListenAddr(new) if offered(new.addr) => { + if !self.listening.contains(new.addr) { + self.listening.push(new.addr.clone()); + } + self.offer_listening(); + } + FromSwarm::ExpiredListenAddr(expired) => { + self.listening.retain(|address| address != expired.addr); + } + FromSwarm::NewExternalAddrCandidate(_) => self.offer_listening(), + _ => {} + } + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: ConnectionId, + event: ::libp2p::swarm::THandlerOutEvent<Self>, + ) { + self.inner + .on_connection_handler_event(peer_id, connection_id, event) + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll< + ::libp2p::swarm::ToSwarm<Self::ToSwarm, ::libp2p::swarm::THandlerInEvent<Self>>, + > { + self.inner.poll(cx) + } +} + /// What the behaviour has besides what every node has. #[derive(Clone, Copy)] struct Features { @@ -1021,7 +1160,7 @@ impl Behaviour { identify: ::libp2p::identify::Behaviour::new(identify), relay_client, relay: relay.into(), - dcutr: ::libp2p::dcutr::Behaviour::new(peer_id), + dcutr: HolePunching::new(peer_id), ping: ::libp2p::ping::Behaviour::new(::libp2p::ping::Config::new()), autonat: ::libp2p::autonat::Behaviour::new( peer_id, @@ -2633,6 +2772,19 @@ mod tests { } } + #[test] + fn listen_addresses_offered_for_hole_punching() { + let parse = |text: &str| text.parse::<Multiaddr>().unwrap(); + assert!(offered(&parse("/ip4/192.168.1.2/udp/9661/quic-v1"))); + assert!(offered(&parse("/ip6/fd00::2/tcp/9661"))); + assert!(!offered(&parse("/ip4/127.0.0.1/udp/9661/quic-v1"))); + assert!(!offered(&parse("/ip6/fe80::1/tcp/9661"))); + let relay = PeerId::random(); + assert!(!offered(&parse(&format!( + "/ip4/192.168.1.2/tcp/9661/p2p/{relay}/p2p-circuit" + )))); + } + /// libp2p's nested dial errors come out as each address's cause, once. #[test] fn dial_errors_are_short() { |
