The tunnel's real bottleneck queue - the UDP receive buffer feeding the
decrypt loop - is invisible to every kernel AQM, so under overload it
regulates ECN-capable flows with tail-drop loss like it's 1993. Sample
SK_MEMINFO once per recvmmsg batch (tunnels.ecn_mark_threshold, fraction
of rcvbuf, 0=off) and treat depth beyond the threshold as an outer CE:
the existing RFC 6040 fold then CE-marks ECT inner packets and senders
back off without loss.
A partial success left the remaining entries' iovecs, sockaddrs, and
cmsgs fully intact, then threw them away and replanned the remainder
from bufs -- doubling the packing work exactly when the socket is
congested. Give sendFn a start offset so the drain resumes the same
prepared array at the first unsent entry, and skip a kernel-rejected
entry in place the same way. Only the GSO-disable path still replans,
since its entries change shape; it now rewinds precisely to the failed
run instead of the whole chunk, so entries already sent are never
duplicated.
New scripted tests pin the two paths that didn't exist before: a mid-
chunk rejected entry (drop it, resume the rest, start offsets advance)
and a mid-chunk EIO (GSO off, replay only the failed run, no dup of
already-sent packets).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
Behavior untouched; each marker records a known gap and the intended
fix so the next visit doesn't rediscover it: transient zero-sent
sendmmsg errors drop a whole run; the non-vnet Poll queue lacks the
post-wake drain loop; recvmmsg controllen resets touch every entry;
cached handshake packets flush one syscall each; the routines clamp in
activate() would blackhole surplus REUSEPORT sockets if it ever became
reachable; darwin WriteBatch burst-drops on EWOULDBLOCK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
- multi_coalesce/batch: the ordering contract now states what Flush
actually guarantees -- per-flow DATA order -- and names the two
shapes later data may legally overtake (pure ACKs by design, and
unparseable in-flow shapes as an accepted tradeoff).
- validVnetHdr claimed DATA_VALID makes the stack skip L4 checksum
verification; the tun write path ignores that bit entirely. What the
header buys is the absence of NEEDS_CSUM.
- tun_darwin Write said "only valid for single threaded use"; it is
concurrency-safe and concurrent callers exist.
- udp_coalesce eviction comment said "Seal it" but never sets sealed.
- recordCapability: note the gauges are process-global while the state
is per-socket (last writer wins).
- drop a stale tunReadBufSize reference.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
Every non-coalesceable in-flow packet evicted the flow's open slot, so
a bidirectional connection's inbound data run was broken by each peer
ACK interleaved into it, largely defeating coalescing on concurrent
upload+download. A bare acknowledgment (zero payload, nothing beyond
ACK|PSH|ECE) carries no ordering obligation toward the flow's data --
delivered late it is just a stale ACK the receiver ignores -- so it
can ride the lane as a passthrough without the evict, same as kernel
GRO, which doesn't flush held data on pure ACKs. SYN/FIN/RST/CWR keep
sealing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
A slot that stays single-segment is byte-identical to the packet it
was seeded from, but flushSlot re-emitted it via WriteGSO with a
seeded pseudo-sum, forcing the kernel to software-checksum up to
~1400B that arrived with a perfectly valid checksum. Keep the borrowed
seed packet on the slot (valid until Flush per the Commit contract)
and emit it through the plain DATA_VALID path when numSeg is still 1
at flush time. appendPayload and mergeSlots only touch hdrBuf once
numSeg >= 2, so the raw bytes are pristine whenever the fast path
fires. This is every non-coalesced TCP/UDP packet: request/response
flows, many-flow fan-in, and each run's leftover tail.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
sendInsideEncrypt ran connectionManager.Out for every segment -- up to
~45 extra atomic stores per TSO superpacket, all inside writeLock when
boring crypto serializes encryption. One mark in sendInsideMessage
covers the whole superpacket on both the direct and relay paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
A failed IPV6_RECVTCLASS probe disabled ECN RX entirely, taking down
working IPv4 (v4-mapped) delivery with it, while the opposite failure
already only degraded. Treat both directions the same: each family
degrades to Not-ECT independently, and only a full-family failure
turns the cmsg parsing off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
decodeRead failures were silently swallowed; a kernel emitting an
unnegotiated GSO type would blackhole all tun traffic with nothing in
the logs. Debug-gated per the usual idiom so the happy path pays
nothing, which means plumbing the logger down through the offload
queueset.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
gsoTypeFromProto returns GSO_NONE when the IP version nibble is
neither 4 nor 6, so a multi-fragment superpacket went out as one
silent jumbo GSO_NONE packet -- exactly the silent mis-emission the
geometry checks promise not to allow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp
The return lived inside a log-level-gated else-if, so with debug
logging off, control fell out of both switches. Nothing follows the
switch today, making it a silent drop by luck; any future code added
after the switch would have run for oversized Test requests only when
debug logging was disabled. Make the drop a guard clause.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp