Commit Graph
2 Commits
Author SHA1 Message Date
Talon 0dad40c9d7 Reconnect on a real handover instead of waiting for a dead path
Build and test / test (macos-latest) (push) Waiting to run
Build and test / test (ubuntu-24.04) (push) Waiting to run
Build and test / test (windows-latest) (push) Waiting to run
Build and test / apple-client (push) Waiting to run
A Wi-Fi to cellular switch left the session visibly dropping: the media transport rebound
itself within a few seconds, but nothing noticed the blackholed control connection until an
unanswered keepalive proved it, and the teardown that followed announced a lost connection and
waited another second before dialling again.

Watch the system path on iOS and fail the control connection the moment the carrying interface
changes, which is the only path change TCP cannot survive. Roaming between access points and a
link that is merely unusable for a while keep the same interface and the same source address,
so ControlPathWatcher reports neither; an unsatisfied path holds the last signature rather than
reporting, so a reconnect is never started into a route that cannot carry it. Tighten the
keepalive window on the phone as the backstop for what the monitor cannot see, run the first
reconnect attempt immediately, and defer the lost-connection announcement until an attempt has
actually failed, so a sub-second handover is silent and only a real outage is announced.

A control reconnect still re-authenticates and rejoins: the media keys come from the TLS
exporter of the connection that was lost, so seamless handover needs control-plane session
resumption rather than a faster reconnect.
2026-09-25 17:20:52 +02:00
Talon f3ac779bf4 Survive changing networks and deepen the receive buffer
Build and test / test (macos-latest) (push) Waiting to run
Build and test / test (ubuntu-24.04) (push) Waiting to run
Build and test / test (windows-latest) (push) Waiting to run
Build and test / apple-client (push) Waiting to run
Media died silently whenever a client's source address changed. The relay bound a
peer's endpoint once and refused to move it, and the client stopped offering its
binding token after the first bind, so a Wi-Fi/cellular handover stranded the
session in both directions. Add an authenticated Rebind media frame: the binding
token travels in the clear for peer lookup only, and the AEAD tag over header and
token plus the peer's existing replay window are what authorize the move, so a
captured rebind cannot be replayed to redirect someone else's downlink. The client
rebuilds its UDP socket instead of retrying on one still pinned to a vanished
interface.

Nothing judged the control connection live: pings were sent and pongs ignored, so a
blackholed TCP path went unnoticed for minutes while the UI showed a live session.
Treat any server traffic as liveness and fail the connection when it stops, which
drives the existing reconnect.

The receive jitter buffer had lost its depth floor, so a channel without FEC or
DRED played out with no buffer at all and ordinary reordering became concealment.
Restore a one-frame floor, observe every arrival rather than only accepted ones —
a shallow buffer was rejecting the late arrivals that should have deepened it —
and allow playout to hold a frame so depth can follow a degrading link. A stalled
consumer now sheds the oldest queued packet instead of refusing the live talkspurt.

Add a deterministic network-impairment simulation covering bursty loss, jitter,
reordering, duplication, outages and a stalled consumer, a handover test against a
real relay, a replay test for the rebind path, and a blackholed control connection
driven through a freezable TCP proxy.
2026-09-24 19:15:16 +02:00