flow_rx_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. //go:build linux && !android && !e2e_testing
  2. package nebula
  3. import (
  4. "fmt"
  5. "net/netip"
  6. "sync/atomic"
  7. "testing"
  8. "time"
  9. "github.com/flynn/noise"
  10. "github.com/rcrowley/go-metrics"
  11. "github.com/slackhq/nebula/header"
  12. "github.com/slackhq/nebula/noiseutil"
  13. "github.com/slackhq/nebula/udp"
  14. "github.com/stretchr/testify/require"
  15. "go.uber.org/goleak"
  16. )
  17. // TestFlowStagedRXOrderAndReplay feeds every packet behind a forgery of it, from another address, and a replay of
  18. // it, then a test packet that jumps the window and the whole stream again, with shuffled workers and a slow tun,
  19. // through small and large batches: the tun sees each segment once, in order, the window marks them all, the forgeries
  20. // neither consume a counter nor roam, and the jump does not get the earlier data packets of its batch rejected.
  21. func TestFlowStagedRXOrderAndReplay(t *testing.T) {
  22. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  23. for _, cipher := range []noise.CipherFunc{noiseutil.CipherAESGCM, noise.CipherChaChaPoly} {
  24. for _, batchLen := range []int{8, 1000} {
  25. t.Run(fmt.Sprintf("%s/batch=%d", cipher.CipherName(), batchLen), func(t *testing.T) {
  26. sender, receiver := newFlowTestPair(t, cipher, stagedOn(4))
  27. delayCrypto(receiver, func(n uint64) {
  28. if n%32 == 0 {
  29. time.Sleep(time.Millisecond)
  30. }
  31. })
  32. receiver.tun.delay = 100 * time.Microsecond
  33. batches, want := flowTestBatches(30, 4, 1, 10)
  34. wire := wireFor(t, sender, batches)
  35. var feed [][]byte
  36. for i, p := range wire {
  37. // Corrupt the tag or the payload
  38. bad := append([]byte(nil), p...)
  39. bad[[]int{len(p) - 1, header.Len + 3}[i%2]] ^= 1
  40. feed = append(feed, bad, p, p)
  41. }
  42. // The jump lands in a batch with fresh data before it, at batch=1000
  43. sender.hostinfo.ConnectionState.messageCounter.Add(2 * ReplayWindow)
  44. feed = append(append(feed, sendControl(sender, header.Test, header.TestReply)), wire...)
  45. forged := func(i int) netip.AddrPort {
  46. if i < 3*len(wire) && i%3 == 0 {
  47. return netip.MustParseAddrPort("198.51.100.9:4242")
  48. }
  49. return flowTestUnder
  50. }
  51. rx0 := rxBatches.Count()
  52. conn := &rxConn{wire: feed, batchLen: batchLen, from: forged}
  53. receiver.receive(conn)
  54. require.Positive(t, rxBatches.Count()-rx0)
  55. require.Equal(t, (len(feed)+batchLen-1)/batchLen, conn.swaps, "every batch is handed off")
  56. require.Equal(t, want, receiver.tun.segments(t), "every packet exactly once, in order")
  57. require.Equal(t, flowTestUnder, receiver.hostinfo.GetRemote())
  58. for _, p := range wire {
  59. h := &header.H{}
  60. require.NoError(t, h.Parse(p))
  61. require.False(t, receiver.hostinfo.ConnectionState.window.Check(receiver.f.l, h.MessageCounter), "counter %d must be marked seen", h.MessageCounter)
  62. }
  63. })
  64. }
  65. }
  66. }
  67. // TestFlowStagedRXClose: a CloseTunnel in the middle of a batch is handled in its place, by the writer. The data after
  68. // it in the batch is still delivered from the batch's hostmap cache, as inline receive does.
  69. func TestFlowStagedRXClose(t *testing.T) {
  70. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  71. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  72. batches, want := flowTestBatches(4, 4, 1, 8)
  73. wire := wireFor(t, sender, batches)
  74. half := len(wire) / 2
  75. feed := append(append(append([][]byte{}, wire[:half]...), sendControl(sender, header.CloseTunnel, 0)), wire[half:]...)
  76. receiver.receive(&rxConn{wire: feed, batchLen: len(feed)})
  77. require.Equal(t, want, receiver.tun.segments(t))
  78. require.Nil(t, receiver.f.hostMap.QueryIndex(receiver.hostinfo.localIndexId))
  79. }
  80. // sendControl returns the wire packet of a control message from sender.
  81. func sendControl(sender *flowTestPeer, typ header.MessageType, st header.MessageSubType) []byte {
  82. sender.f.sendNoMetrics(typ, st, sender.hostinfo.ConnectionState, sender.hostinfo, flowTestUnder, nil, make([]byte, 12), make([]byte, mtu), 0)
  83. wire := sender.conn.captured()
  84. sender.conn.pkts, sender.conn.addrs = nil, nil
  85. return wire[len(wire)-1]
  86. }
  87. // receivePaths is inline, staged and, with the threshold, adaptive receive.
  88. func receivePaths() []stagedConfig {
  89. return []stagedConfig{{}, stagedOn(2), adaptive(2)}
  90. }
  91. // TestFlowStagedRXRecvError feeds a RecvError that deletes the tunnel, then a known replay and new data for it, in one
  92. // batch. Every receive path must drop the data through its own view of the hostmap, without touching the replay window,
  93. // and answer each packet with a RecvError, as inline receive does, without decrypting the replay again.
  94. func TestFlowStagedRXRecvError(t *testing.T) {
  95. for _, sc := range receivePaths() {
  96. t.Run(stagedName(sc), func(t *testing.T) {
  97. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, sc)
  98. receiver.f.messageMetrics = newMessageMetrics()
  99. receiver.f.acceptRecvErrorConfig = recvErrorAlways
  100. receiver.f.sendRecvErrorConfig = recvErrorAlways
  101. batches, want := flowTestBatches(1, 1, 1, 33)
  102. wire := wireFor(t, sender, batches)
  103. rxc := newRxContext(receiver.f, 0)
  104. receiver.f.readOutsidePackets(ViaSender{UdpAddr: flowTestUnder}, append([]byte(nil), wire[0]...), rxc)
  105. receiver.f.flushOutside(rxc)
  106. h := &header.H{}
  107. require.NoError(t, h.Parse(wire[0]))
  108. replay := h.MessageCounter
  109. var decrypts atomic.Int64
  110. delayCrypto(receiver, func(n uint64) {
  111. if n == replay {
  112. decrypts.Add(1)
  113. }
  114. })
  115. ctl := header.Encode(make([]byte, header.Len), header.Version, header.RecvError, 0, receiver.hostinfo.remoteIndexId, 0)
  116. conn := &rxConn{wire: append([][]byte{ctl}, wire...), batchLen: len(wire) + 1}
  117. receiver.receive(conn)
  118. require.Nil(t, receiver.f.hostMap.QueryIndex(receiver.hostinfo.localIndexId))
  119. require.Equal(t, want[:1], receiver.tun.segments(t))
  120. require.Zero(t, decrypts.Load(), "known replays must not be decrypted again")
  121. replies := conn.captured()
  122. require.Len(t, replies, len(wire))
  123. for i, reply := range replies {
  124. require.NoError(t, h.Parse(reply))
  125. require.Equal(t, header.RecvError, h.Type)
  126. require.NoError(t, h.Parse(wire[i]))
  127. require.True(t, i == 0 || receiver.hostinfo.ConnectionState.window.Check(nil, h.MessageCounter), "dropped data must not update replay state")
  128. }
  129. })
  130. }
  131. }
  132. // TestFlowStagedRXReplacedTunnel classifies a data packet, then a close and a new handshake replace its tunnel at the
  133. // same index before the writer commits it, as when the listener runs ahead: it must be dropped untouched.
  134. func TestFlowStagedRXReplacedTunnel(t *testing.T) {
  135. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  136. receiver.f.messageMetrics = newMessageMetrics()
  137. batches, _ := flowTestBatches(1, 1, 1, 1)
  138. wire := wireFor(t, sender, batches)
  139. closePacket := sendControl(sender, header.CloseTunnel, 0)
  140. p := &stagedRx{f: receiver.f, h: &header.H{}, hostmapCache: map[uint32]*HostInfo{}}
  141. item := rxItem{packet: wire[0], via: flowTestUnder}
  142. p.classify(&item)
  143. require.NotNil(t, item.ci)
  144. rxc := newRxContext(receiver.f, 0)
  145. p.process(&rxBatch{items: []rxItem{{packet: closePacket, via: flowTestUnder}}}, rxc)
  146. require.Nil(t, receiver.f.hostMap.QueryIndex(receiver.hostinfo.localIndexId))
  147. _, replacement := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  148. receiver.f.hostMap.Lock()
  149. receiver.f.hostMap.unlockedAddHostInfo(replacement.hostinfo, receiver.f)
  150. receiver.f.hostMap.Unlock()
  151. p.process(&rxBatch{items: []rxItem{item}}, rxc)
  152. require.Empty(t, receiver.tun.writes)
  153. require.True(t, item.ci.window.Check(nil, item.counter))
  154. require.True(t, replacement.hostinfo.ConnectionState.window.Check(nil, item.counter))
  155. }
  156. // TestFlowStagedRXRealSocket exercises buffer handoff through the real Linux UDP receive loop.
  157. func TestFlowStagedRXRealSocket(t *testing.T) {
  158. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  159. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(3))
  160. batches, want := flowTestBatches(60, 4, 1, 8)
  161. wire := wireFor(t, sender, batches)
  162. l := receiver.f.l
  163. conn, err := udp.NewListener(l, udp.Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 16, Offloads: true})
  164. require.NoError(t, err)
  165. addr, err := conn.LocalAddr()
  166. require.NoError(t, err)
  167. out, err := udp.NewListener(l, udp.Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 16, Offloads: true})
  168. require.NoError(t, err)
  169. defer out.Close()
  170. // The sender's address is the tunnel's remote, so no roaming
  171. from, err := out.LocalAddr()
  172. require.NoError(t, err)
  173. receiver.hostinfo.remote.Store(&from)
  174. done := make(chan struct{})
  175. go func() { receiver.receive(conn); close(done) }()
  176. // Send in bursts small enough for the default socket buffer
  177. for off := 0; off < len(wire); off += 32 {
  178. end := min(off+32, len(wire))
  179. addrs := make([]netip.AddrPort, end-off)
  180. for i := range addrs {
  181. addrs[i] = addr
  182. }
  183. _, err := out.WriteBatch(wire[off:end], addrs)
  184. require.NoError(t, err)
  185. require.Eventually(t, func() bool { return len(receiver.tun.segments(t)) >= end }, 5*time.Second, 100*time.Microsecond)
  186. }
  187. receiver.f.closed.Store(true)
  188. require.NoError(t, conn.Close())
  189. <-done
  190. require.Equal(t, want, receiver.tun.segments(t))
  191. }
  192. // TestFlowStagedRXCloseFlushesPartial: packets the listener holds in a batch it never got to end when the conn fails
  193. // are still committed, in order, on a live interface.
  194. func TestFlowStagedRXCloseFlushesPartial(t *testing.T) {
  195. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  196. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  197. batches, want := flowTestBatches(11, 4, 1, 8)
  198. wire := wireFor(t, sender, batches)
  199. p := newStagedRx(receiver.f, 0, newRxContext(receiver.f, 0))
  200. for i, packet := range wire {
  201. p.deliver(flowTestUnder, packet)
  202. if (i+1)%64 == 0 {
  203. p.endBatch()
  204. }
  205. }
  206. p.close()
  207. require.Equal(t, want, receiver.tun.segments(t))
  208. }
  209. // TestFlowStagedRXInlineBelow: small idle batches stay on the listener, larger batches still use the ordered writer.
  210. func TestFlowStagedRXInlineBelow(t *testing.T) {
  211. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  212. sc := adaptive(3)
  213. sc.inlineBelow = 16
  214. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, sc)
  215. batches, want := flowTestBatches(40, 4, 1, 16)
  216. wire := wireFor(t, sender, batches)
  217. inline := metrics.GetOrRegisterCounter("flow.rx.inline", nil)
  218. inline0, rx0 := inline.Count(), rxBatches.Count()
  219. receiver.receive(&rxConn{wire: wire, sizes: []int{8, 8, 8, 128, 128, 8, 8, 128, 4, 128}})
  220. require.Equal(t, want, receiver.tun.segments(t))
  221. require.Positive(t, inline.Count()-inline0)
  222. require.Positive(t, rxBatches.Count()-rx0)
  223. }