### Create a new probe based on a previous output. Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html This function initializes a new Probe by potentially starting probing tasks for UPnP, PCP, and NAT-PMP services if they were not available in the previous output and are enabled in the configuration. It uses tokio::select! to concurrently wait for the probing tasks to complete. ```rust async fn from_output( config: Config, output: ProbeOutput, local_ip: Ipv4Addr, gateway: Ipv4Addr, metrics: Arc, ) -> Probe { let ProbeOutput { upnp, pcp, nat_pmp } = output; let Config { enable_upnp, enable_pcp, enable_nat_pmp, protocol: _, } = config; let mut upnp_probing_task = util::MaybeFuture { inner: (enable_upnp && !upnp).then(|| { let metrics = metrics.clone(); Box::pin(async move { upnp::probe_available(&metrics) .await .map(|addr| (addr, Instant::now())) }) }), }; let mut pcp_probing_task = util::MaybeFuture { inner: (enable_pcp && !pcp).then(|| { let metrics = metrics.clone(); Box::pin(async move { metrics.pcp_probes.inc(); pcp::probe_available(local_ip, gateway) .await .then(Instant::now) }) }), }; let mut nat_pmp_probing_task = util::MaybeFuture { inner: (enable_nat_pmp && !nat_pmp).then(|| { Box::pin(async { nat_pmp::probe_available(local_ip, gateway) .await .then(Instant::now) }) }), }; if upnp_probing_task.inner.is_some() { metrics.upnp_probes.inc(); } let mut upnp_done = upnp_probing_task.inner.is_none(); let mut pcp_done = pcp_probing_task.inner.is_none(); let mut nat_pmp_done = nat_pmp_probing_task.inner.is_none(); let mut probe = Probe::empty(); while !upnp_done || !pcp_done || !nat_pmp_done { tokio::select! { last_upnp_gateway_addr = &mut upnp_probing_task, if !upnp_done => { trace!("tick: upnp probe ready"); probe.last_upnp_gateway_addr = last_upnp_gateway_addr; upnp_done = true; }, last_nat_pmp = &mut nat_pmp_probing_task, if !nat_pmp_done => { trace!("tick: nat_pmp probe ready"); probe.last_nat_pmp = last_nat_pmp; nat_pmp_done = true; }, last_pcp = &mut pcp_probing_task, if !pcp_done => { trace!("tick: pcp probe ready"); probe.last_pcp = last_pcp; pcp_done = true; }, } } probe } ``` -------------------------------- ### Initiate Port Mapping Request Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Initiates a request to get a port mapping. It checks for local port availability, retrieves IP and gateway information, and uses probe results to determine the best mapping strategy. ```rust fn get_mapping(&mut self, external_addr: Option<(Ipv4Addr, NonZeroU16)>) { if let Some(local_port) = self.local_port { self.metrics.mapping_attempts.inc(); let (local_ip, gateway) = match ip_and_gateway() { Ok(ip_and_gw) => ip_and_gw, Err(e) => return debug!("can't get mapping: {e}"), }; let ProbeOutput { upnp, pcp, nat_pmp } = self.full_probe.output(); debug!("getting a port mapping for {local_ip}:{local_port} -> {external_addr:?}"); let recently_probed = self.full_probe.last_probe + UNAVAILABILITY_TRUST_DURATION > Instant::now(); let protocol = self.config.protocol; // strategy: } } ``` -------------------------------- ### Get Local IP and Gateway Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html A Rust function to retrieve the local IP address and gateway address, essential for network configuration and port mapping. ```rust 771/// Gets the local ip and gateway address for port mapping. 772fn ip_and_gateway() -> Result<(Ipv4Addr, Ipv4Addr), ProbeError> { 773 let Some(HomeRouter { gateway, my_ip }) = HomeRouter::new() else { 774 return Err(e!(ProbeError::NoGateway)); 775 }; 776 777 let local_ip = match my_ip { 778 Some(std::net::IpAddr::V4(ip)) 779 if !ip.is_unspecified() && !ip.is_loopback() && !ip.is_multicast() => 780 { 781 ip 782 } 783 other => { 784 debug!("no address suitable for port mapping found ({other:?}), using localhost"); 785 Ipv4Addr::LOCALHOST 786 } 787 }; 788 789 let std::net::IpAddr::V4(gateway) = gateway else { 790 return Err(e!(ProbeError::Ipv6Gateway)); 791 }; 792 793 Ok((local_ip, gateway)) 794} ``` -------------------------------- ### Procuring a Mapping Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Attempts to get a mapping for the last local port if one does not already exist. ```rust /// Try to get a mapping for the last local port if there isn't one already. pub fn procure_mapping(&self) { // requester can't really do anything with this error if returned, so we log it if let Err(e) = self.service_tx.try_send(Message::ProcureMapping) { trace!("Failed to request mapping {e}") } } ``` -------------------------------- ### Client Initialization Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Demonstrates how to create a new port mapping client with default configuration and with custom metrics. ```rust impl Default for Client { fn default() -> Self { Self::new(Config::default()) } } impl Client { /// Create a new port mapping client. pub fn new(config: Config) -> Self { Self::with_metrics(config, Default::default()) } /// Creates a new port mapping client with a previously created metrics collector. pub fn with_metrics(config: Config, metrics: Arc) -> Self { let (service_tx, service_rx) = mpsc::channel(SERVICE_CHANNEL_CAPACITY); let (service, watcher) = Service::new(config, service_rx, metrics.clone()); let handle = AbortOnDropHandle::new(tokio::spawn( async move { service.run().await }.instrument(info_span!("portmapper.service")), )); Client { port_mapping: watcher, service_tx, metrics, _service_handle: std::sync::Arc::new(handle), } } } ``` -------------------------------- ### Service::run Method Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html The main asynchronous loop for the Service, handling incoming messages, probe results, and mapping task completions using tokio::select. ```rust async fn run(mut self) { debug!("portmap starting"); loop { tokio::select! { msg = self.rx.recv() => { trace!("tick: msg {:?}", msg); match msg { Some(msg) => { self.handle_msg(msg).await; }, None => { debug!("portmap service channel dropped. Likely shutting down."); break; } } } mapping_result = util::MaybeFuture{ inner: self.mapping_task.as_mut() } => { trace!("tick: mapping ready"); // regardless of outcome, the task is finished, clear it self.mapping_task = None; // there isn't really a way to react to a join error here. Flatten it to make // it easier to work with self.on_mapping_result(mapping_result); } probe_result = util::MaybeFuture{ inner: self.probing_task.as_mut().map(|(fut, _rec)| fut) } => { trace!("tick: probe ready"); // retrieve the receivers and clear the task let receivers = self.probing_task.take().expect("is some").1; let probe_result = probe_result.map_err(|e| e!(ProbeError::Join { is_panic: e.is_panic(), is_cancelled: e.is_cancelled() })); self.on_probe_result(probe_result, receivers); } Some(event) = self.current_mapping.next() => { trace!("tick: mapping event {:?}", event); match event { ``` -------------------------------- ### Create Announce Request Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/request.rs.html Creates an announce request for PCP. ```rust pub fn announce(client_addr: Ipv6Addr) -> Request { Request { version: Version::Pcp, // opcode announce requires a lifetime of 0 and to ignore the lifetime on response lifetime_seconds: 0, client_addr, // the pcp announce opcode requests and responses have no opcode-specific payload opcode_data: OpcodeData::Announce, } } ``` -------------------------------- ### Port Mapping Service Selection Logic Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html This code snippet demonstrates the logic for selecting a port mapping service (PCP, NAT-PMP, or UPnP) based on availability, configuration, and previous probing results. It prioritizes services in a specific order and includes fallback mechanisms. ```rust self.mapping_task = if pcp { // try pcp if available first let task = mapping::Mapping::new_pcp( protocol, local_ip, local_port, gateway, external_addr, ); Some(AbortOnDropHandle::new(tokio::spawn( task.instrument(info_span!("pcp")), ))) } else if nat_pmp { // next nat_pmp if available let task = mapping::Mapping::new_nat_pmp( protocol, local_ip, local_port, gateway, external_addr, ); Some(AbortOnDropHandle::new(tokio::spawn( task.instrument(info_span!("pmp")), ))) } else if upnp || self.config.enable_upnp { // next upnp if available or enabled let external_port = external_addr.map(|(_addr, port)| port); let gateway = self .full_probe .last_upnp_gateway_addr .as_ref() .map(|(gateway, _last_seen)| gateway.clone()); let task = mapping::Mapping::new_upnp( protocol, local_ip, local_port, gateway, external_port, ); Some(AbortOnDropHandle::new(tokio::spawn( task.instrument(info_span!("upnp")), ))) } else if !recently_probed && self.config.enable_pcp { // if no service is available and the default fallback (upnp) is disabled, try pcp // first let task = mapping::Mapping::new_pcp( protocol, local_ip, local_port, gateway, external_addr, ); Some(AbortOnDropHandle::new(tokio::spawn( task.instrument(info_span!("pcp")), ))) } else if !recently_probed && self.config.enable_nat_pmp { // finally try nat_pmp if enabled let task = mapping::Mapping::new_nat_pmp( protocol, local_ip, local_port, gateway, external_addr, ); Some(AbortOnDropHandle::new(tokio::spawn( task.instrument(info_span!("pmp")), ))) } else { // give up return; } ``` -------------------------------- ### Requesting a Probe Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Shows how to request a probe to the port mapping protocols and receive the result. ```rust /// Request a probe to the port mapping protocols. /// /// Returns the [`oneshot::Receiver`] used to obtain the result of the probe. pub fn probe(&self) -> oneshot::Receiver> { let (result_tx, result_rx) = oneshot::channel(); if let Err(e) = self.service_tx.try_send(Message::Probe { result_tx }) { use mpsc::error::TrySendError::*; // recover the sender and return the error there let (result_tx, e) = match e { Full(Message::Probe { result_tx }) => (result_tx, e!(ProbeError::ChannelFull)), Closed(Message::Probe { result_tx }) => (result_tx, e!(ProbeError::ChannelClosed)), Full(_) | Closed(_) => unreachable!("Sent value is a probe."), }; // sender was just created. If it's dropped we have two send error and are likely // shutting down // NOTE: second Err is infallible match due to being the sent value if let Err(Err(e)) = result_tx.send(Err(e)) { trace!("Failed to request probe: {e}") } } result_rx } ``` -------------------------------- ### Port mapping client and service. Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html This is the main module for port mapping functionality, including client and service implementations. ```rust //! Port mapping client and service. use std::{ net::{Ipv4Addr, SocketAddrV4}, num::NonZeroU16, sync::Arc, time::{Duration, Instant}, }; use current_mapping::CurrentMapping; use futures_lite::StreamExt; use n0_error::{e, stack_error}; use netwatch::interfaces::HomeRouter; use tokio::sync::{mpsc, oneshot, watch}; use tokio_util::task::AbortOnDropHandle; use tracing::{Instrument, debug, info_span, trace}; mod current_mapping; mod mapping; mod metrics; mod nat_pmp; mod pcp; mod upnp; mod util; mod defaults { use std::time::Duration; /// Maximum duration a UPnP search can take before timing out. pub(crate) const UPNP_SEARCH_TIMEOUT: Duration = Duration::from_secs(1); /// Timeout to receive a response from a PCP server. pub(crate) const PCP_RECV_TIMEOUT: Duration = Duration::from_millis(500); /// Timeout to receive a response from a NAT-PMP server. pub(crate) const NAT_PMP_RECV_TIMEOUT: Duration = Duration::from_millis(500); } pub use metrics::Metrics; /// If a port mapping service has been seen within the last [`AVAILABILITY_TRUST_DURATION`] it will /// not be probed again. const AVAILABILITY_TRUST_DURATION: Duration = Duration::from_secs(60 * 10); // 10 minutes /// Capacity of the channel to communicate with the long-running service. const SERVICE_CHANNEL_CAPACITY: usize = 32; // should be plenty /// If a port mapping service has not been seen within the last [`UNAVAILABILITY_TRUST_DURATION`] /// we allow trying a mapping using said protocol. const UNAVAILABILITY_TRUST_DURATION: Duration = Duration::from_secs(5); /// Output of a port mapping probe. #[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)] #[display("portmap={{ UPnP: {upnp}, PMP: {nat_pmp}, PCP: {pcp} }}")] pub struct ProbeOutput { /// If UPnP can be considered available. pub upnp: bool, /// If PCP can be considered available. pub pcp: bool, /// If PMP can be considered available. pub nat_pmp: bool, } impl ProbeOutput { /// Indicates if all port mapping protocols are available. pub fn all_available(&self) -> bool { self.upnp && self.pcp && self.nat_pmp } } #[allow(missing_docs)] #[stack_error(derive, add_meta)] #[derive(Clone)] #[non_exhaustive] pub enum ProbeError { #[error("Mapping channel is full")] ChannelFull, #[error("Mapping channel is closed")] ChannelClosed, #[error("No gateway found for probe")] NoGateway, #[error("gateway found is ipv6, ignoring")] Ipv6Gateway, #[error("Probe task stopped. is_panic: {is_panic}, is_cancelled: {is_cancelled}")] Join { is_panic: bool, is_cancelled: bool }, } #[derive(derive_more::Debug)] enum Message { /// Attempt to get a mapping if the local port is set but there is no mapping. ProcureMapping, /// Request to update the local port. /// /// The resulting external address can be obtained subscribing using /// [`Client::watch_external_address`]. /// A value of `None` will deactivate port mapping. UpdateLocalPort { local_port: Option }, /// Request to probe the port mapping protocols. /// /// The requester should wait for the result at the [`oneshot::Receiver`] counterpart of the /// [`oneshot::Sender`]. Probe { /// Sender side to communicate the result of the probe. #[debug("_")] result_tx: oneshot::Sender>, }, } /// Configuration for UDP or TCP network protocol. #[derive(Debug, Clone, Copy)] pub enum Protocol { /// UDP protocol. Udp, /// TCP protocol. Tcp, } /// Configures which port mapping protocols are enabled in the [`Service`]. #[derive(Debug, Clone)] pub struct Config { /// Whether UPnP is enabled. pub enable_upnp: bool, /// Whether PCP is enabled. pub enable_pcp: bool, /// Whether PMP is enabled. pub enable_nat_pmp: bool, /// Whether to use UDP or TCP. pub protocol: Protocol, } impl Default for Config { /// By default all port mapping protocols are enabled for UDP. fn default() -> Self { Config { enable_upnp: true, enable_pcp: true, enable_nat_pmp: true, protocol: Protocol::Udp, } } } /// Port mapping client. #[derive(Debug, Clone)] pub struct Client { /// A watcher over the most recent external address obtained from port mapping. /// /// See [`watch::Receiver`]. port_mapping: watch::Receiver>, /// Channel used to communicate with the port mapping service. ``` -------------------------------- ### Mapping Release Logic Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp.rs.html This snippet shows the logic for releasing a mapping, including creating a UDP socket, connecting to the gateway, constructing a release request, and sending it. ```rust pub async fn release(self) -> Result<(), Error> { let Mapping { protocol, nonce, local_ip, local_port, gateway, .. } = self; // create the socket and send the request let socket = UdpSocket::bind_full((local_ip, 0))?; socket.connect((gateway, protocol::SERVER_PORT).into())?; let local_port = local_port.into(); let req = protocol::Request::mapping(nonce, protocol, local_port, local_ip, None, None, 0); socket.send(&req.encode()).await?; // mapping deletion is a notification, no point in waiting for the response Ok(()) } } ``` -------------------------------- ### Service::new Constructor Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Constructor for the Service struct, initializing its state and returning the service instance along with a watcher for the current mapping. ```rust fn new( config: Config, rx: mpsc::Receiver, metrics: Arc, ) -> (Self, watch::Receiver>) { let (current_mapping, watcher) = CurrentMapping::new(metrics.clone()); let mut full_probe = Probe::empty(); if let Some(in_the_past) = full_probe .last_probe .checked_sub(AVAILABILITY_TRUST_DURATION) { // we want to do a first full probe, so set is as expired on start-up full_probe.last_probe = in_the_past; } let service = Service { config, local_port: None, rx, current_mapping, full_probe, mapping_task: None, probing_task: None, metrics, }; (service, watcher) } ``` -------------------------------- ### Create Mapping Request Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/request.rs.html Creates a mapping request for PCP. ```rust pub fn mapping( nonce: [u8; 12], protocol: MapProtocol, local_port: u16, local_ip: Ipv4Addr, preferred_external_port: Option, preferred_external_address: Option, lifetime_seconds: u32, ) -> Request { Request { version: Version::Pcp, lifetime_seconds, client_addr: local_ip.to_ipv6_mapped(), opcode_data: OpcodeData::MapData(MapData { nonce, protocol, local_port, // if the pcp client does not know the external port, or does not have a // preference, it must use 0. external_port: preferred_external_port.unwrap_or_default(), external_address: preferred_external_address .unwrap_or(Ipv4Addr::UNSPECIFIED) .to_ipv6_mapped(), }), } } ``` -------------------------------- ### UPnP Port Mapping Implementation Source: https://docs.rs/portmapper/0.16.0/src/portmapper/upnp.rs.html This snippet shows the core implementation for creating and managing UPnP port mappings, including searching for gateways, adding ports (both specific and any), and handling potential errors. ```rust use std::{ net::{Ipv4Addr, SocketAddrV4}, num::NonZeroU16, sync::Arc, time::Duration, }; use igd_next::{AddAnyPortError, GetExternalIpError, RemovePortError, SearchError, aio as aigd}; use n0_error::{e, stack_error}; use tracing::debug; use super::Metrics; pub type Gateway = aigd::Gateway; use crate::{Protocol, defaults::UPNP_SEARCH_TIMEOUT as SEARCH_TIMEOUT}; /// Seconds we ask the router to maintain the port mapping. Use 2 hours for now. const PORT_MAPPING_LEASE_DURATION_SECONDS: u32 = 2 * 60 * 60; /// Tailscale uses the recommended port mapping lifetime for PMP, which is 2 hours. So we assume a /// half lifetime of 1h. See const HALF_LIFETIME: Duration = Duration::from_secs(60 * 60); /// Name with which we register the mapping in the router. const PORT_MAPPING_DESCRIPTION: &str = "iroh-portmap"; #[derive(derive_more::Debug, Clone)] pub struct Mapping { /// Protocol for this mapping. protocol: igd_next::PortMappingProtocol, /// The internet Gateway device (router) used to create this mapping. #[debug("{}", gateway)] gateway: Gateway, /// The external address obtained by this mapping. external_ip: Ipv4Addr, /// External port obtained by this mapping. external_port: NonZeroU16, } #[allow(missing_docs)] #[stack_error(derive, add_meta, std_sources, from_sources)] #[non_exhaustive] pub enum Error { #[error("Zero external port")] ZeroExternalPort {}, #[error("igd device's external ip is ipv6")] NotIpv4 {}, #[error("Remove Port")] RemovePort { source: RemovePortError }, #[error("Search")] Search { source: SearchError }, #[error("Get external IP")] GetExternalIp { source: GetExternalIpError }, #[error("Add any port")] AddAnyPort { source: AddAnyPortError }, #[error("IO")] Io { source: std::io::Error }, } impl Mapping { pub(crate) async fn new( protocol: Protocol, local_addr: Ipv4Addr, port: NonZeroU16, gateway: Option, preferred_port: Option, ) -> Result { let local_addr = SocketAddrV4::new(local_addr, port.into()); // search for a gateway if there is not one already let gateway = if let Some(known_gateway) = gateway { known_gateway } else { // Wrap in manual timeout, because igd_next doesn't respect the set timeout tokio::time::timeout( SEARCH_TIMEOUT, aigd::tokio::search_gateway(igd_next::SearchOptions { timeout: Some(SEARCH_TIMEOUT), ..Default::default() }), ) .await .map_err(|_| { std::io::Error::new(std::io::ErrorKind::TimedOut, "read timeout".to_string()) })?? }; let std::net::IpAddr::V4(external_ip) = gateway.get_external_ip().await? else { return Err(e!(Error::NotIpv4)); }; let protocol = match protocol { Protocol::Udp => igd_next::PortMappingProtocol::UDP, Protocol::Tcp => igd_next::PortMappingProtocol::TCP, }; // if we are trying to get a specific external port, try this first. If this fails, default // to try to get any port if let Some(external_port) = preferred_port && gateway .add_port( protocol, external_port.into(), local_addr.into(), PORT_MAPPING_LEASE_DURATION_SECONDS, PORT_MAPPING_DESCRIPTION, ) .await .is_ok() { return Ok(Mapping { protocol, gateway, external_ip, external_port, }); } let external_port = gateway .add_any_port( protocol, local_addr.into(), PORT_MAPPING_LEASE_DURATION_SECONDS, PORT_MAPPING_DESCRIPTION, ) .await? .try_into() .map_err(|_| e!(Error::ZeroExternalPort))?; Ok(Mapping { protocol, gateway, external_ip, external_port, }) } pub fn half_lifetime(&self) -> Duration { HALF_LIFETIME } /// Releases the mapping. pub(crate) async fn release(self) -> Result<(), Error> { let Mapping { ``` -------------------------------- ### NAT-PMP Response Unit Tests Source: https://docs.rs/portmapper/0.16.0/src/portmapper/nat_pmp/protocol/response.rs.html Unit tests for decoding external address responses and encoding/decoding map responses in the NAT-PMP protocol. ```rust #[cfg(test)] mod tests { use rand::SeedableRng; use super::*; #[test] fn test_decode_external_addr_response() { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); let response = Response::random(Opcode::DetermineExternalAddress, &mut rng); let encoded = response.encode(); assert_eq!(response, Response::decode(&encoded).unwrap()); } #[test] fn test_encode_decode_map_response() { let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); let response = Response::random(Opcode::MapUdp, &mut rng); let encoded = response.encode(); assert_eq!(response, Response::decode(&encoded).unwrap()); } } ``` -------------------------------- ### Probe and Task Handling Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html This snippet shows the logic for creating and handling a probing task within the portmapper, including error handling and instrumentation. ```rust 758 Probe::from_output(config, probe_output, local_ip, gateway, metrics) 759 .await 760 } 761 .instrument(info_span!("portmapper.probe")), 762 ); 763 let receivers = vec![result_tx]; 764 self.probing_task = Some((AbortOnDropHandle::new(handle), receivers)); 765 } 766 } 767 } 768} 769 ``` -------------------------------- ### PCP Availability Probe Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp.rs.html This snippet demonstrates how to probe the local gateway for PCP support. It calls a fallible version of the probe and interprets the response. ```rust pub async fn probe_available(local_ip: Ipv4Addr, gateway: Ipv4Addr) -> bool { match probe_available_fallible(local_ip, gateway).await { Ok(response) => { trace!("probe response: {:?}", response); let protocol::Response { lifetime_seconds: _, epoch_time: _, data, } = response; match data { protocol::OpcodeData::Announce => true, _ => { debug!("server returned an unexpected response type for probe"); // missbehaving server is not useful false } } } Err(e) => { debug!("probe failed: {e}"); false } } } async fn probe_available_fallible( local_ip: Ipv4Addr, gateway: Ipv4Addr, ) -> Result { // create the socket and send the request let socket = UdpSocket::bind_full((local_ip, 0))?; socket.connect((gateway, protocol::SERVER_PORT).into())?; let req = protocol::Request::announce(local_ip.to_ipv6_mapped()); socket.send(&req.encode()).await?; // wait for the response and decode it let mut buffer = vec![0; protocol::Response::MAX_SIZE]; let read = tokio::time::timeout(RECV_TIMEOUT, socket.recv(&mut buffer)) .await .map_err(|_| { e!( Error::Io, std::io::Error::new(std::io::ErrorKind::TimedOut, "read timeout".to_string()) ) })??; let response = protocol::Response::decode(&buffer[..read])?; Ok(response) } ``` -------------------------------- ### PCP Mapping Request Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp.rs.html This snippet shows the creation and sending of a PCP mapping request. It includes setting up a UDP socket, generating a nonce, constructing the mapping request with specified parameters (protocol, local IP/port, preferred external address, and lifetime), and sending it over the network. ```Rust 1//! Definitions and utilities to interact with a PCP server. 2 3use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration}; 4 5use n0_error::{e, stack_error}; 6use netwatch::UdpSocket; 7use rand::Rng; 8use tracing::{debug, trace}; 9 10use crate::{Protocol, defaults::PCP_RECV_TIMEOUT as RECV_TIMEOUT}; 11 12mod protocol; 13 14/// Use the recommended port mapping lifetime for PMP, which is 2 hours. See 15/// 16const MAPPING_REQUESTED_LIFETIME_SECONDS: u32 = 60 * 60; 17 18/// A mapping successfully registered with a PCP server. 19#[derive(Debug)] 20pub struct Mapping { 21 /// Protocol for this mapping. 22 protocol: protocol::MapProtocol, 23 /// Local ip used to create this mapping. 24 local_ip: Ipv4Addr, 25 /// Local port used to create this mapping. 26 local_port: NonZeroU16, 27 /// Gateway address used to registered this mapping. 28 gateway: Ipv4Addr, 29 /// External port of the mapping. 30 external_port: NonZeroU16, 31 /// External address of the mapping. 32 external_address: Ipv4Addr, 33 /// Allowed time for this mapping as informed by the server. 34 lifetime_seconds: u32, 35 /// The nonce of the mapping, used for modifications with the PCP server, for example releasing 36 /// the mapping. 37 nonce: [u8; 12], 38} 39 40#[allow(missing_docs)] 41#[stack_error(derive, add_meta, from_sources)] 42#[non_exhaustive] 43pub enum Error { 44 #[error("received nonce does not match sent request")] 45 NonceMissmatch {}, 46 #[error("received mapping does not match the requested protocol")] 47 ProtocolMissmatch {}, 48 #[error("received mapping is for a local port that does not match the requested one")] 49 PortMissmatch {}, 50 #[error("received 0 external port for mapping")] 51 ZeroExternalPort {}, 52 #[error("received external address is not ipv4")] 53 NotIpv4 {}, 54 #[error("received an announce response for a map request")] 55 InvalidAnnounce {}, 56 #[error("IO error during PCP")] 57 Io { 58 #[error(std_err)] 59 source: std::io::Error, 60 }, 61 #[error("Protocol error during PCP")] 62 Protocol { source: protocol::Error }, 63} 64 65impl super::mapping::PortMapped for Mapping { 66 fn external(&self) -> (Ipv4Addr, NonZeroU16) { 67 (self.external_address, self.external_port) 68 } 69 70 fn half_lifetime(&self) -> Duration { 71 Duration::from_secs((self.lifetime_seconds / 2).into()) 72 } 73} 74 75impl Mapping { 76 /// Attempt to registered a new mapping with the PCP server on the provided gateway. 77 pub async fn new( 78 protocol: Protocol, 79 local_ip: Ipv4Addr, 80 local_port: NonZeroU16, 81 gateway: Ipv4Addr, 82 preferred_external_address: Option<(Ipv4Addr, NonZeroU16)>, 83 ) -> Result { 84 // create the socket and send the request 85 let socket = UdpSocket::bind_full((local_ip, 0))?; 86 socket.connect((gateway, protocol::SERVER_PORT).into())?; 87 88 let mut nonce = [0u8; 12]; 89 rand::rng().fill_bytes(&mut nonce); 90 91 let (requested_address, requested_port) = match preferred_external_address { 92 Some((ip, port)) => (Some(ip), Some(port.into())), 93 None => (None, None), 94 }; 95 96 let protocol = match protocol { 97 Protocol::Udp => protocol::MapProtocol::Udp, 98 Protocol::Tcp => protocol::MapProtocol::Tcp, 99 }; 100 let req = protocol::Request::mapping( 101 nonce, 102 protocol, 103 local_port.into(), 104 local_ip, 105 requested_port, 106 requested_address, 107 MAPPING_REQUESTED_LIFETIME_SECONDS, 108 ); 109 110 socket.send(&req.encode()).await?; 111 112 // wait for the response and decode it 113 let mut buffer = vec![0; protocol::Response::MAX_SIZE]; 114 let read = tokio::time::timeout(RECV_TIMEOUT, socket.recv(&mut buffer)) 115 .await 116 .map_err(|_| { 117 std::io::Error::new(std::io::ErrorKind::TimedOut, "read timeout".to_string()) 118 })??; 119 let response = protocol::Response::decode(&buffer[..read])?; 120 121 // verify that the response is correct and matches the request 122 let protocol::Response { 123 lifetime_seconds, 124 epoch_time: _, 125 data, 126 } = response; 127 128 match data { 129 protocol::OpcodeData::MapData(map_data) => { 130 let protocol::MapData { 131 nonce: received_nonce, 132 protocol: received_protocol, 133 local_port: received_local_port, 134 external_port, 135 external_address, 136 } = map_data; 137 ``` -------------------------------- ### Returns a ProbeOutput indicating which services can be considered available. Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html This function checks the availability of UPnP, PCP, and NAT-PMP services based on the last probing time and a predefined trust duration. It returns a ProbeOutput struct indicating the current availability status of each service. ```rust fn output(&self) -> ProbeOutput { let now = Instant::now(); // check if the last UPnP gateway is valid let upnp = self .last_upnp_gateway_addr .as_ref() .map(|(_gateway_addr, last_probed)| *last_probed + AVAILABILITY_TRUST_DURATION > now) .unwrap_or_default(); let pcp = self .last_pcp .as_ref() .map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now) .unwrap_or_default(); let nat_pmp = self .last_nat_pmp .as_ref() .map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now) .unwrap_or_default(); ProbeOutput { upnp, pcp, nat_pmp } } ``` -------------------------------- ### Test Announce Request Encoding/Decoding Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/request.rs.html Tests the encode and decode functionality for a PCP Announce request. ```Rust let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(42); let request = Request::random(super::super::Opcode::Announce, &mut rng); let encoded = request.encode(); assert_eq!(request, Request::decode(&encoded)); ``` -------------------------------- ### Generating Random Responses for Testing Source: https://docs.rs/portmapper/0.16.0/src/portmapper/nat_pmp/protocol/response.rs.html A helper function to generate random NAT-PMP responses for testing purposes, covering different opcode types. ```rust #[cfg(test)] fn random(opcode: Opcode, rng: &mut R) -> Self { use rand::RngExt; match opcode { Opcode::DetermineExternalAddress => { let octets: [u8; 4] = rng.random(); Response::PublicAddress { epoch_time: rng.random(), public_ip: octets.into(), } } Opcode::MapUdp => Response::PortMap { proto: MapProtocol::Udp, epoch_time: rng.random(), private_port: rng.random(), external_port: rng.random(), lifetime_seconds: rng.random(), }, Opcode::MapTcp => Response::PortMap { proto: MapProtocol::Tcp, epoch_time: rng.random(), private_port: rng.random(), external_port: rng.random(), lifetime_seconds: rng.random(), }, } } ``` -------------------------------- ### OpcodeData::random Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/opcode_data.rs.html Generates random OpcodeData for testing purposes. ```rust 158 #[cfg(test)] 159 pub(crate) fn random(opcode: Opcode, rng: &mut R) -> OpcodeData { 160 match opcode { 161 Opcode::Announce => OpcodeData::Announce, 162 Opcode::Map => OpcodeData::MapData(MapData::random(rng)), 163 } 164 } 165} ``` -------------------------------- ### OpcodeData::decode Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/opcode_data.rs.html Decodes PCP opcode data from a buffer. ```rust 150 Opcode::Announce => Ok(OpcodeData::Announce), 151 Opcode::Map => { 152 let map_data = MapData::decode(buf)?; 153 Ok(OpcodeData::MapData(map_data)) 154 } 155 } 156 } 157 ``` -------------------------------- ### Request Structure Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/request.rs.html Represents a PCP Request with version, lifetime, client address, and opcode-specific data. ```rust pub struct Request { /// [`Version`] to use in this request. pub(super) version: Version, /// Requested lifetime in seconds. pub(super) lifetime_seconds: u32, /// IP Address of the client. /// /// If the IP is an IpV4 address, is represented as a IpV4-mapped IpV6 address. pub(super) client_addr: Ipv6Addr, /// Data associated to the [`super::Opcode`] in this request. pub(super) opcode_data: OpcodeData, } ``` -------------------------------- ### MapData Struct Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/opcode_data.rs.html Contains the data for a PCP Map request, including nonce, protocol, ports, and external address. ```rust pub struct MapData { /// Nonce of the request. Used to verify responses in the client side, and modifications in the /// server side. pub nonce: [u8; 12], /// Protocol for which the mapping is being requested. pub protocol: MapProtocol, /// Local port for the mapping. pub local_port: u16, /// External port of the mapping. pub external_port: u16, /// External ip of the mapping. pub external_address: Ipv6Addr, } ``` -------------------------------- ### Response Constants and Decode Method Source: https://docs.rs/portmapper/0.16.0/src/portmapper/nat_pmp/protocol/response.rs.html Defines minimum and maximum sizes for encoded responses and the RESPONSE_INDICATOR, along with a method to decode map responses. ```rust 96impl Response { 97 /// Minimum size of an encoded [`Response`] sent by a server to this client. 98 pub const MIN_SIZE: usize = // parts of a public ip response 99 1 + // version 100 1 + // opcode 101 2 + // result code 102 4 + // epoch time 103 4; // lifetime 104 105 /// Minimum size of an encoded [`Response`] sent by a server to this client. 106 pub const MAX_SIZE: usize = // parts of mapping response 107 1 + // version 108 1 + // opcode 109 2 + // result code 110 4 + // epoch time 111 2 + // private port 112 2 + // public port 113 4; // lifetime 114 115 /// Indicator ORd into the [`Opcode`] to indicate a response packet. 116 pub const RESPONSE_INDICATOR: u8 = 1u8 << 7; 117 118 /// Decode a map response. 119 fn decode_map(buf: &[u8], proto: MapProtocol) -> Result { 120 if buf.len() != Self::MAX_SIZE { 121 return Err(e!(Error::Malformed)); 122 } 123 124 let epoch_bytes = buf[4..8].try_into().expect("slice has the right len"); 125 let epoch_time = u32::from_be_bytes(epoch_bytes); 126 127 let private_port_bytes = buf[8..10].try_into().expect("slice has the right len"); 128 let private_port = u16::from_be_bytes(private_port_bytes); 129 ``` -------------------------------- ### Encoding a NAT-PMP Map UDP Response Source: https://docs.rs/portmapper/0.16.0/src/portmapper/nat_pmp/protocol/response.rs.html This code snippet demonstrates how to construct a byte buffer for a NAT-PMP Map UDP response, including version, opcode, result code, epoch time, internal port, external port, and lifetime seconds. ```rust } => { let mut buf = Vec::with_capacity(Self::MAX_SIZE); // version buf.push(Version::NatPmp.into()); // response indicator and opcode let opcode: u8 = Opcode::MapUdp.into(); buf.push(Response::RESPONSE_INDICATOR | opcode); // result code let result_code: u16 = ResultCode::Success.into(); for b in result_code.to_be_bytes() { buf.push(b); } // epoch for b in epoch_time.to_be_bytes() { buf.push(b); } // internal port for b in private_port.to_be_bytes() { buf.push(b) } // external port for b in external_port.to_be_bytes() { buf.push(b) } for b in lifetime_seconds.to_be_bytes() { buf.push(b) } buf } ``` -------------------------------- ### Message Handling Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Handles incoming messages, dispatching actions such as procuring a mapping, updating the local port, or requesting a probe. ```rust async fn handle_msg(&mut self, msg: Message) { match msg { Message::ProcureMapping => self.update_local_port(self.local_port).await, Message::UpdateLocalPort { local_port } => self.update_local_port(local_port).await, Message::Probe { result_tx } => self.probe_request(result_tx), } } ``` -------------------------------- ### Accessing Metrics Source: https://docs.rs/portmapper/0.16.0/src/portmapper/lib.rs.html Provides access to the metrics collected by the service. ```rust /// Returns the metrics collected by the service. pub fn metrics(&self) -> &Arc { &self.metrics } ``` -------------------------------- ### Response Struct Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/response.rs.html Represents a PCP successful Response/Notification, including lifetime and epoch time. ```Rust /// A PCP successful Response/Notification. /// /// See [RFC 6887 Response Header](https://datatracker.ietf.org/doc/html/rfc6887#section-7.2) // NOTE: first two fields are *currently* not used, but are useful for debug purposes #[allow(unused)] #[derive(Debug, PartialEq, Eq)] pub struct Response { /// Lifetime in seconds that can be assumed by this response. /// /// For map requests, this lifetime is how long to assume a mapping will last. pub lifetime_seconds: u32, /// Epoch time of the server. pub epoch_time: u32, } ``` -------------------------------- ### OpcodeData::opcode Method Source: https://docs.rs/portmapper/0.16.0/src/portmapper/pcp/protocol/opcode_data.rs.html Returns the associated Opcode for an OpcodeData variant. ```rust pub fn opcode(&self) -> Opcode { match self { OpcodeData::Announce => Opcode::Announce, OpcodeData::MapData(_) => Opcode::Map, } } ``` -------------------------------- ### Encoding a PublicAddress Response Source: https://docs.rs/portmapper/0.16.0/src/portmapper/nat_pmp/protocol/response.rs.html This snippet shows how to encode a PublicAddress response into a byte vector, including version, opcode, result code, epoch time, and public IP. ```rust #[cfg(test)] fn encode(&self) -> Vec { match self { Response::PublicAddress { epoch_time, public_ip, } => { let mut buf = Vec::with_capacity(Self::MIN_SIZE); // version buf.push(Version::NatPmp.into()); // response indicator and opcode let opcode: u8 = Opcode::DetermineExternalAddress.into(); buf.push(Response::RESPONSE_INDICATOR | opcode); // result code let result_code: u16 = ResultCode::Success.into(); for b in result_code.to_be_bytes() { buf.push(b); } // epoch for b in epoch_time.to_be_bytes() { buf.push(b); } // public ip for b in public_ip.octets() { buf.push(b) } buf } Response::PortMap { proto: _, epoch_time, private_port, external_port, ```