Skip to content

Commit

Permalink
feat(iroh-net): Nat-PMP probes and mappings (#1283)
Browse files Browse the repository at this point in the history
  • Loading branch information
divagant-martian authored Jul 20, 2023
1 parent f671aa5 commit 5c38730
Show file tree
Hide file tree
Showing 8 changed files with 705 additions and 29 deletions.
62 changes: 38 additions & 24 deletions iroh-net/src/portmapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use current_mapping::CurrentMapping;
mod current_mapping;
mod mapping;
mod metrics;
mod nat_pmp;
mod pcp;
mod upnp;

Expand All @@ -38,20 +39,20 @@ 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: {pmp}, PCP: {pcp} }}")]
#[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 pmp: bool,
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.pmp
self.upnp && self.pcp && self.nat_pmp
}
}

Expand Down Expand Up @@ -214,8 +215,8 @@ struct Probe {
last_upnp_gateway_addr: Option<(upnp::Gateway, Instant)>,
/// Last time PCP was seen.
last_pcp: Option<Instant>,
// TODO(@divma): PMP placeholder.
last_pmp: Option<Instant>,
/// Last time NAT-PMP was seen.
last_nat_pmp: Option<Instant>,
}

impl Default for Probe {
Expand All @@ -224,7 +225,7 @@ impl Default for Probe {
last_probe: Instant::now() - AVAILABILITY_TRUST_DURATION,
last_upnp_gateway_addr: None,
last_pcp: None,
last_pmp: None,
last_nat_pmp: None,
}
}
}
Expand All @@ -237,11 +238,11 @@ impl Probe {
local_ip: Ipv4Addr,
gateway: Ipv4Addr,
) -> Probe {
let ProbeOutput { upnp, pcp, pmp: _ } = output;
let ProbeOutput { upnp, pcp, nat_pmp } = output;
let Config {
enable_upnp,
enable_pcp,
enable_nat_pmp: _,
enable_nat_pmp,
} = config;
let mut upnp_probing_task = util::MaybeFuture {
inner: (enable_upnp && !upnp).then(|| {
Expand All @@ -264,31 +265,37 @@ impl Probe {
}),
};

let pmp_probing_task = async { None };
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() {
inc!(Metrics, upnp_probes);
}

let mut upnp_done = upnp_probing_task.inner.is_none();
let mut pcp_done = pcp_probing_task.inner.is_none();
let mut pmp_done = true;

tokio::pin!(pmp_probing_task);
let mut nat_pmp_done = nat_pmp_probing_task.inner.is_none();

let mut probe = Probe::default();

while !upnp_done || !pcp_done || !pmp_done {
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_pmp = &mut pmp_probing_task, if !pmp_done => {
trace!("tick: pmp probe ready");
probe.last_pmp = last_pmp;
pmp_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");
Expand Down Expand Up @@ -318,10 +325,13 @@ impl Probe {
.map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now)
.unwrap_or_default();

// not probing for now
let pmp = false;
let nat_pmp = self
.last_nat_pmp
.as_ref()
.map(|last_probed| *last_probed + AVAILABILITY_TRUST_DURATION > now)
.unwrap_or_default();

ProbeOutput { upnp, pcp, pmp }
ProbeOutput { upnp, pcp, nat_pmp }
}

/// Updates a probe with the `Some` values of another probe that is _assumed_ newer.
Expand All @@ -330,7 +340,7 @@ impl Probe {
last_probe,
last_upnp_gateway_addr,
last_pcp,
last_pmp,
last_nat_pmp,
} = probe;
if last_upnp_gateway_addr.is_some() {
inc!(Metrics, upnp_available);
Expand Down Expand Up @@ -359,8 +369,8 @@ impl Probe {
inc!(Metrics, pcp_available);
self.last_pcp = last_pcp;
}
if last_pmp.is_some() {
self.last_pmp = last_pmp;
if last_nat_pmp.is_some() {
self.last_nat_pmp = last_nat_pmp;
}

self.last_probe = last_probe;
Expand Down Expand Up @@ -571,7 +581,7 @@ impl Service {
Err(e) => return debug!("can't get mapping: {e}"),
};

let ProbeOutput { upnp, pcp, pmp: _ } = self.full_probe.output();
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 =
Expand All @@ -583,6 +593,10 @@ impl Service {
self.mapping_task = if pcp || (!recently_probed && self.config.enable_pcp) {
let task = mapping::Mapping::new_pcp(local_ip, local_port, gateway, external_addr);
Some(tokio::spawn(task).into())
} else if nat_pmp || (!recently_probed && self.config.enable_nat_pmp) {
let task =
mapping::Mapping::new_nat_pmp(local_ip, local_port, gateway, external_addr);
Some(tokio::spawn(task).into())
} else if upnp || self.config.enable_upnp {
let external_port = external_addr.map(|(_addr, port)| port);
let gateway = self
Expand Down
25 changes: 24 additions & 1 deletion iroh-net/src/portmapper/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration};

use anyhow::Result;

use super::{pcp, upnp};
use super::{nat_pmp, pcp, upnp};

pub(super) trait PortMapped: std::fmt::Debug + Unpin {
fn external(&self) -> (Ipv4Addr, NonZeroU16);
Expand All @@ -21,6 +21,9 @@ pub enum Mapping {
/// A PCP mapping.
#[debug(transparent)]
Pcp(pcp::Mapping),
/// A NAT-PMP mapping.
#[debug(transparent)]
NatPmp(nat_pmp::Mapping),
}

impl Mapping {
Expand All @@ -36,6 +39,23 @@ impl Mapping {
.map(Self::Pcp)
}

/// Create a new NAT-PMP mapping.
pub(crate) async fn new_nat_pmp(
local_ip: Ipv4Addr,
local_port: NonZeroU16,
gateway: Ipv4Addr,
external_addr: Option<(Ipv4Addr, NonZeroU16)>,
) -> Result<Self> {
nat_pmp::Mapping::new(
local_ip,
local_port,
gateway,
external_addr.map(|(_addr, port)| port),
)
.await
.map(Self::NatPmp)
}

/// Create a new UPnP mapping.
pub(crate) async fn new_upnp(
local_ip: Ipv4Addr,
Expand All @@ -53,6 +73,7 @@ impl Mapping {
match self {
Mapping::Upnp(m) => m.release().await,
Mapping::Pcp(m) => m.release().await,
Mapping::NatPmp(m) => m.release().await,
}
}
}
Expand All @@ -62,13 +83,15 @@ impl PortMapped for Mapping {
match self {
Mapping::Upnp(m) => m.external(),
Mapping::Pcp(m) => m.external(),
Mapping::NatPmp(m) => m.external(),
}
}

fn half_lifetime(&self) -> Duration {
match self {
Mapping::Upnp(m) => m.half_lifetime(),
Mapping::Pcp(m) => m.half_lifetime(),
Mapping::NatPmp(m) => m.half_lifetime(),
}
}
}
Loading

0 comments on commit 5c38730

Please sign in to comment.