diff --git a/anchor/client/src/cli.rs b/anchor/client/src/cli.rs index 992a6f513..6f3e8f9d8 100644 --- a/anchor/client/src/cli.rs +++ b/anchor/client/src/cli.rs @@ -235,6 +235,14 @@ pub struct Node { )] pub use_zero_ports: bool, + #[clap( + long, + help = "Disables UPnP support. Setting this will prevent Anchor \ + from attempting to automatically establish external port mappings.", + default_value = "false" + )] + pub disable_upnp: bool, + #[clap( long, help = "Specify the target number of connected peers", diff --git a/anchor/client/src/config.rs b/anchor/client/src/config.rs index 775dc68e2..918a7755c 100644 --- a/anchor/client/src/config.rs +++ b/anchor/client/src/config.rs @@ -183,9 +183,13 @@ pub fn from_cli(cli_args: &Node, global_config: GlobalConfig) -> Result, pub handshake: handshake::Behaviour, } @@ -157,6 +163,12 @@ impl AnchorBehaviour { let handshake = handshake::create_behaviour(local_keypair); + let upnp = Toggle::from( + network_config + .upnp_enabled + .then(libp2p::upnp::tokio::Behaviour::default), + ); + Ok(AnchorBehaviour { identify, ping: ping::Behaviour::default(), @@ -164,6 +176,7 @@ impl AnchorBehaviour { discovery, peer_manager, handshake, + upnp, }) } } diff --git a/anchor/network/src/config.rs b/anchor/network/src/config.rs index 3fc2ce31b..428349570 100644 --- a/anchor/network/src/config.rs +++ b/anchor/network/src/config.rs @@ -77,6 +77,9 @@ pub struct Config { /// Target number of connected peers. pub target_peers: usize, + /// Attempt to construct external port mappings with UPnP. + pub upnp_enabled: bool, + pub domain_type: DomainType, } @@ -109,6 +112,7 @@ impl Config { disable_quic_support: false, subscribe_all_subnets: false, domain_type: DomainType::default(), + upnp_enabled: true, } } } diff --git a/anchor/network/src/network.rs b/anchor/network/src/network.rs index f7583830d..a0bf7d07c 100644 --- a/anchor/network/src/network.rs +++ b/anchor/network/src/network.rs @@ -18,6 +18,7 @@ use libp2p::{ identity::Keypair, multiaddr::Protocol, swarm::{SwarmEvent, dial_opts::DialOpts}, + upnp::Event, }; use message_receiver::{MessageReceiver, Outcome}; use prometheus_client::registry::Registry; @@ -235,6 +236,9 @@ impl Network { self.handle_handshake_result(result); } } + AnchorBehaviourEvent::Upnp(upnp_event) => { + self.on_upnp_event(upnp_event); + } AnchorBehaviourEvent::PeerManager(peer_manager::Event::Heartbeat(heartbeat)) => { if let Some(actions) = heartbeat.connect_actions { self.handle_connect_actions(actions); @@ -542,6 +546,48 @@ impl Network { } } + fn on_upnp_event(&mut self, event: Event) { + match event { + libp2p::upnp::Event::NewExternalAddr(addr) => { + info!(%addr, "UPnP route established"); + let mut iter = addr.iter(); + let is_ipv6 = { + let addr = iter.next(); + matches!(addr, Some(Protocol::Ip6(_))) + }; + match iter.next() { + Some(Protocol::Udp(udp_port)) => match iter.next() { + Some(Protocol::QuicV1) => { + if let Err(e) = + self.discovery().try_update_port(false, is_ipv6, udp_port) + { + warn!(error = e, "Failed to update ENR"); + } + } + _ => { + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); + } + }, + Some(Protocol::Tcp(tcp_port)) => { + if let Err(e) = self.discovery().try_update_port(true, is_ipv6, tcp_port) { + warn!(error = e, "Failed to update ENR"); + } + } + _ => { + trace!(%addr, "UPnP address mapped multiaddr from unknown transport"); + } + } + } + libp2p::upnp::Event::ExpiredExternalAddr(addr) => { + info!(%addr, "UPnP route expired"); + } + libp2p::upnp::Event::GatewayNotFound => info!("UPnP not available."), + libp2p::upnp::Event::NonRoutableGateway => { + info!("UPnP is available but gateway is not exposed to public network") + } + } + } + fn peer_manager(&mut self) -> &mut PeerManager { &mut self.swarm.behaviour_mut().peer_manager }