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}