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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
//! NAT traversal — reflex-address discovery, NAT-type classification,
//! hole-punch rendezvous, and (feature-gated) UPnP / NAT-PMP / PCP
//! port mapping.
//!
//! **Framing.** NAT traversal in this codebase is a
//! **latency / throughput optimization**, not a correctness
//! requirement. Connectivity between two NATed peers already works
//! via routed handshakes + relay forwarding — every message reaches
//! its destination regardless of NAT type. What this module adds is
//! a shorter path for the cases where a direct punch is feasible,
//! reducing the per-packet relay tax and the load concentrated on
//! topological relays.
//!
//! A `NatType::Symmetric` classification or a `PunchFailed` outcome
//! is **not** a connectivity failure — it just means traffic keeps
//! riding the relay. The design doc
//! (`docs/NAT_TRAVERSAL_PLAN.md`) treats this framing as
//! load-bearing; docstrings added here must not imply that any
//! NAT-traversal primitive is required for peers behind NAT to
//! talk to each other.
//!
//! # Module layout
//!
//! - [`reflex`] — reflex probe subprotocol handler + client.
//! - [`classify`] — `NatType` classification state machine.
//! - [`rendezvous`] — hole-punch coordinator subprotocol.
//! - [`config`] — [`TraversalConfig`] (probe cadence, timeouts, …).
//! - `portmap` — UPnP / NAT-PMP / PCP client (gated behind
//! the `port-mapping` cargo feature; lands in stage 4 of the plan).
//!
//! # Staging
//!
//! Implemented incrementally per `docs/NAT_TRAVERSAL_PLAN.md`:
//!
//! | Stage | Surface | Status |
//! |-------|--------------------------------------------|-------------------|
//! | 0 | Module scaffolding + feature gate | **done** |
//! | 1 | Reflex probe subprotocol | **done** |
//! | 2 | NAT type classification + `reflex_addr` | **done** |
//! | 3 | Hole-punch rendezvous (coordinator + ack + keep-alive train) | **done** |
//! | 4a | Reflex override (config + runtime setters) | **done** |
//! | 4b | UPnP / NAT-PMP / PCP port-mapping client | deferred (needs `igd-next` + `rust-natpmp` deps + real-router testing; the `set_reflex_override` runtime setter in stage 4a is the hook point) |
//! | 5 | SDK + NAPI + PyO3 + Go binding surface | **done** |
//!
//! Every stage is independently shippable. Earlier stages provide
//! observability (`nat_type`, `reflex_addr`) without the later
//! stages having landed; later stages lift performance without
//! changing the correctness contract.
// Re-exports for the stable sub-module surface. Kept narrow on
// purpose — each sub-module owns the bulk of its public types
// and users import them from their origin rather than the root.
pub use TraversalConfig;
// =========================================================================
// Traversal stats
// =========================================================================
use ;
use Arc;
use ArcSwapOption;
/// Counters tracking traversal decisions + outcomes. Exposed via
/// [`crate::adapter::net::MeshNode::traversal_stats`]. Every
/// counter is monotonic; resetting isn't supported because the
/// values are only meaningful cumulatively.
///
/// The three punch/fallback counters partition all
/// `connect_direct` outcomes:
///
/// - **`punches_attempted`** — the coordinator mediated a
/// punch: the `PunchRequest` went on the wire and the
/// `PunchIntroduce` came back. Fast-fails before that point
/// (coordinator unreachable, socket send failed) do NOT
/// increment — the counter reflects *actual punch activity*,
/// not just matrix decisions. Bumped whether the subsequent
/// ack / direct handshake ultimately succeeds or falls back.
/// - **`relay_fallbacks`** — connection ended up on the routed-
/// handshake path: `SkipPunch` (Symmetric-×-Symmetric), a
/// failed-and-fallen-back punch, or a failed-and-fallen-back
/// `Direct` attempt. Only incremented after the routed
/// handshake *itself* lands. Successful direct connects don't
/// contribute, and failed-and-ALSO-failed fallback attempts
/// don't either — so the counter stays a real "we're on the
/// relay right now" signal operators can use.
/// - **`punches_succeeded`** — the punch completed within the
/// deadline and produced a direct session. Always `≤
/// punches_attempted`; the difference is the punch-failure
/// rate.
///
/// Plus three port-mapping fields (stage 4b): `port_mapping_active`
/// plus a monotonic renewal counter and the current external
/// `SocketAddr`. See `docs/PORT_MAPPING_PLAN.md` §8 for the
/// derivation.
///
/// Read via [`TraversalStats::snapshot`] for a consistent
/// point-in-time view.
/// Consistent point-in-time view of the [`TraversalStats`]
/// counters.
// =========================================================================
// Error surface
// =========================================================================
/// Typed failures from the NAT-traversal subsystem. Matches the
/// vocabulary locked in `docs/NAT_TRAVERSAL_PLAN.md` stage 5 — each
/// variant maps to a stable `kind` string the SDK bindings expose
/// to callers.
///
/// **Framing reminder.** Every variant here describes the failure
/// of an *optimization*, not a connectivity failure. A caller that
/// receives `TraversalError` can always proceed via routed-handshake
/// — the traversal path just didn't pan out.
// =========================================================================
// Subprotocol ID assignment
// =========================================================================
//
// The `0x0D00` block is the first unused range after the existing
// subprotocol allocations (`0x0400` causal, `0x0500` migration,
// `0x0A00` channel membership, `0x0B00` stream window,
// `0x0C00` capability announcement). Reserved for NAT-traversal
// primitives; ids consumed here:
//
// 0x0D00 — reflex probe (stage 1)
// 0x0D01 — rendezvous (stage 3)
// 0x0D02 — reserved for port-mapping metadata (stage 4, optional)
//
// Future traversal primitives take `0x0D0x` ids sequentially.
/// Subprotocol ID for the reflex-probe request/response exchange.
///
/// Any peer that receives a `SUBPROTOCOL_REFLEX` request replies with
/// the source `ip:port` it observed on the request's UDP envelope.
/// Two or more probes to different peers are sufficient to detect
/// symmetric NAT (the observed source port differs per destination).
///
/// See [`reflex`] for the handler / client implementation.
pub const SUBPROTOCOL_REFLEX: u16 = 0x0D00;
/// Subprotocol ID for hole-punch rendezvous coordination.
///
/// Carries the three-message dance (`PunchRequest` →
/// `PunchIntroduce` × 2 → `PunchAck`) that synchronizes a
/// simultaneous open between two NATed peers, mediated by a
/// mutually-connected relay.
///
/// See [`rendezvous`] for the state machine.
pub const SUBPROTOCOL_RENDEZVOUS: u16 = 0x0D01;