From 8044b40c8248b81813d039d98a14f77583aa2676 Mon Sep 17 00:00:00 2001 From: JackDoan Date: Wed, 29 Jul 2026 17:17:56 -0500 Subject: [PATCH] udp: degrade outer-ECN RX per-family on dual-stack sockets 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 Claude-Session: https://claude.ai/code/session_014ugV2edVqoz3tBvq9J6yWp --- udp/udp_linux.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/udp/udp_linux.go b/udp/udp_linux.go index 95d3e8b5..854aa03c 100644 --- a/udp/udp_linux.go +++ b/udp/udp_linux.go @@ -133,24 +133,24 @@ func (u *StdConn) prepareGRO() { // field of each arriving datagram is delivered as ancillary data alongside // the payload. ListenOut reads it via parseRecvCmsg and passes the codepoint // through the EncReader for RFC 6040 combine on the decap side. Best-effort: -// we keep going on failure. Only the socket's own family gates support; on a -// dual-stack v6 socket a failed IPv4 probe just degrades v4 peers to Not-ECT -// (could be a v6-specific bind). +// we keep going on failure, and each family degrades independently — a peer +// whose family's probe failed just delivers no cmsg and lands as Not-ECT. +// Only a failure of every family the socket speaks turns the parsing off. func (u *StdConn) prepareECNRecv() { v4err := unix.SetsockoptInt(u.sysFd, unix.IPPROTO_IP, unix.IP_RECVTOS, 1) - err := v4err + var v6err error if !u.isV4 { - err = unix.SetsockoptInt(u.sysFd, unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1) - if err != nil { - err = errors.Join(v4err, err) - } else if v4err != nil { - u.l.Debug("udp: outer-ECN RX degraded", "reason", "kernel rejected probe on IPv4", "error", v4err) - } + v6err = unix.SetsockoptInt(u.sysFd, unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1) } - if err != nil { - u.l.Info("udp: outer-ECN RX disabled", "reason", "kernel rejected probe", "error", err) + switch { + case v4err != nil && (u.isV4 || v6err != nil): + u.l.Info("udp: outer-ECN RX disabled", "reason", "kernel rejected probe", "error", errors.Join(v4err, v6err)) recordCapability("udp.ecn_rx.enabled", false) return + case v4err != nil: + u.l.Debug("udp: outer-ECN RX degraded", "reason", "kernel rejected probe on IPv4", "error", v4err) + case v6err != nil: + u.l.Debug("udp: outer-ECN RX degraded", "reason", "kernel rejected probe on IPv6", "error", v6err) } u.ecnRecvSupported = true u.l.Info("udp: outer-ECN RX enabled")