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
//! [`RealIpRouteManager`]: the real-transport [`ntk_neighborhood::IpRouteManager`], over the
//! concrete [`ntk_netlink::RealNetlink`] (not generic — `IpRouteManager`'s methods return
//! `BoxFuture`, which needs a provably-`Send` future; `ntk_netlink`'s `async fn`-in-trait
//! methods only prove that for concrete implementors, the same reason `ntk-neighborhood`'s own
//! sealed `InterfaceState` trait exists). Tests use `ntk-neighborhood`'s own
//! [`ntk_neighborhood::FakeIpRouteManager`] directly instead of this type.
//!
//! `add_address`/`remove_address` are real, via [`ntk_netlink::AddressTable`].
//!
//! # `add_neighbor`/`remove_neighbor`: a per-neighbor `/32` on-link route
//! This is the fix for the relay reply-path defect: a node monitoring 2+ NICs installs one
//! `169.254.0.0/16` connected route per NIC (RFC 3927), so the kernel FIB holds several routes
//! to the *identical* prefix at metric 0 and resolves any destination-based lookup in that
//! prefix — including a `TcpServer`-accepted connection's own reply traffic, which never goes
//! through `ntk_rpc::TcpRpcClient::connect_via`'s `SO_BINDTODEVICE` dial-side fix — to whichever
//! NIC's route was installed first, regardless of which NIC the peer is actually reachable on.
//! [`ntk_netlink::RouteTarget::OnLink`] (added alongside this fix) is longest-prefix-match's
//! own escape hatch: a `/32` host route to the *specific* neighbor, via the *specific* device
//! the neighborhood handshake already learned it on, always outranks the ambiguous `/16`.
//!
//! ## Table choice: [`NEIGHBOR_ROUTE_TABLE`], a table of its own — deliberately not
//! [`ntk_netlink::DEFAULT_MAIN_TABLE_ID`]
//! The obvious first choice is the main identity's own table (251): `crate::kernel::routes::
//! RouteInstaller::install_identity` already installs an unconditional
//! [`ntk_netlink::RuleSelector::Any`] rule there, at [`ntk_netlink::DEFAULT_MAIN_RULE_PRIORITY`]
//! — well ahead of the kernel's own implicit main-table rule (priority 32766) — so anything in
//! that table is already consulted before the kernel's ambiguous `/16`s, for free. **Tried
//! first, rejected on evidence**: `tests/mesh.rs`'s own fixture (`tests/netns/mod.rs`'s
//! `NodeReport`) reads back exactly this table and several of its scenarios assert the *exact*
//! set of routes found there (e.g. `chain_of_four_converges_to_exact_multi_hop_routes`'s "must
//! have exactly the 3 converged destinations installed, nothing stale") — confirmed by directly
//! running that suite with the main-table version of this fix: a real, permanent on-link
//! neighbor route is exactly the kind of "extra" entry those assertions correctly reject, since
//! table 251 is that fixture's *only* signal for "what did `RouteInstaller` converge to". Table
//! 251 must stay [`crate::kernel::routes::RouteInstaller`]'s exclusively.
//!
//! So this uses [`NEIGHBOR_ROUTE_TABLE`] — a second, dedicated fixed table, outside both
//! [`ntk_netlink::DEFAULT_MAIN_TABLE_ID`] and [`ntk_netlink::DEFAULT_PEER_TABLE_RANGE`] (so it
//! can never collide with a future real per-peer `fwmark` table allocation from that pool) —
//! with its own unconditional `Any` rule at `NEIGHBOR_RULE_PRIORITY`, installed lazily and
//! idempotently by `ensure_neighbor_rule` the first time [`RealIpRouteManager::add_neighbor`]
//! runs (there is no per-node startup hook this type could use instead — [`RealIpRouteManager`]
//! is constructed as a plain struct literal by several call sites this crate does not own, so
//! it cannot gain a new field to remember "already installed" and must re-check idempotently;
//! see `ensure_neighbor_rule`'s own doc). Priority `10_001` is chosen only to sit clear of
//! [`ntk_netlink::TableAllocator`]'s peer-priority pool (`9_949..10_000` under the default
//! range) and of `251`'s own `10_000` — evaluation order between the two `Any` rules does not
//! matter, since each table only ever holds routes for a disjoint destination range (`10.0.0.0/8`
//! vs `169.254.0.0/16`), so a lookup that misses one falls through with no effect either way.
//!
//! Because [`NEIGHBOR_ROUTE_TABLE`] is deliberately outside [`ntk_netlink::TableAllocator`]'s
//! own ownership range, [`ntk_netlink::cleanup`]'s existing route/rule sweep does **not** see
//! it — verified directly, not assumed: [`cleanup_neighbor_routes`] is this table's explicit
//! crash-recovery counterpart, called by [`crate::node::supervisor::run`] alongside the main
//! `ntk_netlink::cleanup` call.
//!
//! ## Why this is race-free
//! `add_neighbor`/`remove_neighbor` only ever run from inside `ntk_neighborhood::Manager`'s
//! command loop, reacting to a `here_i_am`/`request_arc`/arc-removal *message*, and that loop
//! processes commands strictly one at a time — so two concurrent `add_neighbor` calls racing
//! `ensure_neighbor_rule`'s own check-then-add against each other cannot happen for a single
//! node's one `RealIpRouteManager`.
//!
//! `add_address`'s widened-to-`/16` rationale is unaffected by any of the above — see
//! `linklocal_net`'s own doc.
use BoxFuture;
use ;
use ;
/// RFC 3927 §2.1's link-local block, `169.254.0.0/16` — the prefix length every neighborhood
/// link-local address is installed at (see [`linklocal_net`]'s doc for why).
const LINKLOCAL_PREFIX_LEN: u8 = 16;
/// The dedicated table [`RealIpRouteManager::add_neighbor`]/[`RealIpRouteManager::remove_neighbor`]
/// install into — see this module's doc comment's "Table choice" section for why this exact,
/// separate table rather than the main identity's own. Outside [`ntk_netlink::RT_TABLE_MAIN`]/
/// `RT_TABLE_DEFAULT`/`RT_TABLE_LOCAL`/`RT_TABLE_UNSPEC` (kernel-reserved, `guard_table` would
/// reject it), [`ntk_netlink::DEFAULT_MAIN_TABLE_ID`] (251, `RouteInstaller`'s own), and
/// [`ntk_netlink::DEFAULT_PEER_TABLE_RANGE`] (`200..=250`, reserved for a future real per-peer
/// allocation via [`ntk_netlink::TableAllocator::acquire`]).
pub const NEIGHBOR_ROUTE_TABLE: u32 = 252;
/// [`NEIGHBOR_ROUTE_TABLE`]'s own catch-all rule priority — see this module's doc comment for
/// why this exact value.
const NEIGHBOR_RULE_PRIORITY: u32 = 10_001;
/// The [`Ipv4Net`] this manager installs/removes for a neighborhood link-local address.
///
/// Deliberately **not** [`Ipv4Net::host`] (a `/32`): `addr` here is always drawn from RFC
/// 3927's `169.254.0.0/16` block (`crate::node::lifecycle::linklocal_allocator`), and a `/32`
/// linklocal address is broken on a real kernel — confirmed by a minimal two-namespace veth
/// reproduction outside this codebase: with each side's address installed as `/32`,
/// `sendto(255.255.255.255)` reports success but the kernel never actually delivers the frame
/// (a host route has no on-link broadcast domain to broadcast onto), so neighborhood
/// discovery's UDP broadcast handshake never arrives. Installing the full `/16` — the exact
/// prefix RFC 3927 itself specifies for this block — gives the interface a real on-link
/// broadcast domain and fixes delivery. Widening from `/32` to `/16` does not by itself make
/// two nodes' addresses collide: each is still a distinct address *within* the shared prefix
/// (see `crate::node::lifecycle::linklocal_allocator`'s doc for how that distinctness is
/// arranged) — the two properties (on-link prefix width, address distinctness) are independent
/// and both were broken, which is why fixing only one left discovery red.
///
/// This is the only prefix length this file ever chooses on its own. Every other [`Ipv4Net`]
/// this daemon installs (`crate::kernel::routes`) is a real Netsukuku identity or g-node
/// address/range, whose prefix comes from the topology/NIP model, not from here — those are
/// genuinely host routes (`/32` per identity) or genuine sub-ranges, not an on-link broadcast
/// prefix, so they correctly keep using host/topology-derived prefixes instead of this one.
/// The `/32` on-link [`RouteSpec`] [`RealIpRouteManager::add_neighbor`] installs for
/// `neighbor` via `my_dev` — split out from the trait method so it can be pinned by a plain,
/// unprivileged unit test (below) without a real or fake netlink backend.
/// The [`RouteKey`] [`RealIpRouteManager::remove_neighbor`] removes for `neighbor` — always
/// exactly the key of the [`RouteSpec`] [`neighbor_route`] built for that same address.
/// Idempotently makes sure [`NEIGHBOR_ROUTE_TABLE`]'s catch-all rule exists, installing it on
/// the first call and no-op'ing on every later one. Cannot instead be installed once, eagerly,
/// at node startup (mirroring how `RouteInstaller::install_identity` installs table 251's own
/// rule): [`RealIpRouteManager`] is built as a plain `pub` struct literal by several call sites
/// outside this crate's control, so it cannot gain a "not yet installed" field to make that
/// eager version race-free without breaking every one of them — see this module's doc comment.
/// Checking via [`ntk_netlink::RuleTable::list_rules`] rather than blindly calling `add_rule`
/// and swallowing its error: a real kernel failure surfaces as the generic, unmatchable
/// [`NetlinkError::Netlink`] variant, not the specific [`NetlinkError::AlreadyExists`]
/// [`ntk_netlink::FakeNetlink`] uses for the same condition — there is no reliable way to tell
/// "already there" apart from a genuine failure by matching on that variant alone.
async
/// [`NEIGHBOR_ROUTE_TABLE`]'s explicit crash-recovery counterpart to [`ntk_netlink::cleanup`] —
/// see this module's doc comment for why this table needs one of its own. Called by
/// [`crate::node::supervisor::run`] alongside the main `ntk_netlink::cleanup` sweep.
///
/// # Errors
/// [`NetlinkError`] if a kernel query or mutation fails.
pub async