drop ECN support for this release

This commit is contained in:
JackDoan
2026-07-31 13:38:16 -05:00
parent d3779b6a39
commit 93946faf7a
24 changed files with 148 additions and 1101 deletions
+14 -140
View File
@@ -3,13 +3,11 @@
package udp
import (
"encoding/binary"
"fmt"
"log/slog"
"net"
"net/netip"
"slices"
"syscall"
"testing"
"time"
"unsafe"
@@ -56,39 +54,6 @@ func buildCmsg(level, typ int32, data []byte) []byte {
return buf
}
// TestParseRecvCmsgOuterECNFamily is the RX half of the dual-stack ECN fix:
// parseRecvCmsg must read the outer ECN from whichever family the kernel
// delivered, not from the socket family. On the default `::` dual-stack bind
// a v4 peer's outer ECN arrives as an IP_TOS cmsg, which the old socket-family
// gate ignored entirely.
func TestParseRecvCmsgOuterECNFamily(t *testing.T) {
tc := make([]byte, 4)
binary.NativeEndian.PutUint32(tc, 0x02)
cases := []struct {
name string
ctrl []byte
want byte
}{
{"ip_tos_ce", buildCmsg(int32(unix.IPPROTO_IP), int32(unix.IP_TOS), []byte{0x03}), 0x03},
{"ip_tos_ect0", buildCmsg(int32(unix.IPPROTO_IP), int32(unix.IP_TOS), []byte{0x02}), 0x02},
{"ipv6_tclass_ect0", buildCmsg(int32(unix.IPPROTO_IPV6), int32(unix.IPV6_TCLASS), tc), 0x02},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
hdr := &msghdr{Control: &c.ctrl[0]}
setMsgControllen(hdr, len(c.ctrl))
gso, ecn := parseRecvCmsg(hdr, false, true)
if gso != 0 {
t.Errorf("gso = %d, want 0 (no UDP_GRO cmsg present)", gso)
}
if ecn != c.want {
t.Errorf("ecn = 0x%02x, want 0x%02x", ecn, c.want)
}
})
}
}
func testLogger() *slog.Logger {
return slog.New(slog.DiscardHandler)
}
@@ -125,7 +90,7 @@ func TestWriteBatchBadFamilyDeliversOthers(t *testing.T) {
bufs := [][]byte{[]byte("AAA"), []byte("BBB"), []byte("CCC")}
addrs := []netip.AddrPort{good, bad, good}
n, err := sender.WriteBatch(bufs, addrs, nil)
n, err := sender.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch returned error, want nil (bad dest should be isolated): %v", err)
}
@@ -151,93 +116,6 @@ func TestWriteBatchBadFamilyDeliversOthers(t *testing.T) {
}
}
// TestWriteBatchOuterTOSToV4Mapped is the TX half of the dual-stack ECN fix,
// verified against a live kernel: WriteBatch on the default `::` dual-stack
// socket, sending to a v4-mapped destination, must stamp the outer ECN via an
// IP_TOS cmsg (not IPV6_TCLASS, which the kernel's v4 path ignores) so a v4
// receiver actually sees it.
func TestWriteBatchOuterTOSToV4Mapped(t *testing.T) {
rx, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
if err != nil {
t.Skipf("cannot open v4 receiver (sandbox?): %v", err)
}
defer rx.Close()
rxPort := rx.LocalAddr().(*net.UDPAddr).Port
// Ask the kernel to deliver the received outer TOS as ancillary data.
rxRaw, err := rx.SyscallConn()
if err != nil {
t.Fatalf("SyscallConn: %v", err)
}
var soErr error
if err := rxRaw.Control(func(fd uintptr) {
soErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
}); err != nil || soErr != nil {
t.Skipf("cannot enable IP_RECVTOS (sandbox/kernel?): ctrl=%v so=%v", err, soErr)
}
c, err := NewListener(testLogger(), netip.IPv6Unspecified(), 0, false, 1)
if err != nil {
t.Skipf("cannot open dual-stack sender (sandbox?): %v", err)
}
defer c.Close()
sender := c.(*StdConn)
if sender.isV4 {
t.Skipf("sender came up v4-only; need a dual-stack v6 socket for this test")
}
// v4-mapped-in-v6 destination: routed through the kernel's IPv4 path.
dst := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), uint16(rxPort))
const wantECN = byte(0x02) // ECT(0)
if _, err := sender.WriteBatch([][]byte{[]byte("tos-probe")}, []netip.AddrPort{dst}, []byte{wantECN}); err != nil {
t.Fatalf("WriteBatch: %v", err)
}
// Read the datagram plus its ancillary TOS.
rx.SetReadDeadline(time.Now().Add(3 * time.Second))
payload := make([]byte, 128)
oob := make([]byte, 512)
var n, oobn int
var rerr error
if err := rxRaw.Read(func(fd uintptr) bool {
n, oobn, _, _, rerr = unix.Recvmsg(int(fd), payload, oob, 0)
if rerr == syscall.EAGAIN || rerr == syscall.EWOULDBLOCK {
return false
}
return true
}); err != nil {
t.Fatalf("waiting for datagram failed (no delivery?): %v", err)
}
if rerr != nil {
t.Fatalf("Recvmsg: %v", rerr)
}
if string(payload[:n]) != "tos-probe" {
t.Fatalf("payload = %q, want %q", string(payload[:n]), "tos-probe")
}
cmsgs, err := unix.ParseSocketControlMessage(oob[:oobn])
if err != nil {
t.Fatalf("ParseSocketControlMessage: %v", err)
}
found := false
var gotTOS byte
for _, m := range cmsgs {
if m.Header.Level == unix.IPPROTO_IP && m.Header.Type == unix.IP_TOS && len(m.Data) >= 1 {
found = true
gotTOS = m.Data[0]
}
}
if !found {
t.Fatalf("no IP_TOS cmsg delivered to v4 receiver — outer ECN did not land (%d cmsgs)", len(cmsgs))
}
if gotTOS&0x03 != wantECN {
t.Errorf("received outer TOS = 0x%02x, want low-2-bits = 0x%02x", gotTOS, wantECN)
} else {
t.Logf("verified: v4 receiver saw outer TOS 0x%02x (ECN=0x%02x) from dual-stack sender", gotTOS, gotTOS&0x03)
}
}
// TestWriteBatchUnreachableDestDeliversOthers is the kernel-rejection twin of
// TestWriteBatchBadFamilyDeliversOthers. A destination the kernel refuses outright (240.0.0.0/4 is reserved, so
// the send returns EINVAL) fails its sendmmsg entry; WriteBatch must drop only that entry and still deliver
@@ -264,7 +142,7 @@ func TestWriteBatchUnreachableDestDeliversOthers(t *testing.T) {
addrs := []netip.AddrPort{good, good, bad, good, good}
// The bad destination is reported, but only after every other packet has been attempted.
if _, err := sender.WriteBatch(bufs, addrs, nil); err == nil {
if _, err := sender.WriteBatch(bufs, addrs); err == nil {
t.Log("WriteBatch returned nil; kernel accepted the reserved address, delivery assertions still apply")
}
@@ -317,11 +195,11 @@ func TestParseRecvCmsgCorruptLenNoPanic(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
hdr := &msghdr{Control: &c.ctrl[0]}
setMsgControllen(hdr, len(c.ctrl))
gso, ecn := parseRecvCmsg(hdr, true, true)
gso := parseRecvCmsg(hdr)
// The valid leading UDP_GRO cmsg (payload 0) must still parse;
// the corrupt trailer just ends the walk.
if gso != 0 || ecn != 0 {
t.Errorf("parseRecvCmsg = (%d, %#x), want (0, 0)", gso, ecn)
if gso != 0 {
t.Errorf("parseRecvCmsg = %d, want 0", gso)
}
})
}
@@ -367,16 +245,12 @@ func TestDeliverSegments(t *testing.T) {
}
var got [][]byte
meta := RxMeta{OuterECN: 0x2}
deliverSegments(func(a netip.AddrPort, seg []byte, m RxMeta) {
deliverSegments(func(a netip.AddrPort, seg []byte) {
if a != from {
t.Errorf("from = %v, want %v", a, from)
}
if m != meta {
t.Errorf("meta = %+v, want %+v", m, meta)
}
got = append(got, seg)
}, from, c.payload, c.segSize, meta)
}, from, c.payload, c.segSize)
if len(got) != len(wantLens) {
t.Fatalf("delivered %d segments, want %d", len(got), len(wantLens))
@@ -481,7 +355,7 @@ func TestWriteBatchPartialSendRewind(t *testing.T) {
return accept, nil
}
written, err := w.WriteBatch(bufs, addrs, nil)
written, err := w.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}
@@ -534,7 +408,7 @@ func TestWriteBatchSkipUnroutableRunAccounting(t *testing.T) {
return accept, nil
}
written, err := w.WriteBatch(bufs, addrs, nil)
written, err := w.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}
@@ -591,7 +465,7 @@ func TestWriteBatchMidChunkRejectResumes(t *testing.T) {
}
}
written, err := w.WriteBatch(bufs, addrs, nil)
written, err := w.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}
@@ -648,7 +522,7 @@ func TestWriteBatchMidChunkEIODisablesGSOWithoutDup(t *testing.T) {
}
}
written, err := w.WriteBatch(bufs, addrs, nil)
written, err := w.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}
@@ -676,7 +550,7 @@ func TestWriteBatchZeroProgress(t *testing.T) {
w.sendFn = func(start, n int) (int, error) { return 0, nil }
bufs := [][]byte{make([]byte, 100)}
addrs := []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:4242")}
if _, err := w.WriteBatch(bufs, addrs, nil); err == nil {
if _, err := w.WriteBatch(bufs, addrs); err == nil {
t.Fatal("WriteBatch = nil error on zero progress, want error")
}
}
@@ -702,7 +576,7 @@ func TestWriteBatchEIODisablesGSOAndReplays(t *testing.T) {
return n, nil
}
written, err := w.WriteBatch(bufs, addrs, nil)
written, err := w.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}
@@ -773,7 +647,7 @@ func TestGSOEngagesOnLoopback(t *testing.T) {
addrs[i] = dst
}
written, err := sc.WriteBatch(bufs, addrs, nil)
written, err := sc.WriteBatch(bufs, addrs)
if err != nil {
t.Fatalf("WriteBatch: %v", err)
}