quic: a multi-homed daemon replies from the wrong source address, so every dial silently falls back to ssh #78
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
[claude 2026-09-04] Found while investigating eight long-lived
ssh ... mux d endpointprocesses on a wall (they turned out to be correct
use_pipetransports; the realdefect was two hops upstream).
Symptom
Every QUIC dial from 192.168.0.190 to the daemon on 192.168.0.207 times out. The wall
therefore runs all four of that host's panes over the ssh pipe, permanently, with no
user-visible error.
handoff.zig:547(afterAnnounce) is working as designed here: thewarm dial failed, ssh re-announced the SAME endpoint, so it refuses to re-dial
coordinates it just proved dead and pins the pane to
use_pipefor that pane's life.There is no re-promotion, so a transient-looking failure is permanent.
Root cause
The daemon's box is multi-homed on ONE subnet:
The client dials the wifi address. The daemon's QUIC listener binds the wildcard
0.0.0.0:45464, so when it replies, the kernel picks the route to the client(
ip route get 192.168.0.190->dev enp63s0 src 192.168.0.208) and stamps theethernet source address on the reply. tcpdump on the daemon's box, during a dial:
The client dialled .207 and the answer arrives from .208, so it discards it. Silence is
indistinguishable from a blackholed port, which is exactly the case
handoff.zig'sattach-budget comment describes.
src/server/quic_server.zigcarrieslocal_storage/local_lenper Connection, butnothing in it (or
src/quic.zig) usesrecvmsg/IP_PKTINFO/cmsg— it is plainrecvfrom/sendto, so the listener cannot know which of its addresses a datagram wassent to and cannot pin the reply's source to it.
Reproduction
On a box with two interfaces on the same subnet, where the dialled address is NOT the
one the return route prefers:
Evidence that ruled everything else out
/proc/net/snmpInDatagrams).clients=8ofmax_clients=32— not slot exhaustion.is healthy.
nc -u -lon the client box DOES receive datagrams from that daemon — anunconnected socket accepts any source, which is why a naive reachability test passes
while QUIC fails. Worth knowing for whoever writes the test.
Suggested fix
Set
IP_PKTINFO/IPV6_RECVPKTINFOon the listener socket, read the destinationaddress from the cmsg in
recvmsg, and pass it back as the source insendmsg(thestandard multi-homed QUIC server fix).
local_storage/local_lenis already the rightplace to keep it.
Worth considering separately: the failure mode is a silent, permanent downgrade to a
slower transport. Even with the fix, a pane pinned to
use_pipemight deserve a visiblenotice, since today nothing on screen says the wall gave up on QUIC.
Migrated from git-collab issue
df3935c1e00381688ce26de4f6ac3b724a38a882.Original author: a73x dev@a73x.sh.