lx: rebind socket on handshake give-up (sing-box-lx SPEC 041 self-heal)

After ~90s of unanswered handshake initiations (the give-up branch of
expiredRetransmitHandshake) the socket's 5-tuple is proven dead (expired
NAT mapping / poisoned DPI flow entry after device sleep) and upstream
retries into it forever; only a manual reconnect healed the peer.

Reopen the bind once per give-up cycle (fresh ephemeral port unless the
user pinned listen_port), then re-initiate immediately. Debounced via CAS;
no timers or goroutines while healthy; a rebind racing Down()/Close()
degrades to a no-op inside BindUpdate. Red/green e2e + unit tests.
This commit is contained in:
Leadaxe 2026-07-31 23:49:57 +03:00
parent 37bc7b9f55
commit c4e0bcf768
4 changed files with 330 additions and 0 deletions

View file

@ -99,6 +99,14 @@ func expiredRetransmitHandshake(peer *Peer) {
peer.timers.zeroKeyMaterial.Mod(RejectAfterTime * 3)
}
peer.noteSessionHandshakeStopped()
/* lx: SPEC 041 the exhausted cycle just proved the current socket's
* 5-tuple dead (90s of initiations, zero replies). Rebind once and
* re-initiate, so a stale NAT mapping / poisoned DPI flow entry cannot
* pin this peer to a dead socket until a manual reconnect. Runs after
* the session-state notification so a consumer sees "handshake stopped"
* before the socket is recreated. */
peer.device.handleHandshakeGiveUp(peer)
} else {
peer.timers.handshakeAttempts.Add(1)
peer.device.log.Verbosef("%s - Handshake did not complete after %d seconds, retrying (try %d)", peer, int(RekeyTimeout.Seconds()), peer.timers.handshakeAttempts.Load()+1)