udp_sendmsgx_darwin_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. //go:build !e2e_testing
  2. package udp
  3. import (
  4. "fmt"
  5. "log/slog"
  6. "net"
  7. "net/netip"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. )
  13. // recvAll reads n datagrams from pc, or fails the test after a second without one.
  14. func recvAll(t *testing.T, pc net.PacketConn, n int) []string {
  15. t.Helper()
  16. var got []string
  17. buf := make([]byte, 64)
  18. for len(got) < n {
  19. require.NoError(t, pc.SetReadDeadline(time.Now().Add(time.Second)))
  20. l, _, err := pc.ReadFrom(buf)
  21. require.NoError(t, err, "after %d of %d datagrams", len(got), n)
  22. got = append(got, string(buf[:l]))
  23. }
  24. return got
  25. }
  26. // A batch longer than sendBatch reaches each peer whole and in order through sendmsg_x, without falling
  27. // back to sendto: a run to one peer that spans two calls, then datagrams alternating between peers.
  28. func TestWriteBatchSendsEachToItsPeer(t *testing.T) {
  29. for _, tc := range []struct{ name, listen string }{
  30. {"v4", "127.0.0.1"},
  31. {"v6", "::1"},
  32. } {
  33. t.Run(tc.name, func(t *testing.T) {
  34. c, err := NewListener(slog.New(slog.DiscardHandler), Settings{Listen: netip.AddrPortFrom(netip.MustParseAddr(tc.listen), 0)})
  35. require.NoError(t, err)
  36. defer c.Close()
  37. var peers [2]net.PacketConn
  38. var dsts [2]netip.AddrPort
  39. for i := range peers {
  40. peers[i], err = net.ListenPacket("udp", net.JoinHostPort(tc.listen, "0"))
  41. require.NoError(t, err)
  42. defer peers[i].Close()
  43. dsts[i] = netip.MustParseAddrPort(peers[i].LocalAddr().String())
  44. }
  45. const total = sendBatch*2 + 5
  46. bufs := make([][]byte, total)
  47. addrs := make([]netip.AddrPort, total)
  48. var want [2][]string
  49. for i := range bufs {
  50. bufs[i] = fmt.Appendf(nil, "pkt-%03d", i)
  51. p := i % 2
  52. if i < sendBatch+6 {
  53. p = 0
  54. }
  55. addrs[i] = dsts[p]
  56. want[p] = append(want[p], string(bufs[i]))
  57. }
  58. n, err := c.WriteBatch(bufs, addrs)
  59. require.NoError(t, err)
  60. assert.Equal(t, total, n)
  61. assert.False(t, c.(*StdConn).noSendmsgX.Load(), "fell back to sendto")
  62. for i := range peers {
  63. assert.Equal(t, want[i], recvAll(t, peers[i], len(want[i])))
  64. }
  65. })
  66. }
  67. }
  68. // Destinations that can't be sent to cost only their own packets: an IPv6 peer on an IPv4 socket is
  69. // skipped before the call, and a broadcast address without SO_BROADCAST is refused by the kernel
  70. // mid-batch. Neither stops the packets around it, delivers any twice, or turns sendmsg_x off.
  71. func TestWriteBatchSkipsUnsendableDestinations(t *testing.T) {
  72. c, err := NewListener(slog.New(slog.DiscardHandler), Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0")})
  73. require.NoError(t, err)
  74. defer c.Close()
  75. peer, err := net.ListenPacket("udp", "127.0.0.1:0")
  76. require.NoError(t, err)
  77. defer peer.Close()
  78. good := netip.MustParseAddrPort(peer.LocalAddr().String())
  79. bcast := netip.AddrPortFrom(netip.MustParseAddr("255.255.255.255"), good.Port())
  80. v6 := netip.MustParseAddrPort("[::1]:4242")
  81. addrs := []netip.AddrPort{good, bcast, bcast, good, v6, good, bcast}
  82. bufs := make([][]byte, len(addrs))
  83. for i := range bufs {
  84. bufs[i] = fmt.Appendf(nil, "pkt-%d", i)
  85. }
  86. n, err := c.WriteBatch(bufs, addrs)
  87. require.NoError(t, err)
  88. assert.Equal(t, 3, n)
  89. assert.False(t, c.(*StdConn).noSendmsgX.Load(), "fell back to sendto")
  90. assert.Equal(t, []string{"pkt-0", "pkt-3", "pkt-5"}, recvAll(t, peer, 3))
  91. require.NoError(t, peer.SetReadDeadline(time.Now().Add(100*time.Millisecond)))
  92. _, _, err = peer.ReadFrom(make([]byte, 64))
  93. assert.Error(t, err, "a datagram arrived twice")
  94. }
  95. // A datagram the kernel refuses as its own fault (EMSGSIZE here, as ENOBUFS would be under a full
  96. // interface queue) costs only itself, whether it fails after others in the same call (a short count) or
  97. // first (the errno alone): the rest of its run still arrives, in order, and sendmsg_x stays on.
  98. func TestWriteBatchLosesOnlyTheFailedDatagram(t *testing.T) {
  99. for _, tc := range []struct {
  100. name string
  101. big int
  102. }{
  103. {"mid-call", 2},
  104. {"first", 0},
  105. } {
  106. t.Run(tc.name, func(t *testing.T) {
  107. c, err := NewListener(slog.New(slog.DiscardHandler), Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0")})
  108. require.NoError(t, err)
  109. defer c.Close()
  110. peer, err := net.ListenPacket("udp", "127.0.0.1:0")
  111. require.NoError(t, err)
  112. defer peer.Close()
  113. dst := netip.MustParseAddrPort(peer.LocalAddr().String())
  114. bufs := make([][]byte, 5)
  115. addrs := make([]netip.AddrPort, len(bufs))
  116. var want []string
  117. for i := range bufs {
  118. addrs[i] = dst
  119. if i == tc.big {
  120. // Past the 65535-byte IP datagram limit, so the kernel can only refuse it.
  121. bufs[i] = make([]byte, 70000)
  122. continue
  123. }
  124. bufs[i] = fmt.Appendf(nil, "pkt-%d", i)
  125. want = append(want, string(bufs[i]))
  126. }
  127. n, err := c.WriteBatch(bufs, addrs)
  128. require.NoError(t, err)
  129. assert.Equal(t, len(want), n)
  130. assert.False(t, c.(*StdConn).noSendmsgX.Load(), "fell back to sendto")
  131. assert.Equal(t, want, recvAll(t, peer, len(want)))
  132. })
  133. }
  134. }