Skip to main content

portmapper/
lib.rs

1//! Port mapping client and service.
2
3use std::{
4    net::{Ipv4Addr, SocketAddrV4},
5    num::NonZeroU16,
6    sync::Arc,
7    time::{Duration, Instant},
8};
9
10use current_mapping::CurrentMapping;
11use n0_error::{e, stack_error};
12use n0_future::StreamExt;
13use netwatch::interfaces::HomeRouter;
14use tokio::sync::{mpsc, oneshot, watch};
15use tokio_util::task::AbortOnDropHandle;
16use tracing::{Instrument, debug, info_span, trace};
17
18mod current_mapping;
19mod mapping;
20mod metrics;
21mod observer;
22#[cfg(test)]
23mod observer_tests;
24pub use observer::{Event, MappingProtocol, Observer};
25mod nat_pmp;
26mod pcp;
27mod upnp;
28mod util;
29mod defaults {
30    use std::time::Duration;
31
32    /// Maximum duration a UPnP search can take before timing out.
33    pub(crate) const UPNP_SEARCH_TIMEOUT: Duration = Duration::from_secs(1);
34
35    /// Timeout to receive a response from a PCP server.
36    pub(crate) const PCP_RECV_TIMEOUT: Duration = Duration::from_millis(500);
37
38    /// Timeout to receive a response from a NAT-PMP server.
39    pub(crate) const NAT_PMP_RECV_TIMEOUT: Duration = Duration::from_millis(500);
40}
41
42pub use metrics::Metrics;
43
44/// If a port mapping service has been seen within the last [`AVAILABILITY_TRUST_DURATION`] it will
45/// not be probed again.
46const AVAILABILITY_TRUST_DURATION: Duration = Duration::from_secs(60 * 10); // 10 minutes
47
48/// Capacity of the channel to communicate with the long-running service.
49const SERVICE_CHANNEL_CAPACITY: usize = 32; // should be plenty
50
51/// If a port mapping service has not been seen within the last [`UNAVAILABILITY_TRUST_DURATION`]
52/// we allow trying a mapping using said protocol.
53const UNAVAILABILITY_TRUST_DURATION: Duration = Duration::from_secs(5);
54
55/// Output of a port mapping probe.
56#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
57#[display("portmap={{ UPnP: {upnp}, PMP: {nat_pmp}, PCP: {pcp} }}")]
58pub struct ProbeOutput {
59    /// If UPnP can be considered available.
60    pub upnp: bool,
61    /// If PCP can be considered available.
62    pub pcp: bool,
63    /// If PMP can be considered available.
64    pub nat_pmp: bool,
65}
66
67impl ProbeOutput {
68    /// Indicates if all port mapping protocols are available.
69    pub fn all_available(&self) -> bool {
70        self.upnp && self.pcp && self.nat_pmp
71    }
72}
73
74#[allow(missing_docs)]
75#[stack_error(derive, add_meta)]
76#[derive(Clone)]
77#[non_exhaustive]
78pub enum ProbeError {
79    #[error("Mapping channel is full")]
80    ChannelFull,
81    #[error("Mapping channel is closed")]
82    ChannelClosed,
83    #[error("No gateway found for probe")]
84    NoGateway,
85    #[error("gateway found is ipv6, ignoring")]
86    Ipv6Gateway,
87    #[error("Probe task stopped. is_panic: {is_panic}, is_cancelled: {is_cancelled}")]
88    Join { is_panic: bool, is_cancelled: bool },
89}
90
91#[derive(derive_more::Debug)]
92enum Message {
93    Deactivate {
94        done: oneshot::Sender<()>,
95    },
96    /// Attempt to get a mapping if the local port is set but there is no mapping.
97    ProcureMapping,
98    /// Request to update the local port.
99    ///
100    /// The resulting external address can be obtained subscribing using
101    /// [`Client::watch_external_address`].
102    /// A value of `None` will deactivate port mapping.
103    UpdateLocalPort {
104        local_port: Option<NonZeroU16>,
105    },
106    /// Request to probe the port mapping protocols.
107    ///
108    /// The requester should wait for the result at the [`oneshot::Receiver`] counterpart of the
109    /// [`oneshot::Sender`].
110    Probe {
111        /// Sender side to communicate the result of the probe.
112        #[debug("_")]
113        result_tx: oneshot::Sender<Result<ProbeOutput, ProbeError>>,
114    },
115}
116
117/// Configuration for UDP or TCP network protocol.
118#[derive(Debug, Clone, Copy)]
119pub enum Protocol {
120    /// UDP protocol.
121    Udp,
122    /// TCP protocol.
123    Tcp,
124}
125
126/// Configures which port mapping protocols are enabled in the [`Service`].
127#[derive(Debug, Clone)]
128pub struct Config {
129    /// Whether UPnP is enabled.
130    pub enable_upnp: bool,
131    /// Whether PCP is enabled.
132    pub enable_pcp: bool,
133    /// Whether PMP is enabled.
134    pub enable_nat_pmp: bool,
135    /// Whether to use UDP or TCP.
136    pub protocol: Protocol,
137}
138
139impl Default for Config {
140    /// By default all port mapping protocols are enabled for UDP.
141    fn default() -> Self {
142        Config {
143            enable_upnp: true,
144            enable_pcp: true,
145            enable_nat_pmp: true,
146            protocol: Protocol::Udp,
147        }
148    }
149}
150
151/// Port mapping client.
152#[derive(Debug, Clone)]
153pub struct Client {
154    /// A watcher over the most recent external address obtained from port mapping.
155    ///
156    /// See [`watch::Receiver`].
157    port_mapping: watch::Receiver<Option<SocketAddrV4>>,
158    /// Channel used to communicate with the port mapping service.
159    service_tx: mpsc::Sender<Message>,
160    /// Metrics collected by the service.
161    metrics: Arc<Metrics>,
162    /// A handle to the service that will cancel the spawned task once the client is dropped.
163    _service_handle: std::sync::Arc<AbortOnDropHandle<()>>,
164}
165
166impl Default for Client {
167    fn default() -> Self {
168        Self::new(Config::default())
169    }
170}
171
172impl Client {
173    /// Create a new port mapping client.
174    pub fn new(config: Config) -> Self {
175        Self::with_metrics(config, Default::default())
176    }
177
178    /// Creates a new port mapping client with a previously created metrics collector.
179    pub fn with_metrics(config: Config, metrics: Arc<Metrics>) -> Self {
180        Self::with_observer(config, metrics, Observer::default())
181    }
182
183    /// Create a client whose discovery, lease and release transitions are observed.
184    pub fn with_observer(config: Config, metrics: Arc<Metrics>, observer: Observer) -> Self {
185        let (service_tx, service_rx) = mpsc::channel(SERVICE_CHANNEL_CAPACITY);
186
187        let (mut service, watcher) = Service::new(config, service_rx, metrics.clone());
188        service.observer = observer;
189
190        let handle = AbortOnDropHandle::new(tokio::spawn(
191            async move { service.run().await }.instrument(info_span!("portmapper.service")),
192        ));
193
194        Client {
195            port_mapping: watcher,
196            service_tx,
197            metrics,
198            _service_handle: std::sync::Arc::new(handle),
199        }
200    }
201
202    /// Request a probe to the port mapping protocols.
203    ///
204    /// Returns the [`oneshot::Receiver`] used to obtain the result of the probe.
205    pub fn probe(&self) -> oneshot::Receiver<Result<ProbeOutput, ProbeError>> {
206        let (result_tx, result_rx) = oneshot::channel();
207
208        if let Err(e) = self.service_tx.try_send(Message::Probe { result_tx }) {
209            use mpsc::error::TrySendError::*;
210
211            // recover the sender and return the error there
212            let (result_tx, e) = match e {
213                Full(Message::Probe { result_tx }) => (result_tx, e!(ProbeError::ChannelFull)),
214                Closed(Message::Probe { result_tx }) => (result_tx, e!(ProbeError::ChannelClosed)),
215                Full(_) | Closed(_) => unreachable!("Sent value is a probe."),
216            };
217
218            // sender was just created. If it's dropped we have two send error and are likely
219            // shutting down
220            // NOTE: second Err is infallible match due to being the sent value
221            if let Err(Err(e)) = result_tx.send(Err(e)) {
222                trace!("Failed to request probe: {e}")
223            }
224        }
225        result_rx
226    }
227
228    /// Try to get a mapping for the last local port if there isn't one already.
229    pub fn procure_mapping(&self) {
230        // requester can't really do anything with this error if returned, so we log it
231        if let Err(e) = self.service_tx.try_send(Message::ProcureMapping) {
232            trace!("Failed to request mapping {e}")
233        }
234    }
235
236    /// Update the local port.
237    ///
238    /// If the port changes, this will trigger a port mapping attempt.
239    pub fn update_local_port(&self, local_port: NonZeroU16) {
240        let local_port = Some(local_port);
241        // requester can't really do anything with this error if returned, so we log it
242        if let Err(e) = self
243            .service_tx
244            .try_send(Message::UpdateLocalPort { local_port })
245        {
246            trace!("Failed to update local port {e}")
247        }
248    }
249
250    /// Deactivate port mapping.
251    pub fn deactivate(&self) {
252        // requester can't really do anything with this error if returned, so we log it
253        if let Err(e) = self
254            .service_tx
255            .try_send(Message::UpdateLocalPort { local_port: None })
256        {
257            trace!("Failed to deactivate port mapping {e}")
258        }
259    }
260
261    /// Deactivate and acknowledge completion of lease cleanup. Observer events
262    /// distinguish successful release from a gateway error.
263    pub async fn deactivate_and_wait(&self) {
264        let (done, waiting) = oneshot::channel();
265        if self
266            .service_tx
267            .send(Message::Deactivate { done })
268            .await
269            .is_ok()
270        {
271            let _ = waiting.await;
272        }
273    }
274
275    /// Watch the external address for changes in the mappings.
276    pub fn watch_external_address(&self) -> watch::Receiver<Option<SocketAddrV4>> {
277        self.port_mapping.clone()
278    }
279
280    /// Returns the metrics collected by the service.
281    pub fn metrics(&self) -> &Arc<Metrics> {
282        &self.metrics
283    }
284}
285
286/// Port mapping protocol information obtained during a probe.
287#[derive(Debug)]
288struct Probe {
289    /// When was the probe last updated.
290    last_probe: Instant,
291    /// The last [`upnp::Gateway`] and when was it last seen.
292    last_upnp_gateway_addr: Option<(upnp::Gateway, Instant)>,
293    /// Last time PCP was seen.
294    last_pcp: Option<Instant>,
295    /// Last time NAT-PMP was seen.
296    last_nat_pmp: Option<Instant>,
297}
298
299impl Probe {
300    /// An empty probe set to `now`.
301    fn empty() -> Self {
302        Self {
303            last_probe: Instant::now(),
304            last_upnp_gateway_addr: None,
305            last_pcp: None,
306            last_nat_pmp: None,
307        }
308    }
309    /// Create a new probe based on a previous output.
310    async fn from_output(
311        config: Config,
312        output: ProbeOutput,
313        local_ip: Ipv4Addr,
314        gateway: Ipv4Addr,
315        metrics: Arc<Metrics>,
316    ) -> Probe {
317        let ProbeOutput { upnp, pcp, nat_pmp } = output;
318        let Config {
319            enable_upnp,
320            enable_pcp,
321            enable_nat_pmp,
322            protocol: _,
323        } = config;
324        let mut upnp_probing_task = util::MaybeFuture {
325            inner: (enable_upnp && !upnp).then(|| {
326                let metrics = metrics.clone();
327                Box::pin(async move {
328                    upnp::probe_available(&metrics)
329                        .await
330                        .map(|addr| (addr, Instant::now()))
331                })
332            }),
333        };
334
335        let mut pcp_probing_task = util::MaybeFuture {
336            inner: (enable_pcp && !pcp).then(|| {
337                let metrics = metrics.clone();
338                Box::pin(async move {
339                    metrics.pcp_probes.inc();
340                    pcp::probe_available(local_ip, gateway)
341                        .await
342                        .then(Instant::now)
343                })
344            }),
345        };
346
347        let mut nat_pmp_probing_task = util::MaybeFuture {
348            inner: (enable_nat_pmp && !nat_pmp).then(|| {
349                Box::pin(async {
350                    nat_pmp::probe_available(local_ip, gateway)
351                        .await
352                        .then(Instant::now)
353                })
354            }),
355        };
356
357        if upnp_probing_task.inner.is_some() {
358            metrics.upnp_probes.inc();
359        }
360
361        let mut upnp_done = upnp_probing_task.inner.is_none();
362        let mut pcp_done = pcp_probing_task.inner.is_none();
363        let mut nat_pmp_done = nat_pmp_probing_task.inner.is_none();
364
365        let mut probe = Probe::empty();
366
367        while !upnp_done || !pcp_done || !nat_pmp_done {
368            tokio::select! {
369                last_upnp_gateway_addr = &mut upnp_probing_task, if !upnp_done => {
370                    trace!("tick: upnp probe ready");
371                    probe.last_upnp_gateway_addr = last_upnp_gateway_addr;
372                    upnp_done = true;
373                },
374                last_nat_pmp = &mut nat_pmp_probing_task, if !nat_pmp_done => {
375                    trace!("tick: nat_pmp probe ready");
376                    probe.last_nat_pmp = last_nat_pmp;
377                    nat_pmp_done = true;
378                },
379                last_pcp = &mut pcp_probing_task, if !pcp_done => {
380                    trace!("tick: pcp probe ready");
381                    probe.last_pcp = last_pcp;
382                    pcp_done = true;
383                },
384            }
385        }
386
387        probe
388    }
389
390    /// Returns a [`ProbeOutput`] indicating which services can be considered available.
391    fn output(&self) -> ProbeOutput {
392        let now = Instant::now();
393
394        // check if the last UPnP gateway is valid
395        let upnp = self
396            .last_upnp_gateway_addr
397            .as_ref()
398            .map(|(_gateway_addr, last_probed)| *last_probed + AVAILABILITY_TRUST_DURATION > now)
399            .unwrap_or_default();
400
401        let pcp = self
402            .last_pcp
403            .as_ref()
404            .map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now)
405            .unwrap_or_default();
406
407        let nat_pmp = self
408            .last_nat_pmp
409            .as_ref()
410            .map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now)
411            .unwrap_or_default();
412
413        ProbeOutput { upnp, pcp, nat_pmp }
414    }
415
416    /// Updates a probe with the `Some` values of another probe that is _assumed_ newer.
417    fn update(&mut self, probe: Probe, metrics: &Arc<Metrics>) {
418        let Probe {
419            last_probe,
420            last_upnp_gateway_addr,
421            last_pcp,
422            last_nat_pmp,
423        } = probe;
424        if last_upnp_gateway_addr.is_some() {
425            metrics.upnp_available.inc();
426            let new_gateway = last_upnp_gateway_addr
427                .as_ref()
428                .map(|(addr, _last_seen)| addr);
429            let old_gateway = self
430                .last_upnp_gateway_addr
431                .as_ref()
432                .map(|(addr, _last_seen)| addr);
433            if new_gateway != old_gateway {
434                metrics.upnp_gateway_updated.inc();
435                debug!(
436                    "upnp gateway changed {:?} -> {:?}",
437                    old_gateway
438                        .map(|gw| gw.to_string())
439                        .unwrap_or("None".into()),
440                    new_gateway
441                        .map(|gw| gw.to_string())
442                        .unwrap_or("None".into())
443                )
444            };
445            self.last_upnp_gateway_addr = last_upnp_gateway_addr;
446        }
447        if last_pcp.is_some() {
448            metrics.pcp_available.inc();
449            self.last_pcp = last_pcp;
450        }
451        if last_nat_pmp.is_some() {
452            self.last_nat_pmp = last_nat_pmp;
453        }
454
455        self.last_probe = last_probe;
456    }
457}
458
459// mainly to make clippy happy
460type ProbeResult = Result<ProbeOutput, ProbeError>;
461
462/// A port mapping client.
463#[derive(Debug)]
464pub struct Service {
465    config: Config,
466    observer: Observer,
467    /// Local port to map.
468    local_port: Option<NonZeroU16>,
469    /// Channel over which the service is informed of messages.
470    ///
471    /// The service will stop when all senders are gone.
472    rx: mpsc::Receiver<Message>,
473    /// Currently active mapping.
474    current_mapping: CurrentMapping,
475    /// Last updated probe.
476    full_probe: Probe,
477    /// Task attempting to get a port mapping.
478    ///
479    /// This task will be cancelled if a request to set the local port arrives before it's
480    /// finished.
481    mapping_task: Option<AbortOnDropHandle<Result<mapping::Mapping, mapping::Error>>>,
482    /// Task probing the necessary protocols.
483    ///
484    /// Requests for a probe that arrive while this task is still in progress will receive the same
485    /// result.
486    probing_task: Option<(AbortOnDropHandle<Probe>, Vec<oneshot::Sender<ProbeResult>>)>,
487    metrics: Arc<Metrics>,
488}
489
490impl Service {
491    fn new(
492        config: Config,
493        rx: mpsc::Receiver<Message>,
494        metrics: Arc<Metrics>,
495    ) -> (Self, watch::Receiver<Option<SocketAddrV4>>) {
496        let (current_mapping, watcher) = CurrentMapping::new(metrics.clone());
497        let mut full_probe = Probe::empty();
498        if let Some(in_the_past) = full_probe
499            .last_probe
500            .checked_sub(AVAILABILITY_TRUST_DURATION)
501        {
502            // we want to do a first full probe, so set is as expired on start-up
503            full_probe.last_probe = in_the_past;
504        }
505        let service = Service {
506            config,
507            observer: Observer::default(),
508            local_port: None,
509            rx,
510            current_mapping,
511            full_probe,
512            mapping_task: None,
513            probing_task: None,
514            metrics,
515        };
516
517        (service, watcher)
518    }
519
520    /// Clears the current mapping and releases it.
521    async fn invalidate_mapping(&mut self) {
522        if let Some(old_mapping) = self.current_mapping.update(None) {
523            use mapping::PortMapped;
524            let (ip, port) = old_mapping.external();
525            let external = SocketAddrV4::new(ip, port.get());
526            let confirmed = matches!(&old_mapping, mapping::Mapping::Upnp(_));
527            self.observer.emit(Event::Releasing(external));
528            match old_mapping.release().await {
529                Ok(()) => self.observer.emit(Event::Released {
530                    external,
531                    confirmed,
532                }),
533                Err(e) => {
534                    debug!("failed to release mapping {e}");
535                    self.observer.emit(Event::ReleaseFailed {
536                        external,
537                        error: observer::detail(e),
538                    });
539                }
540            }
541        }
542    }
543
544    async fn run(mut self) {
545        debug!("portmap starting");
546        loop {
547            tokio::select! {
548                msg = self.rx.recv() => {
549                    trace!("tick: msg {msg:?}");
550                    match msg {
551                        Some(msg) => {
552                            self.handle_msg(msg).await;
553                        },
554                        None => {
555                            debug!("portmap service channel dropped. Likely shutting down.");
556                            break;
557                        }
558                    }
559                }
560                mapping_result = util::MaybeFuture{ inner: self.mapping_task.as_mut() } => {
561                    trace!("tick: mapping ready");
562                    // regardless of outcome, the task is finished, clear it
563                    self.mapping_task = None;
564                    // there isn't really a way to react to a join error here. Flatten it to make
565                    // it easier to work with
566                    self.on_mapping_result(mapping_result);
567                }
568                probe_result = util::MaybeFuture{ inner: self.probing_task.as_mut().map(|(fut, _rec)| fut) } => {
569                    trace!("tick: probe ready");
570                    // retrieve the receivers and clear the task
571                    let receivers = self.probing_task.take().expect("is some").1;
572                    let probe_result = probe_result.map_err(|e| e!(ProbeError::Join { is_panic: e.is_panic(), is_cancelled: e.is_cancelled() }));
573                    self.on_probe_result(probe_result, receivers);
574                }
575                Some(event) = self.current_mapping.next() => {
576                    trace!("tick: mapping event {event:?}");
577                    match event {
578                        current_mapping::Event::Renew { external_ip, external_port } => {
579                            self.get_mapping(Some((external_ip, external_port)));
580                        },
581                        current_mapping::Event::Expired { external_ip, external_port } => {
582                            self.observer.emit(Event::Expired(SocketAddrV4::new(external_ip, external_port.get())));
583                            self.get_mapping(Some((external_ip, external_port)));
584                        },
585                    }
586
587                }
588            }
589        }
590    }
591
592    fn on_probe_result(
593        &mut self,
594        result: Result<Probe, ProbeError>,
595        receivers: Vec<oneshot::Sender<ProbeResult>>,
596    ) {
597        let result = result.map(|probe| {
598            self.full_probe.update(probe, &self.metrics);
599            // TODO(@divma): the gateway of the current mapping could have changed. Tailscale
600            // still assumes the current mapping is valid/active and will return it even after
601            // this
602            let output = self.full_probe.output();
603            trace!(?output, "probe output");
604            output
605        });
606        match &result {
607            Ok(output) => self.observer.emit(Event::Discovered(output.clone())),
608            Err(error) => self
609                .observer
610                .emit(Event::DiscoveryFailed(observer::detail(error))),
611        }
612        for tx in receivers {
613            // ignore the error. If the receiver is no longer there we don't really care
614            let _ = tx.send(result.clone());
615        }
616    }
617
618    fn on_mapping_result(
619        &mut self,
620        result: Result<Result<mapping::Mapping, mapping::Error>, tokio::task::JoinError>,
621    ) {
622        match result {
623            Ok(Ok(mapping)) => {
624                use mapping::PortMapped;
625                let protocol = match &mapping {
626                    mapping::Mapping::Pcp(_) => MappingProtocol::Pcp,
627                    mapping::Mapping::NatPmp(_) => MappingProtocol::NatPmp,
628                    mapping::Mapping::Upnp(_) => MappingProtocol::Upnp,
629                };
630                let (ip, port) = mapping.external();
631                self.observer.emit(Event::Active {
632                    protocol,
633                    external: SocketAddrV4::new(ip, port.get()),
634                    lifetime: mapping.half_lifetime().saturating_mul(2),
635                });
636                self.current_mapping.update(Some(mapping));
637            }
638            Ok(Err(e)) => {
639                debug!("failed to get a port mapping {e}");
640                self.metrics.mapping_failures.inc();
641                self.observer
642                    .emit(Event::MappingFailed(observer::detail(e)));
643            }
644            Err(e) => {
645                debug!("failed to get a port mapping {e}");
646                self.metrics.mapping_failures.inc();
647                self.observer
648                    .emit(Event::MappingFailed(observer::detail(e)));
649            }
650        }
651    }
652
653    async fn handle_msg(&mut self, msg: Message) {
654        match msg {
655            Message::Deactivate { done } => {
656                self.update_local_port(None).await;
657                self.probing_task = None;
658                let _ = done.send(());
659            }
660            Message::ProcureMapping => self.update_local_port(self.local_port).await,
661            Message::UpdateLocalPort { local_port } => self.update_local_port(local_port).await,
662            Message::Probe { result_tx } => self.probe_request(result_tx),
663        }
664    }
665
666    /// Updates the local port of the port mapping service.
667    ///
668    /// If the port changed, any port mapping task is cancelled. If the new port is some, it will
669    /// start a new port mapping task.
670    async fn update_local_port(&mut self, local_port: Option<NonZeroU16>) {
671        // ignore requests to update the local port in a way that does not produce a change
672        if local_port != self.local_port {
673            self.metrics.local_port_updates.inc();
674            let old_port = std::mem::replace(&mut self.local_port, local_port);
675
676            // clear the current mapping task if any
677
678            let dropped_task = self.mapping_task.take();
679            // check if the dropped task had finished to reduce log noise
680            let did_cancel = dropped_task
681                .map(|task| !task.is_finished())
682                .unwrap_or_default();
683
684            if did_cancel {
685                self.observer.emit(Event::Cancelled);
686                debug!(
687                    "canceled mapping task due to local port update. Old: {:?} New: {:?}",
688                    old_port, self.local_port
689                )
690            }
691
692            // get the current external port if any to try to get it again
693            let external_addr = self.current_mapping.external();
694
695            // since the port has changed, the current mapping is no longer valid and should be
696            // released
697
698            if external_addr.is_some() {
699                self.invalidate_mapping().await;
700            }
701
702            // start a new mapping task to account for the new port if necessary
703            self.get_mapping(external_addr);
704        } else if self.current_mapping.external().is_none() {
705            // if the local port has not changed, but there is no active mapping try to get one
706            self.get_mapping(None)
707        }
708    }
709
710    fn get_mapping(&mut self, external_addr: Option<(Ipv4Addr, NonZeroU16)>) {
711        if let Some(local_port) = self.local_port {
712            self.metrics.mapping_attempts.inc();
713
714            let (local_ip, gateway) = match ip_and_gateway() {
715                Ok(ip_and_gw) => ip_and_gw,
716                Err(e) => {
717                    self.observer
718                        .emit(Event::MappingFailed(observer::detail(&e)));
719                    return debug!("can't get mapping: {e}");
720                }
721            };
722
723            let ProbeOutput { upnp, pcp, nat_pmp } = self.full_probe.output();
724
725            debug!("getting a port mapping for {local_ip}:{local_port} -> {external_addr:?}");
726            let recently_probed =
727                self.full_probe.last_probe + UNAVAILABILITY_TRUST_DURATION > Instant::now();
728            let protocol = self.config.protocol;
729            // Prefer discovered PCP, NAT-PMP, then UPnP. Without a recent
730            // probe, preserve the configured fallback order.
731            let selected = if pcp {
732                MappingProtocol::Pcp
733            } else if nat_pmp {
734                MappingProtocol::NatPmp
735            } else if upnp || self.config.enable_upnp {
736                MappingProtocol::Upnp
737            } else if !recently_probed && self.config.enable_pcp {
738                MappingProtocol::Pcp
739            } else if !recently_probed && self.config.enable_nat_pmp {
740                MappingProtocol::NatPmp
741            } else {
742                self.observer
743                    .emit(Event::MappingFailed("no supported mapping protocol".into()));
744                return;
745            };
746            self.observer.emit(Event::Requesting {
747                protocol: selected,
748                gateway,
749                renewal: self.current_mapping.external().is_some(),
750            });
751            let upnp_gateway = self
752                .full_probe
753                .last_upnp_gateway_addr
754                .as_ref()
755                .map(|(gateway, _)| gateway.clone());
756            self.mapping_task = Some(AbortOnDropHandle::new(tokio::spawn(
757                async move {
758                    match selected {
759                        MappingProtocol::Pcp => {
760                            mapping::Mapping::new_pcp(
761                                protocol,
762                                local_ip,
763                                local_port,
764                                gateway,
765                                external_addr,
766                            )
767                            .await
768                        }
769                        MappingProtocol::NatPmp => {
770                            mapping::Mapping::new_nat_pmp(
771                                protocol,
772                                local_ip,
773                                local_port,
774                                gateway,
775                                external_addr,
776                            )
777                            .await
778                        }
779                        MappingProtocol::Upnp => {
780                            mapping::Mapping::new_upnp(
781                                protocol,
782                                local_ip,
783                                local_port,
784                                upnp_gateway,
785                                external_addr.map(|(_, port)| port),
786                            )
787                            .await
788                        }
789                    }
790                }
791                .instrument(info_span!("mapping", ?selected)),
792            )));
793        }
794    }
795
796    /// Handles a probe request.
797    ///
798    /// If there is a task getting a probe, the receiver will be added with any other waiting for a
799    /// result. If no probe is underway, a result can be returned immediately if it's still
800    /// considered valid. Otherwise, a new probe task will be started.
801    fn probe_request(&mut self, result_tx: oneshot::Sender<Result<ProbeOutput, ProbeError>>) {
802        match self.probing_task.as_mut() {
803            Some((_task_handle, receivers)) => receivers.push(result_tx),
804            None => {
805                let probe_output = self.full_probe.output();
806                if probe_output.all_available() {
807                    self.observer.emit(Event::Discovered(probe_output.clone()));
808                    // we don't care if the requester is no longer there
809                    let _ = result_tx.send(Ok(probe_output));
810                } else {
811                    self.metrics.probes_started.inc();
812                    self.observer.emit(Event::Discovering);
813
814                    let (local_ip, gateway) = match ip_and_gateway() {
815                        Ok(ip_and_gw) => ip_and_gw,
816                        Err(e) => {
817                            // there is no guarantee this will be displayed, so log it anyway
818                            debug!("could not start probe: {e}");
819                            self.observer
820                                .emit(Event::DiscoveryFailed(observer::detail(&e)));
821                            let _ = result_tx.send(Err(e));
822                            return;
823                        }
824                    };
825
826                    self.observer.emit(Event::Gateway { local_ip, gateway });
827
828                    let config = self.config.clone();
829                    let metrics = self.metrics.clone();
830                    let handle = tokio::spawn(
831                        async move {
832                            Probe::from_output(config, probe_output, local_ip, gateway, metrics)
833                                .await
834                        }
835                        .instrument(info_span!("portmapper.probe")),
836                    );
837                    let receivers = vec![result_tx];
838                    self.probing_task = Some((AbortOnDropHandle::new(handle), receivers));
839                }
840            }
841        }
842    }
843}
844
845/// Gets the local ip and gateway address for port mapping.
846fn ip_and_gateway() -> Result<(Ipv4Addr, Ipv4Addr), ProbeError> {
847    let Some(HomeRouter { gateway, my_ip }) = HomeRouter::new() else {
848        return Err(e!(ProbeError::NoGateway));
849    };
850
851    let local_ip = match my_ip {
852        Some(std::net::IpAddr::V4(ip))
853            if !ip.is_unspecified() && !ip.is_loopback() && !ip.is_multicast() =>
854        {
855            ip
856        }
857        other => {
858            debug!("no address suitable for port mapping found ({other:?}), using localhost");
859            Ipv4Addr::LOCALHOST
860        }
861    };
862
863    let std::net::IpAddr::V4(gateway) = gateway else {
864        return Err(e!(ProbeError::Ipv6Gateway));
865    };
866
867    Ok((local_ip, gateway))
868}