1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! # dig-nat — abstract NAT traversal for DIG Node peer connections
//!
//! One API, [`connect`], establishes a **mutually-authenticated (mTLS)** connection to a peer using
//! the best available NAT-traversal method, transparently. **The caller never chooses the method** —
//! they describe the peer once and get back a verified [`PeerConnection`]; which technique got there
//! is reported for observability but is not something the caller handles.
//!
//! ## Traversal order (first success wins, relay last)
//!
//! Internally the [`strategy`] attempts, in this order:
//! 1. **Direct** — peer publicly reachable / already port-forwarded ([`method::direct`])
//! 2. **UPnP/IGD** port mapping ([`method::upnp`])
//! 3. **NAT-PMP** (RFC 6886, [`method::natpmp`])
//! 4. **PCP** (RFC 6887, [`method::pcp`])
//! 5. **Relay-coordinated hole-punch** (RLY-007, [`method::hole_punch`])
//! 6. **Relayed transport** via `relay.dig.net` — the LAST resort ([`relay`])
//!
//! [`stun`] (RFC 5389) discovers this node's reflexive address for candidate advertisement +
//! hole-punch coordination.
//!
//! ## Streaming-first + multiplexed transport
//!
//! Whatever tier establishes the connection, the result is uniform: a [`PeerConnection`] wrapping a
//! single mTLS byte stream in [`yamux`](mux) multiplexing. The caller opens **many cheap concurrent
//! logical streams** ([`PeerConnection::open_stream`]) with no head-of-line blocking, and
//! **byte-range streams** ([`PeerConnection::open_range_stream`]) scoped to `[offset, len)` of a
//! resource — so a downloader fetches DIFFERENT ranges from DIFFERENT peers in parallel and
//! reassembles. The API is streaming (read bytes as they arrive), never buffer-the-whole-response.
//!
//! ## Identity + mTLS — delegated to `dig-tls`
//!
//! Every peer connection is mutual TLS, and the entire certificate model is owned by the canonical
//! [`dig-tls`](dig_tls) crate (L00): the shipped public DigNetwork CA, the per-peer CA-signed
//! [`NodeCert`], `peer_id = SHA-256(TLS SPKI DER)`, the #1204 BLS-G1 cert binding, and the ready
//! rustls mutual-auth configs. dig-nat presents this node's [`NodeCert`] and uses
//! [`dig_tls::client_config`] to pin the remote's `peer_id` to the [`peer::PeerTarget::peer_id`] the
//! caller asked for — so the transport is self-authenticating. dig-nat holds NO cert/binding/peer_id
//! code of its own (it was extracted to dig-tls in 0.6.0); the names below are re-exports for
//! convenience.
//!
//! ## Graceful fallback + relay resilience
//!
//! Each method is bounded by a per-method timeout; if ALL fail, [`connect`] returns a clear
//! [`NatError::AllMethodsFailed`] (never panics, never hangs). The [`relay`] client — used both as
//! the last-resort transport and as a node's persistent reachability channel — establishes and
//! maintains its session with keepalive + capped-exponential-backoff reconnect, tolerates the relay
//! being down (retries in the background, never crashes the node), logs once per state change, and
//! honours the `DIG_RELAY_URL=off` opt-out. See [`relay::RelayStatus`].
//!
//! ## Example
//!
//! ```no_run
//! # use std::sync::Arc;
//! # use dig_nat::{connect, NatConfig, NodeCert, PeerTarget, PeerId};
//! # async fn run(node: Arc<NodeCert>, peer_id: PeerId, addr: std::net::SocketAddr) -> Result<(), dig_nat::NatError> {
//! let peer = PeerTarget::with_addr(peer_id, addr, "DIG_MAINNET");
//! let conn = connect(&peer, &node, &NatConfig::default()).await?;
//! println!("connected to {} via {:?}", conn.peer_id, conn.method);
//! # Ok(()) }
//! ```
use Arc;
// --- Certificate / mTLS / identity model — re-exported from the canonical `dig-tls` crate (L00).
// dig-nat CONSUMES dig-tls for ALL of these (it holds no copy of its own), so a single source of
// truth means the DIG cert shape can never byte-drift between crates. ---
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
use MtlsDialer;
use DirectMethod;
use NatPmpMethod;
use PcpMethod;
/// Establish a mutually-authenticated connection to `peer` with an empty runtime — the convenience
/// entry point for a caller that holds NO live transport handles (a publicly-reachable node). Only
/// the **Direct** tier is composable without runtime handles; a NAT'd node that needs the full ladder
/// (UPnP/NAT-PMP/PCP/hole-punch/relayed) calls [`connect_with_runtime`] with a [`NatRuntime`] carrying
/// the gateway/port/relay handles.
///
/// `node` is this node's [`NodeCert`] — its CA-signed mTLS identity from [`dig-tls`](dig_tls),
/// presented as the client certificate; `config` selects which methods are enabled, the per-method
/// timeout, and the [`BindingPolicy`] applied to the peer's #1204 cert binding.
///
/// # Errors
/// - [`NatError::NoMethodsEnabled`] — no method could be composed (nothing enabled, or the enabled
/// tiers all lacked their runtime inputs — here, only Direct is available).
/// - [`NatError::AllMethodsFailed`] — every composed method failed (with per-method reasons).
///
/// This never panics and never hangs: every method + dial is bounded by
/// [`NatConfig::per_method_timeout`].
pub async
/// Establish a mutually-authenticated connection to `peer`, auto-composing the **FULL** NAT-traversal
/// ladder — direct → UPnP → NAT-PMP → PCP → hole-punch → relayed — trying each in rank order, first
/// success wins, relay last. The caller never chooses the method: it supplies the data [`NatConfig`]
/// and the live [`NatRuntime`] handles, and the strategy picks the first tier that establishes an
/// mTLS [`PeerConnection`] whose remote `peer_id` matches [`PeerTarget::peer_id`].
///
/// Each tier is composed ONLY when it is enabled in `config` AND its runtime inputs are present in
/// `runtime` (an absent tier is skipped — the composition is honest, never a silently-broken dial):
/// - **Direct** — always (no runtime input).
/// - **UPnP** — `runtime.local_port` (+ an optional injected IGD gateway; else the real one).
/// - **NAT-PMP** — `runtime.local_port` + `runtime.gateway_v4`.
/// - **PCP** — `runtime.local_port` + `runtime.gateway_v4` + `runtime.client_ip`.
/// - **Hole-punch** — `runtime.hole_punch` + `runtime.my_external_addr`.
/// - **Relayed** — `runtime.relayed` (carries mTLS over the relay tunnel — NOT a weaker connection).
///
/// Every tier — including the relayed one — runs the SAME dig-tls mTLS: the CA-chained [`NodeCert`],
/// the `peer_id` pin, and the #1204 BLS binding. IPv6 is preferred at every IP-dialing tier via
/// `dig-ip` (§5.2). The relayed tier tunnels the identical handshake through the relay, which forwards
/// only ciphertext it cannot read.
///
/// # Errors
/// Same as [`connect`]: [`NatError::NoMethodsEnabled`] if no tier could be composed, else
/// [`NatError::AllMethodsFailed`] with each composed tier's reason in attempt order.
pub async
/// Assemble the [`TraversalMethod`] trait objects for the full ladder from the enabled tiers in
/// `config` whose runtime inputs are present in `runtime`. The strategy orders them by
/// [`TraversalKind::rank`], so the order they are pushed here is irrelevant. A tier missing its
/// runtime inputs is silently omitted — `connect` only ever attempts a tier it can actually run.