Commit e16b2d1
`stable_lan_port` was correct. Its unit test asserted "same identity must
derive the same port across restarts" and passed. The node churned its port
on every restart anyway, because of how the bind site handled failure:
Err(_preferred_taken) => adapter.listen((UNSPECIFIED, 0)).await
Measured on the Windows node 2026-08-15, immediately after `airc update`:
peer_id e85a5bb3-74f0-4325-87df-7d5f27637063 -> stable port 61539
daemon advertising -> 64463
61539 at that moment -> BINDABLE. Free, nothing
listening, not in any
Windows excluded range.
The new daemon raced the outgoing one during the update handover, lost by
milliseconds, took an ephemeral port, and then held it for the entire process
lifetime — after the stable port freed. Every endpoint peers had cached for
this node pointed at a dead port. M5 observed the other half independently and
without knowing the cause: "your airc INBOUND is unreachable, that is the whole
'everyone waiting on everyone' mystery."
Three defects in that one expression:
1. NO RETRY. The contended moment IS the restart handover — the single most
common moment this code runs. One attempt loses it.
2. SILENT. `_preferred_taken` discards the reason, so a node that has just
become unreachable at every address its peers know reports nothing. That is
the masking fallback this repo denies at the clippy gate, written longhand.
3. NO RECOVERY. Once ephemeral, ephemeral for the whole process.
Now: retry the identity port briefly (5 x 200ms — bounded, because a genuinely
occupied port must surface fast rather than stall startup), and if it still
cannot be had, take an ephemeral one but WARN loudly naming the consequence.
"Reachable at an address nobody has" is precisely the failure that reads as a
quiet room rather than a broken wire, so it must never again be silent.
The regression test reproduces the handover: hold the stable port, release it
mid-window, assert the bind still lands on the stable port. Positive control
run rather than assumed — with the retry reduced to one attempt (the old
behaviour) it FAILS on an ephemeral port (64596 vs 64185); restored, it passes.
This is the mechanism behind "routes decay to zero after every update", which
is what made "always run the latest binary" and "stay connected" fight each
other on this node.
Verified: cargo test -p airc-lib --lib lan:: — 8 passed, 0 failed.
Claude-Session: https://claude.ai/code/session_01Q4NU4VNiELPQfBpCacDZGc
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 2b5bdac commit e16b2d1
1 file changed
Lines changed: 137 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
164 | 157 | | |
165 | 158 | | |
166 | 159 | | |
| |||
194 | 187 | | |
195 | 188 | | |
196 | 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 | + | |
197 | 269 | | |
198 | 270 | | |
199 | 271 | | |
| |||
466 | 538 | | |
467 | 539 | | |
468 | 540 | | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
469 | 596 | | |
470 | 597 | | |
471 | 598 | | |
| |||
0 commit comments