1
0

netstack_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package amneziawgnet
  2. import (
  3. "testing"
  4. "gvisor.dev/gvisor/pkg/buffer"
  5. "gvisor.dev/gvisor/pkg/tcpip/link/channel"
  6. )
  7. // TestStackTunReadDrainsBufferedBatch is a regression test for a real
  8. // throughput bug: Read used to always return exactly one packet per call
  9. // no matter how many were already queued, forcing amneziawg-go's TUN
  10. // reader to pay a full peer-lookup+staging+syscall cycle per packet on the
  11. // download path while the upload path (via the UDP bind's own
  12. // recvmmsg/sendmmsg batching) amortized that cost across up to 128
  13. // packets. Confirmed live: this alone took real download throughput from
  14. // 30-40 Mbit/s to 130-250 Mbit/s on a real test connection (see commit
  15. // 6436fd9c's message and internal/amneziawgnet/netstack.go's own comment
  16. // on tunQueueDepth for the full story) -- this test locks in the second,
  17. // finer-grained fix on top of that: Read must actually drain what's
  18. // already buffered instead of returning after the first packet.
  19. func TestStackTunReadDrainsBufferedBatch(t *testing.T) {
  20. t.Parallel()
  21. tun := &stackTun{incomingPacket: make(chan *buffer.View, tunQueueDepth)}
  22. packets := [][]byte{{1, 2, 3}, {4, 5}, {6, 7, 8, 9}}
  23. for _, p := range packets {
  24. tun.incomingPacket <- buffer.NewViewWithData(p)
  25. }
  26. buf := make([][]byte, 8)
  27. sizes := make([]int, 8)
  28. for i := range buf {
  29. buf[i] = make([]byte, 64)
  30. }
  31. n, err := tun.Read(buf, sizes, 0)
  32. if err != nil {
  33. t.Fatalf("Read: %v", err)
  34. }
  35. if n != len(packets) {
  36. t.Fatalf("Read returned %d packets, want %d (all buffered packets in one call)", n, len(packets))
  37. }
  38. for i, want := range packets {
  39. got := buf[i][:sizes[i]]
  40. if string(got) != string(want) {
  41. t.Errorf("packet %d = %v, want %v", i, got, want)
  42. }
  43. }
  44. }
  45. // TestStackTunReadStopsAtBufCapacity confirms Read never returns more
  46. // packets than the caller's buf can hold, and that whatever didn't fit is
  47. // still there (in order) for the next call -- draining must respect the
  48. // caller's batch size, not just gulp everything queued.
  49. func TestStackTunReadStopsAtBufCapacity(t *testing.T) {
  50. t.Parallel()
  51. tun := &stackTun{incomingPacket: make(chan *buffer.View, tunQueueDepth)}
  52. packets := [][]byte{{1}, {2}, {3}}
  53. for _, p := range packets {
  54. tun.incomingPacket <- buffer.NewViewWithData(p)
  55. }
  56. buf := make([][]byte, 2)
  57. sizes := make([]int, 2)
  58. for i := range buf {
  59. buf[i] = make([]byte, 64)
  60. }
  61. n, err := tun.Read(buf, sizes, 0)
  62. if err != nil {
  63. t.Fatalf("first Read: %v", err)
  64. }
  65. if n != 2 {
  66. t.Fatalf("first Read returned %d, want 2 (buf capacity)", n)
  67. }
  68. n, err = tun.Read(buf, sizes, 0)
  69. if err != nil {
  70. t.Fatalf("second Read: %v", err)
  71. }
  72. if n != 1 {
  73. t.Fatalf("second Read returned %d, want 1 (the leftover packet)", n)
  74. }
  75. if got := buf[0][:sizes[0]]; string(got) != "\x03" {
  76. t.Errorf("leftover packet = %v, want [3]", got)
  77. }
  78. }
  79. // TestStackTunWriteReturnsPacketBuffersToPool locks in gVisor's ownership rule
  80. // for the upload path: whoever calls InjectInbound must DecRef the packet.
  81. func TestStackTunWriteReturnsPacketBuffersToPool(t *testing.T) {
  82. tun := &stackTun{ep: channel.New(tunQueueDepth, 1420, ""), mtu: 1420}
  83. defer tun.ep.Close()
  84. packet := make([]byte, 1400)
  85. packet[0] = 0x45 // IPv4, version nibble is all Write inspects
  86. bufs := [][]byte{packet}
  87. allocs := testing.AllocsPerRun(1000, func() {
  88. if _, err := tun.Write(bufs, 0); err != nil {
  89. t.Fatalf("Write: %v", err)
  90. }
  91. })
  92. // 0 once pooled, 4 when every packet buffer is stranded; -race adds ~1.
  93. if allocs > 1 {
  94. t.Fatalf("Write allocates %v times per packet, want <=1: injected packet buffers are not being returned to gVisor's pools", allocs)
  95. }
  96. }
  97. // TestStackTunReadReturnsViewsToPool is the download-path counterpart: a view
  98. // that is copied out but never released strands its pooled chunk.
  99. func TestStackTunReadReturnsViewsToPool(t *testing.T) {
  100. tun := &stackTun{incomingPacket: make(chan *buffer.View, tunQueueDepth)}
  101. packet := make([]byte, 1400)
  102. buf := [][]byte{make([]byte, 2048)}
  103. sizes := make([]int, 1)
  104. allocs := testing.AllocsPerRun(1000, func() {
  105. tun.incomingPacket <- buffer.NewViewWithData(packet)
  106. if _, err := tun.Read(buf, sizes, 0); err != nil {
  107. t.Fatalf("Read: %v", err)
  108. }
  109. })
  110. // 0 once the drained view goes back to viewPool, 3 when it does not.
  111. if allocs > 1 {
  112. t.Fatalf("Read allocates %v times per packet, want <=1: drained views are not being released", allocs)
  113. }
  114. }