lx: early rebind and wake nudge for provably dead sessions (sing-box-lx SPEC 041 v2)
The v1 give-up rebind heals a dead 5-tuple only at ~90s, while users probe within the first seconds after device wake. Two new triggers over the same action and shared debounce window: - early: >=3 unanswered initiations against a provably dead session (no live keypair, or last handshake older than RejectAfterTime) rebind at ~15s from the retry branch; a live session keeps byte-for-byte upstream behaviour; - nudge: public Device.RebindIfSessionStale() lets the consumer report a device wake-up and heal stale peers immediately, without traffic demand. The whole mechanism now lives in device/lx_giveup_rebind.go (moved from device.go to keep the upstream file delta minimal); the rebind log line carries the trigger label.
This commit is contained in:
parent
c4e0bcf768
commit
3909adbf87
5 changed files with 444 additions and 60 deletions
|
|
@ -123,17 +123,14 @@ type Device struct {
|
|||
|
||||
ipackets [5]*obfChain
|
||||
|
||||
// lx: SPEC 041 — passive self-heal on handshake give-up. When a peer's
|
||||
// handshake retry cycle exhausts (the give-up branch of
|
||||
// expiredRetransmitHandshake), the device reopens its bind once — with a
|
||||
// fresh ephemeral port when freshPort is set — and immediately
|
||||
// re-initiates. Heals dead per-flow path state (an expired NAT mapping or
|
||||
// a poisoned DPI flow entry) that otherwise pins every retry to the same
|
||||
// dead 5-tuple until a manual reconnect. Zero cost while healthy: no
|
||||
// timers, no goroutines — the trigger is the existing give-up event,
|
||||
// which only fires under traffic demand after ~90s of unanswered
|
||||
// initiations. Enabled by default; sing-box decides freshPort from
|
||||
// whether the user pinned listen_port.
|
||||
// lx: SPEC 041 — passive self-heal state: reopen the bind once (fresh
|
||||
// ephemeral port when freshPort is set) and immediately re-initiate, to
|
||||
// heal dead per-flow path state (an expired NAT mapping or a poisoned DPI
|
||||
// flow entry) that otherwise pins every retry to the same dead 5-tuple
|
||||
// until a manual reconnect. The whole mechanism — three triggers (giveup /
|
||||
// early / nudge) sharing this state and its debounce — lives in
|
||||
// lx_giveup_rebind.go. Enabled by default; sing-box decides freshPort
|
||||
// from whether the user pinned listen_port.
|
||||
giveUpRebind struct {
|
||||
enabled atomic.Bool
|
||||
freshPort atomic.Bool
|
||||
|
|
@ -803,55 +800,6 @@ func (device *Device) BindUpdate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// lx: SPEC 041 — configure the handshake give-up self-heal (see the
|
||||
// giveUpRebind field comment). freshPort must be false when the user pinned
|
||||
// an explicit listen_port: the pinned port is preserved, at the cost of the
|
||||
// rebind not changing the 5-tuple.
|
||||
func (device *Device) SetGiveUpRebind(enabled, freshPort bool) {
|
||||
device.giveUpRebind.enabled.Store(enabled)
|
||||
device.giveUpRebind.freshPort.Store(freshPort)
|
||||
}
|
||||
|
||||
// lx: SPEC 041 — invoked from the give-up branch of
|
||||
// expiredRetransmitHandshake: ~90s of initiations went unanswered, so the
|
||||
// current socket's 5-tuple is proven dead. Reopen the bind (fresh ephemeral
|
||||
// port when allowed) and kick a new handshake cycle immediately. Runs the
|
||||
// heavy part in a goroutine so the timer callback never blocks on
|
||||
// BindUpdate's worker drain. Debounced to one rebind per RekeyAttemptTime
|
||||
// per device (CAS on `last` settles concurrent multi-peer give-ups). On a
|
||||
// down or closed device BindUpdate does not reopen the socket, so a rebind
|
||||
// racing idle-suspend (SPEC 020) or Close degrades to a no-op.
|
||||
func (device *Device) handleHandshakeGiveUp(peer *Peer) {
|
||||
if !device.giveUpRebind.enabled.Load() {
|
||||
return
|
||||
}
|
||||
if device.isClosed() {
|
||||
return
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
last := device.giveUpRebind.last.Load()
|
||||
if now-last < int64(RekeyAttemptTime/time.Second) {
|
||||
return
|
||||
}
|
||||
if !device.giveUpRebind.last.CompareAndSwap(last, now) {
|
||||
return
|
||||
}
|
||||
fresh := device.giveUpRebind.freshPort.Load()
|
||||
go func() {
|
||||
if fresh {
|
||||
device.net.Lock()
|
||||
device.net.port = 0
|
||||
device.net.Unlock()
|
||||
}
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
device.log.Errorf("%v - Failed to rebind after handshake give-up: %v", peer, err)
|
||||
return
|
||||
}
|
||||
device.log.Verbosef("%v - Rebound socket after handshake give-up (fresh port=%v)", peer, fresh)
|
||||
peer.SendHandshakeInitiation(false)
|
||||
}()
|
||||
}
|
||||
|
||||
func (device *Device) BindClose() error {
|
||||
device.net.Lock()
|
||||
err := closeBindLocked(device)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue