udp_linux_offloads_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. //go:build linux && !android && !e2e_testing
  2. package udp
  3. import (
  4. "net"
  5. "net/netip"
  6. "slices"
  7. "testing"
  8. "time"
  9. "golang.org/x/sys/unix"
  10. )
  11. // These tests pin the listen.udp_offloads=false behavior: no GSO/GRO probes,
  12. // no cmsg scratch, and — critically — a still-functional send/receive path.
  13. // The sockaddr name buffers are needed for every sendmmsg entry whether or
  14. // not offloads are on, so prepareWriteMessages must allocate them even when
  15. // it skips the cmsg slab (a nil name buffer panics in writeSockaddr on the
  16. // first WriteBatch).
  17. // TestPrepareWriteMessagesAlwaysAllocatesNames covers all four
  18. // (offloadsEnabled, gsoSupported) combinations: the sockaddr name buffers
  19. // must exist in every one, and the cmsg slab only when both are true.
  20. // gsoSupported=false with offloads enabled is the old-kernel path where the
  21. // UDP_SEGMENT probe fails — not just a config choice.
  22. func TestPrepareWriteMessagesAlwaysAllocatesNames(t *testing.T) {
  23. cases := []struct {
  24. name string
  25. offloads bool
  26. gso bool
  27. }{
  28. {"offloads-off", false, false},
  29. {"offloads-on-probe-failed", true, false},
  30. {"offloads-off-gso-flag-set", false, true},
  31. {"offloads-on", true, true},
  32. }
  33. for _, tc := range cases {
  34. t.Run(tc.name, func(t *testing.T) {
  35. w := &batchWriter{fd: -1, isV4: true, l: testLogger()}
  36. w.gsoSupported = tc.gso
  37. w.prepareWriteMessages(MaxWriteBatch, tc.offloads)
  38. for i := range w.msgs {
  39. if len(w.names[i]) != unix.SizeofSockaddrInet6 {
  40. t.Fatalf("names[%d] len=%d, want %d", i, len(w.names[i]), unix.SizeofSockaddrInet6)
  41. }
  42. if w.msgs[i].Hdr.Name == nil {
  43. t.Fatalf("msgs[%d].Hdr.Name is nil", i)
  44. }
  45. }
  46. wantCmsg := tc.offloads && tc.gso
  47. if (w.cmsg != nil) != wantCmsg {
  48. t.Errorf("cmsg allocated = %v, want %v", w.cmsg != nil, wantCmsg)
  49. }
  50. })
  51. }
  52. }
  53. // TestWriteBatchOffloadsDisabledScripted drives WriteBatch through a
  54. // batchWriter built with offloads disabled and a scripted sendFn: every
  55. // packet must become its own sendmmsg entry (no GSO coalescing to plan),
  56. // packed correctly despite the missing cmsg slab.
  57. func TestWriteBatchOffloadsDisabledScripted(t *testing.T) {
  58. w := &batchWriter{fd: -1, isV4: true, l: testLogger()}
  59. w.prepareWriteMessages(MaxWriteBatch, false)
  60. var entryCounts []int
  61. w.sendFn = func(start, n int) (int, error) {
  62. entryCounts = append(entryCounts, n)
  63. return n, nil
  64. }
  65. // Same destination, equal sizes: prime coalescing bait that must not
  66. // coalesce with offloads off.
  67. dst := netip.MustParseAddrPort("127.0.0.1:4242")
  68. const numPkts = 4
  69. bufs := make([][]byte, numPkts)
  70. addrs := make([]netip.AddrPort, numPkts)
  71. for i := range bufs {
  72. bufs[i] = make([]byte, 1200)
  73. for j := range bufs[i] {
  74. bufs[i][j] = byte(i)
  75. }
  76. addrs[i] = dst
  77. }
  78. written, err := w.WriteBatch(bufs, addrs)
  79. if err != nil {
  80. t.Fatalf("WriteBatch: %v", err)
  81. }
  82. if written != numPkts {
  83. t.Errorf("written = %d, want %d", written, numPkts)
  84. }
  85. if len(entryCounts) != 1 || entryCounts[0] != numPkts {
  86. t.Errorf("sendmmsg entry counts = %v, want [%d]: packets must be one entry each", entryCounts, numPkts)
  87. }
  88. // Each prepared entry must carry exactly its own packet's bytes.
  89. got := capturePrepared(w, 0, numPkts)
  90. if len(got) != numPkts {
  91. t.Fatalf("prepared %d packets, want %d", len(got), numPkts)
  92. }
  93. for i, pkt := range got {
  94. if !slices.Equal(pkt, bufs[i]) {
  95. t.Errorf("entry %d bytes differ from bufs[%d]", i, i)
  96. }
  97. }
  98. }
  99. // TestOffloadsDisabledOnLoopback is the offloads-off smoke test, the mirror
  100. // of TestGSOEngagesOnLoopback: a real socket built with Offloads=false must
  101. // skip the GSO/GRO probes entirely (even on kernels that support them) and
  102. // still deliver a same-destination batch as plain per-packet datagrams.
  103. func TestOffloadsDisabledOnLoopback(t *testing.T) {
  104. rx, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)})
  105. if err != nil {
  106. t.Fatalf("listen rx: %v", err)
  107. }
  108. defer rx.Close()
  109. dst := rx.LocalAddr().(*net.UDPAddr).AddrPort()
  110. udpSettings := Settings{
  111. Listen: netip.MustParseAddrPort("127.0.0.1:0"),
  112. Multi: false,
  113. Batch: 8, // batch > 1 would enable GRO if Offloads did not gate it
  114. Offloads: false,
  115. }
  116. uc, err := NewListener(testLogger(), udpSettings)
  117. if err != nil {
  118. t.Fatalf("NewListener: %v", err)
  119. }
  120. sc := uc.(*StdConn)
  121. defer sc.Close()
  122. if sc.bw.gsoSupported {
  123. t.Error("gsoSupported true with offloads disabled: probe was not skipped")
  124. }
  125. if sc.bw.cmsg != nil {
  126. t.Error("cmsg slab allocated with offloads disabled")
  127. }
  128. if sc.groSupported {
  129. t.Error("groSupported true with offloads disabled: probe was not skipped")
  130. }
  131. var entryCounts []int
  132. real := sc.bw.sendFn
  133. sc.bw.sendFn = func(start, n int) (int, error) {
  134. entryCounts = append(entryCounts, n)
  135. return real(start, n)
  136. }
  137. const numPkts = 8
  138. const pktLen = 1200
  139. bufs := make([][]byte, numPkts)
  140. addrs := make([]netip.AddrPort, numPkts)
  141. for i := range bufs {
  142. bufs[i] = make([]byte, pktLen)
  143. for j := range bufs[i] {
  144. bufs[i][j] = byte(i)
  145. }
  146. addrs[i] = dst
  147. }
  148. written, err := sc.WriteBatch(bufs, addrs)
  149. if err != nil {
  150. t.Fatalf("WriteBatch: %v", err)
  151. }
  152. if written != numPkts {
  153. t.Fatalf("written = %d, want %d", written, numPkts)
  154. }
  155. // One sendmmsg call with one entry per packet: a single-entry call here
  156. // means GSO engaged despite being disabled.
  157. if len(entryCounts) != 1 || entryCounts[0] != numPkts {
  158. t.Fatalf("sendmmsg entry counts = %v, want [%d]", entryCounts, numPkts)
  159. }
  160. _ = rx.SetReadDeadline(time.Now().Add(5 * time.Second))
  161. got := make([]byte, pktLen+1)
  162. for i := range numPkts {
  163. n, _, err := rx.ReadFromUDP(got)
  164. if err != nil {
  165. t.Fatalf("rx read %d: %v", i, err)
  166. }
  167. if n != pktLen {
  168. t.Fatalf("rx read %d: len=%d want %d", i, n, pktLen)
  169. }
  170. for j := range n {
  171. if got[j] != byte(i) {
  172. t.Fatalf("rx read %d: byte %d = %#x, want %#x", i, j, got[j], byte(i))
  173. }
  174. }
  175. }
  176. }
  177. // TestOffloadsDisabledRxDelivers exercises the receive path with GRO gated
  178. // off but batch reads still on: ListenOut must deliver plain datagrams via
  179. // the MTU-sized buffer layout (no cmsg slots).
  180. func TestOffloadsDisabledRxDelivers(t *testing.T) {
  181. udpSettings := Settings{
  182. Listen: netip.MustParseAddrPort("127.0.0.1:0"),
  183. Multi: false,
  184. Batch: 8,
  185. Offloads: false,
  186. }
  187. uc, err := NewListener(testLogger(), udpSettings)
  188. if err != nil {
  189. t.Fatalf("NewListener: %v", err)
  190. }
  191. sc := uc.(*StdConn)
  192. addr, err := sc.LocalAddr()
  193. if err != nil {
  194. t.Fatalf("LocalAddr: %v", err)
  195. }
  196. type rxPkt struct {
  197. from netip.AddrPort
  198. payload []byte
  199. }
  200. rxCh := make(chan rxPkt, 16)
  201. listenDone := make(chan struct{})
  202. go func() {
  203. defer close(listenDone)
  204. _ = sc.ListenOut(func(from netip.AddrPort, payload []byte) {
  205. // payload aliases the shared recv buffer row; copy before handing off.
  206. rxCh <- rxPkt{from, slices.Clone(payload)}
  207. }, func(bufs [][]byte) [][]byte { return bufs })
  208. }()
  209. tx, err := net.DialUDP("udp4", nil, net.UDPAddrFromAddrPort(addr))
  210. if err != nil {
  211. t.Fatalf("dial tx: %v", err)
  212. }
  213. defer tx.Close()
  214. want := [][]byte{
  215. []byte("one"),
  216. make([]byte, 1200),
  217. make([]byte, 9000), // near-MTU datagram must fit the non-GRO buffer size
  218. }
  219. for i := range want[1] {
  220. want[1][i] = 0xAB
  221. }
  222. for i := range want[2] {
  223. want[2][i] = 0xCD
  224. }
  225. for i, p := range want {
  226. if _, err := tx.Write(p); err != nil {
  227. t.Fatalf("tx write %d: %v", i, err)
  228. }
  229. }
  230. for i, p := range want {
  231. select {
  232. case got := <-rxCh:
  233. if !slices.Equal(got.payload, p) {
  234. t.Errorf("packet %d: payload differs (len=%d want %d)", i, len(got.payload), len(p))
  235. }
  236. if got.from.Port() != tx.LocalAddr().(*net.UDPAddr).AddrPort().Port() {
  237. t.Errorf("packet %d: from=%v, want sender port %d", i, got.from, tx.LocalAddr().(*net.UDPAddr).Port)
  238. }
  239. case <-time.After(5 * time.Second):
  240. t.Fatalf("timed out waiting for packet %d", i)
  241. }
  242. }
  243. if err := sc.Close(); err != nil {
  244. t.Fatalf("Close: %v", err)
  245. }
  246. select {
  247. case <-listenDone:
  248. case <-time.After(5 * time.Second):
  249. t.Fatal("ListenOut did not return after Close")
  250. }
  251. }