udp_linux_fixes_test.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. //go:build linux && !android && !e2e_testing
  2. package udp
  3. import (
  4. "encoding/binary"
  5. "log/slog"
  6. "net"
  7. "net/netip"
  8. "syscall"
  9. "testing"
  10. "time"
  11. "unsafe"
  12. "golang.org/x/sys/unix"
  13. )
  14. // TestGSOMaxSegmentsKernelGate pins the corrected kernel-version gate: the
  15. // 128-segment cap (127 usable) only lands in Linux v6.9 (commit 1382e3b6a350),
  16. // not 5.5. Everything older stays at the conservative 63.
  17. func TestGSOMaxSegmentsKernelGate(t *testing.T) {
  18. cases := []struct {
  19. release string
  20. want int
  21. }{
  22. {"5.4.0", 63},
  23. {"5.5.0-generic", 63}, // the old bug bumped here — it must not now
  24. {"5.15.0", 63},
  25. {"6.1.0", 63},
  26. {"6.8.0-generic", 63},
  27. {"6.9.0", 127},
  28. {"6.10.1-arch1-1", 127},
  29. {"7.0.5-arch1-1", 127},
  30. {"garbage", 63},
  31. {"", 63},
  32. }
  33. for _, c := range cases {
  34. if got := gsoMaxSegments(c.release); got != c.want {
  35. t.Errorf("gsoMaxSegments(%q) = %d, want %d", c.release, got, c.want)
  36. }
  37. }
  38. }
  39. // buildCmsg lays out a single ancillary cmsg (header + data) in a fresh buffer
  40. // the way the kernel would deliver it, so parseRecvCmsg can be exercised
  41. // without a live socket.
  42. func buildCmsg(level, typ int32, data []byte) []byte {
  43. buf := make([]byte, unix.CmsgSpace(len(data)))
  44. h := (*unix.Cmsghdr)(unsafe.Pointer(&buf[0]))
  45. h.Level = level
  46. h.Type = typ
  47. setCmsgLen(h, unix.CmsgLen(len(data)))
  48. copy(buf[unix.CmsgLen(0):], data)
  49. return buf
  50. }
  51. // TestParseRecvCmsgOuterECNFamily is the RX half of the dual-stack ECN fix:
  52. // parseRecvCmsg must read the outer ECN from whichever family the kernel
  53. // delivered, not from the socket family. On the default `::` dual-stack bind
  54. // a v4 peer's outer ECN arrives as an IP_TOS cmsg, which the old socket-family
  55. // gate ignored entirely.
  56. func TestParseRecvCmsgOuterECNFamily(t *testing.T) {
  57. tc := make([]byte, 4)
  58. binary.NativeEndian.PutUint32(tc, 0x02)
  59. cases := []struct {
  60. name string
  61. ctrl []byte
  62. want byte
  63. }{
  64. {"ip_tos_ce", buildCmsg(int32(unix.IPPROTO_IP), int32(unix.IP_TOS), []byte{0x03}), 0x03},
  65. {"ip_tos_ect0", buildCmsg(int32(unix.IPPROTO_IP), int32(unix.IP_TOS), []byte{0x02}), 0x02},
  66. {"ipv6_tclass_ect0", buildCmsg(int32(unix.IPPROTO_IPV6), int32(unix.IPV6_TCLASS), tc), 0x02},
  67. }
  68. for _, c := range cases {
  69. t.Run(c.name, func(t *testing.T) {
  70. hdr := &msghdr{Control: &c.ctrl[0]}
  71. setMsgControllen(hdr, len(c.ctrl))
  72. gso, ecn := parseRecvCmsg(hdr, false, true)
  73. if gso != 0 {
  74. t.Errorf("gso = %d, want 0 (no UDP_GRO cmsg present)", gso)
  75. }
  76. if ecn != c.want {
  77. t.Errorf("ecn = 0x%02x, want 0x%02x", ecn, c.want)
  78. }
  79. })
  80. }
  81. }
  82. func testLogger() *slog.Logger {
  83. return slog.New(slog.DiscardHandler)
  84. }
  85. // TestWriteBatchBadFamilyDeliversOthers is the H3 regression: a batch that
  86. // contains one destination the socket can't reach (an IPv6 remote on a
  87. // v4-bound socket) must still deliver every other packet. Before the fix the
  88. // writeSockaddr error returned early and dropped the whole chunk.
  89. func TestWriteBatchBadFamilyDeliversOthers(t *testing.T) {
  90. rx, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  91. if err != nil {
  92. t.Skipf("cannot open v4 receiver (sandbox?): %v", err)
  93. }
  94. defer rx.Close()
  95. rxPort := rx.LocalAddr().(*net.UDPAddr).Port
  96. // Bind a *non-wildcard* v4 address so Go gives us a genuine AF_INET
  97. // socket. A wildcard v4 bind (0.0.0.0) via network "udp" comes up as a
  98. // dual-stack AF_INET6 socket on Linux, for which a v6 dest is not a bad
  99. // family — which would defeat the point of this test.
  100. c, err := NewListener(testLogger(), netip.MustParseAddr("127.0.0.1"), 0, false, 1)
  101. if err != nil {
  102. t.Skipf("cannot open v4 sender (sandbox?): %v", err)
  103. }
  104. defer c.Close()
  105. sender := c.(*StdConn)
  106. if !sender.isV4 {
  107. t.Fatalf("expected a v4-bound sender socket, got isV4=false")
  108. }
  109. good := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), uint16(rxPort))
  110. bad := netip.MustParseAddrPort("[2001:db8::1]:9999") // genuine v6, unreachable on v4 socket
  111. bufs := [][]byte{[]byte("AAA"), []byte("BBB"), []byte("CCC")}
  112. addrs := []netip.AddrPort{good, bad, good}
  113. if err := sender.WriteBatch(bufs, addrs, nil); err != nil {
  114. t.Fatalf("WriteBatch returned error, want nil (bad dest should be isolated): %v", err)
  115. }
  116. got := map[string]bool{}
  117. rx.SetReadDeadline(time.Now().Add(2 * time.Second))
  118. buf := make([]byte, 64)
  119. for i := 0; i < 2; i++ {
  120. n, _, rerr := rx.ReadFromUDPAddrPort(buf)
  121. if rerr != nil {
  122. t.Fatalf("expected 2 delivered packets, read #%d failed: %v", i+1, rerr)
  123. }
  124. got[string(buf[:n])] = true
  125. }
  126. if !got["AAA"] || !got["CCC"] {
  127. t.Errorf("delivered set = %v, want AAA and CCC both present", got)
  128. }
  129. if got["BBB"] {
  130. t.Errorf("the bad-family packet BBB was somehow delivered")
  131. }
  132. }
  133. // TestWriteBatchOuterTOSToV4Mapped is the TX half of the dual-stack ECN fix,
  134. // verified against a live kernel: WriteBatch on the default `::` dual-stack
  135. // socket, sending to a v4-mapped destination, must stamp the outer ECN via an
  136. // IP_TOS cmsg (not IPV6_TCLASS, which the kernel's v4 path ignores) so a v4
  137. // receiver actually sees it.
  138. func TestWriteBatchOuterTOSToV4Mapped(t *testing.T) {
  139. rx, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  140. if err != nil {
  141. t.Skipf("cannot open v4 receiver (sandbox?): %v", err)
  142. }
  143. defer rx.Close()
  144. rxPort := rx.LocalAddr().(*net.UDPAddr).Port
  145. // Ask the kernel to deliver the received outer TOS as ancillary data.
  146. rxRaw, err := rx.SyscallConn()
  147. if err != nil {
  148. t.Fatalf("SyscallConn: %v", err)
  149. }
  150. var soErr error
  151. if err := rxRaw.Control(func(fd uintptr) {
  152. soErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
  153. }); err != nil || soErr != nil {
  154. t.Skipf("cannot enable IP_RECVTOS (sandbox/kernel?): ctrl=%v so=%v", err, soErr)
  155. }
  156. c, err := NewListener(testLogger(), netip.IPv6Unspecified(), 0, false, 1)
  157. if err != nil {
  158. t.Skipf("cannot open dual-stack sender (sandbox?): %v", err)
  159. }
  160. defer c.Close()
  161. sender := c.(*StdConn)
  162. if sender.isV4 {
  163. t.Skipf("sender came up v4-only; need a dual-stack v6 socket for this test")
  164. }
  165. // v4-mapped-in-v6 destination: routed through the kernel's IPv4 path.
  166. dst := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), uint16(rxPort))
  167. const wantECN = byte(0x02) // ECT(0)
  168. if err := sender.WriteBatch([][]byte{[]byte("tos-probe")}, []netip.AddrPort{dst}, []byte{wantECN}); err != nil {
  169. t.Fatalf("WriteBatch: %v", err)
  170. }
  171. // Read the datagram plus its ancillary TOS.
  172. rx.SetReadDeadline(time.Now().Add(3 * time.Second))
  173. payload := make([]byte, 128)
  174. oob := make([]byte, 512)
  175. var n, oobn int
  176. var rerr error
  177. if err := rxRaw.Read(func(fd uintptr) bool {
  178. n, oobn, _, _, rerr = unix.Recvmsg(int(fd), payload, oob, 0)
  179. if rerr == syscall.EAGAIN || rerr == syscall.EWOULDBLOCK {
  180. return false
  181. }
  182. return true
  183. }); err != nil {
  184. t.Fatalf("waiting for datagram failed (no delivery?): %v", err)
  185. }
  186. if rerr != nil {
  187. t.Fatalf("Recvmsg: %v", rerr)
  188. }
  189. if string(payload[:n]) != "tos-probe" {
  190. t.Fatalf("payload = %q, want %q", string(payload[:n]), "tos-probe")
  191. }
  192. cmsgs, err := unix.ParseSocketControlMessage(oob[:oobn])
  193. if err != nil {
  194. t.Fatalf("ParseSocketControlMessage: %v", err)
  195. }
  196. found := false
  197. var gotTOS byte
  198. for _, m := range cmsgs {
  199. if m.Header.Level == unix.IPPROTO_IP && m.Header.Type == unix.IP_TOS && len(m.Data) >= 1 {
  200. found = true
  201. gotTOS = m.Data[0]
  202. }
  203. }
  204. if !found {
  205. t.Fatalf("no IP_TOS cmsg delivered to v4 receiver — outer ECN did not land (%d cmsgs)", len(cmsgs))
  206. }
  207. if gotTOS&0x03 != wantECN {
  208. t.Errorf("received outer TOS = 0x%02x, want low-2-bits = 0x%02x", gotTOS, wantECN)
  209. } else {
  210. t.Logf("verified: v4 receiver saw outer TOS 0x%02x (ECN=0x%02x) from dual-stack sender", gotTOS, gotTOS&0x03)
  211. }
  212. }