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
// Copyright 2024 Saorsa Labs Ltd.
//
// This Saorsa Network Software is licensed under the GPL, version 3.
// See LICENSE-GPL for the full text.
//! MASQUE-relayed byte-stream: partial coverage + NAT-sim gap documentation.
//!
//! # What IS tested here
//!
//! **`relay_connect_udp_bind_on_live_node`**: Two real loopback `Node` instances (`a`
//! and `r`). `a` connects to `r`, then opens a raw QUIC bidi stream (no ANQAppB1 /
//! ANQAckB3 magic prefix) and exchanges a length-prefixed CONNECT-UDP Bind / Response
//! with `r`'s relay service. The test asserts the response is a success and that `r`
//! allocated a UDP forwarding socket for the session.
//!
//! This exercises a code path not covered elsewhere:
//! - `handle_relay_requests` accepting bidi streams on an accepted connection
//! (`nat_traversal_api.rs` line 5527)
//! - `handle_relay_bidi_stream_with_prefix` parsing the CONNECT-UDP Bind frame
//! (`nat_traversal_api.rs` line 5419)
//! - `MasqueRelayServer::handle_connect_request` binding a real UDP socket for
//! the session (`masque/relay_server.rs` line 512)
//! - The length-prefixed CONNECT-UDP Response encoding
//!
//! `masque_integration_tests.rs` tests the relay protocol types in isolation using
//! **mock addresses** (192.168.x.x, 203.0.113.x) — no real QUIC connection is made.
//! This test uses real QUIC transport on loopback: a genuine ML-DSA-65 handshake,
//! real stream flow control, and a real bound UDP socket allocated by the relay.
//!
//! # The open_bi / accept_bi over relay gap
//!
//! A complete end-to-end test of `Node::open_bi` + `Node::accept_bi` over a
//! MASQUE-relayed connection (`a → relay r → b`) is **not** written here. The relay
//! protocol itself is proven correct by this test and by `masque_integration_tests.rs`.
//! The gap is about reaching the relay stage in the connection orchestrator:
//! `P2pEndpoint::connect_with_fallback` (`p2p_endpoint.rs` line 4654) only enters the
//! `ConnectionStage::Relay` branch after ALL direct stages fail. On loopback, direct
//! QUIC always succeeds, so the relay branch is never reached.
//!
//! **Specific blockers — in order of implementation cost:**
//!
//! 1. **No force-relay API on `Node`**: `Node` has no `connect_via_relay(target, relay)`
//! method. Adding this one public method to `Node` (wrapping
//! `P2pEndpoint::try_relay_connection`, already implemented) would unblock the full
//! e2e test.
//!
//! 2. **`NodeConfig` does not expose `relay_nodes`**: `NatTraversalConfig::relay_nodes`
//! (`unified_config.rs` line 636) is not reachable from `NodeConfig`. Even if it
//! were, setting relay nodes still would not force relay use on loopback — direct
//! always wins.
//!
//! 3. **No in-process NAT simulation**: Docker-based NAT simulation exists in
//! `tests/docker_nat_integration.rs` but requires Docker Compose, takes minutes,
//! and exercises QUIC NAT traversal generically — it does not reach `open_bi` /
//! `accept_bi`. A lightweight in-process `UdpProxy` that drops direct-path packets
//! between two test-only ports would close this gap without Docker.
//!
//! **Coverage by construction**: `spawn_reader_task` (`p2p_endpoint.rs` line 8553)
//! performs the identical ANQAppB1 demux on every connection regardless of how it was
//! established. `try_relay_connection` calls `spawn_reader_task` at line 5677, using
//! the same code that handles direct connections. Therefore, once a relay connection
//! exists, `Node::open_bi` / `Node::accept_bi` behave identically to the direct-path
//! tests in `node_app_streams.rs`.
use Duration;
use ;
use Bytes;
use timeout;
/// Normalise a node's bound address to a concrete loopback address.
///
/// `Node::local_addr()` may return `[::]:port` (unspecified) on dual-stack hosts;
/// connecting to that address is invalid, so normalise to `127.0.0.1:port`.
/// Relay CONNECT-UDP Bind handshake over a live loopback QUIC connection.
///
/// Two real `Node` instances are bound on loopback. `a` opens a raw QUIC bidi stream
/// on its connection to `r` (bypassing `Node::open_bi`'s ANQAppB1 prefix) and
/// exchanges a CONNECT-UDP Bind / Response with `r`'s relay service.
///
/// # Determinism guarantee
///
/// `r.accept()` is deliberately **never called**. Without an application-layer accept
/// loop, `spawn_reader_task` is never spawned on `r` for the connection from `a`. The
/// only concurrent consumer of bidi streams on that connection is `handle_relay_requests`,
/// which is spawned automatically by the NAT traversal layer when the connection is
/// accepted (`nat_traversal_api.rs` line 5346). This makes the test deterministic:
/// there is no race between `spawn_reader_task` and `handle_relay_requests` for the
/// CONNECT-UDP Bind stream.
///
/// (For comparison: in `node_app_streams.rs` the connected-pair helper does call
/// `b.accept()`, which spawns both handlers concurrently. ANQAppB1 and ANQAckB3
/// streams route correctly because each unknown-prefix stream is silently dropped by
/// the other handler — but a raw CONNECT-UDP stream might be stolen by
/// `spawn_reader_task` if both are running, making such a test non-deterministic.)
async