Skip to main content

dig_nat/
runtime.rs

1//! The runtime-handle carrier — the LIVE transport handles + discovery inputs the data-only
2//! [`NatConfig`](crate::NatConfig) deliberately cannot hold.
3//!
4//! [`NatConfig`] is `Clone + Debug` DATA (which methods are enabled, timeouts, endpoints, the binding
5//! policy). The relay-dependent + port-mapping tiers additionally need LIVE, non-cloneable things — a
6//! relay coordinator, a relay data-plane, an IGD gateway handle, this node's discovered addresses. Jam
7//! them into `NatConfig` and it could no longer be cheap cloneable data. So they live here, in a
8//! separate [`NatRuntime`] carrier that is intentionally **neither `Clone` nor `Debug`** (it holds
9//! `Arc<dyn …>` trait objects + live sockets). [`connect_with_runtime`](crate::connect_with_runtime)
10//! takes both — the config for the data, the runtime for the handles — and composes the FULL ladder.
11//!
12//! Every field is OPTIONAL: a tier whose inputs are absent is simply NOT composed. This keeps the
13//! composition honest — `connect` never claims a tier it cannot actually run, and never produces a
14//! silently-broken dial. A node supplies exactly the handles it has (a NAT'd node with a relay
15//! reservation supplies the hole-punch + relayed handles; a node with a mappable gateway supplies the
16//! port + gateway; a fully-public node supplies none and gets Direct only).
17
18use std::net::{IpAddr, Ipv4Addr, SocketAddr};
19use std::sync::Arc;
20
21use crate::method::hole_punch::HolePunchCoordinator;
22use crate::method::relayed::RelayedDialer;
23use crate::method::upnp::IgdGateway;
24
25/// Live transport handles + discovery inputs for the non-Direct traversal tiers. Built with a fluent
26/// builder; passed to [`connect_with_runtime`](crate::connect_with_runtime) alongside the
27/// [`NatConfig`](crate::NatConfig). Deliberately NOT `Clone`/`Debug` — it carries live `Arc<dyn …>`
28/// handles, unlike the data-only config.
29#[derive(Default)]
30pub struct NatRuntime {
31    /// This node's local listen port — mapped + advertised by the port-mapping tiers (UPnP/NAT-PMP/
32    /// PCP). Absent → those tiers are not composed.
33    pub(crate) local_port: Option<u16>,
34    /// The local IPv4 default-gateway address for the NAT-PMP + PCP mapping tiers.
35    pub(crate) gateway_v4: Option<Ipv4Addr>,
36    /// This node's client IP as the gateway sees it — required in the PCP MAP request (RFC 6887).
37    pub(crate) client_ip: Option<IpAddr>,
38    /// UPnP/IGD gateway handle; `None` uses the real SSDP-discovered gateway when a `local_port` is
39    /// set (a test injects a fake here).
40    pub(crate) igd: Option<Arc<dyn IgdGateway>>,
41    /// This node's STUN-discovered reflexive address, advertised in the RLY-007 hole-punch exchange.
42    pub(crate) my_external_addr: Option<SocketAddr>,
43    /// The relay hole-punch coordinator (RLY-007 signaling) — enables the hole-punch tier.
44    pub(crate) hole_punch: Option<Arc<dyn HolePunchCoordinator>>,
45    /// The relay data-plane tunnel dialer (RLY-002) — enables the relayed (TURN-last) tier, carrying
46    /// mTLS over the relay so a relayed connection is not weaker than a direct one.
47    pub(crate) relayed: Option<Arc<dyn RelayedDialer>>,
48}
49
50impl NatRuntime {
51    /// An empty runtime — no handles wired, so only the Direct tier is composable. Equivalent to
52    /// [`NatRuntime::default`]; the starting point for the builder.
53    pub fn builder() -> NatRuntimeBuilder {
54        NatRuntimeBuilder {
55            rt: NatRuntime::default(),
56        }
57    }
58}
59
60/// Fluent builder for [`NatRuntime`]. Set only the handles this node actually has; the rest stay
61/// `None` and their tiers are not composed.
62#[derive(Default)]
63pub struct NatRuntimeBuilder {
64    rt: NatRuntime,
65}
66
67impl NatRuntimeBuilder {
68    /// Set this node's local listen port (enables the port-mapping tiers, given their gateway inputs).
69    pub fn local_port(mut self, port: u16) -> Self {
70        self.rt.local_port = Some(port);
71        self
72    }
73
74    /// Set the local IPv4 default-gateway for the NAT-PMP + PCP tiers.
75    pub fn gateway_v4(mut self, gateway: Ipv4Addr) -> Self {
76        self.rt.gateway_v4 = Some(gateway);
77        self
78    }
79
80    /// Set this node's client IP for the PCP MAP request.
81    pub fn client_ip(mut self, ip: IpAddr) -> Self {
82        self.rt.client_ip = Some(ip);
83        self
84    }
85
86    /// Inject a UPnP/IGD gateway handle (a test fake, or a pre-built real gateway). Absent → the real
87    /// SSDP-discovered gateway is used when a `local_port` is set.
88    pub fn igd(mut self, igd: Arc<dyn IgdGateway>) -> Self {
89        self.rt.igd = Some(igd);
90        self
91    }
92
93    /// Set this node's STUN-discovered reflexive address for the hole-punch exchange.
94    pub fn my_external_addr(mut self, addr: SocketAddr) -> Self {
95        self.rt.my_external_addr = Some(addr);
96        self
97    }
98
99    /// Wire the relay hole-punch coordinator (enables the hole-punch tier; also needs
100    /// [`my_external_addr`](Self::my_external_addr)).
101    pub fn hole_punch(mut self, coordinator: Arc<dyn HolePunchCoordinator>) -> Self {
102        self.rt.hole_punch = Some(coordinator);
103        self
104    }
105
106    /// Wire the relay data-plane tunnel dialer (enables the relayed / TURN-last tier).
107    pub fn relayed(mut self, relayed: Arc<dyn RelayedDialer>) -> Self {
108        self.rt.relayed = Some(relayed);
109        self
110    }
111
112    /// Finalize the runtime carrier.
113    pub fn build(self) -> NatRuntime {
114        self.rt
115    }
116}