flow_tx_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. //go:build linux && !android && !e2e_testing
  2. package nebula
  3. import (
  4. "fmt"
  5. "net/netip"
  6. "os"
  7. "os/exec"
  8. "testing"
  9. "time"
  10. "github.com/flynn/noise"
  11. "github.com/rcrowley/go-metrics"
  12. "github.com/slackhq/nebula/cpupick"
  13. "github.com/slackhq/nebula/firewall"
  14. "github.com/slackhq/nebula/header"
  15. "github.com/slackhq/nebula/noiseutil"
  16. "github.com/slackhq/nebula/overlay/batch"
  17. "github.com/slackhq/nebula/overlay/tio"
  18. "github.com/slackhq/nebula/udp"
  19. "github.com/slackhq/nebula/util"
  20. "github.com/stretchr/testify/require"
  21. "go.uber.org/goleak"
  22. "golang.org/x/sys/unix"
  23. )
  24. // TestReservedEncryptionMatchesInline encrypts a batch with counters reserved up front: the receiver accepts every
  25. // segment, in order.
  26. func TestReservedEncryptionMatchesInline(t *testing.T) {
  27. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedConfig{})
  28. packets, want := flowTestBatches(1, 3, 3, 9)
  29. r := txReserver{f: sender.f}
  30. items := r.collect(nil, packets[0], &firewall.ParsedPacket{}, make([]byte, mtu), 0, nil)
  31. require.Equal(t, uint64(len(want)), r.tunnels[0].n)
  32. sb := batch.NewSendBatch(sender.conn, 128, 65536)
  33. for _, it := range r.reserve(items) {
  34. sender.f.encryptTxItem(&it, make([]byte, 12), sb)
  35. }
  36. sender.f.flushSendBatch(sb, 0)
  37. require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()))
  38. }
  39. // TestSegmentCount checks segmentCount predicts exactly the segments tio.SegmentSuperpacket cuts, odd tails included.
  40. func TestSegmentCount(t *testing.T) {
  41. require.Equal(t, uint64(1), segmentCount(tio.Packet{Bytes: make([]byte, 60)}))
  42. for _, segs := range []int{1, 2, 3, 45} {
  43. for _, tail := range []int{0, 1, flowTestMSS - 1} {
  44. pkt := flowTCPSuperpacket(0, 0, segs)
  45. pkt.Bytes = pkt.Bytes[:len(pkt.Bytes)-tail]
  46. pkt.GSO = tio.GSOInfo{Size: flowTestMSS, HdrLen: 40, CsumStart: 20, Proto: tio.GSOProtoTCP}
  47. want, got := segmentCount(pkt), uint64(0)
  48. require.NoError(t, tio.SegmentSuperpacket(pkt, func([]byte) error { got++; return nil }))
  49. require.Equal(t, want, got, "segs %d tail %d", segs, tail)
  50. }
  51. }
  52. }
  53. // TestFlowStagedTXOrderAndCounters runs the staged transmit pipeline with shuffled workers and a slow socket, so the
  54. // sender lags and the reader runs into backpressure, then fails the tun read with batches in flight: the wire must
  55. // carry every segment once, in tun read order, under strictly increasing counters that were each reserved exactly
  56. // once, and the read error is still reported.
  57. func TestFlowStagedTXOrderAndCounters(t *testing.T) {
  58. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  59. for _, cipher := range []noise.CipherFunc{noiseutil.CipherAESGCM, noise.CipherChaChaPoly} {
  60. t.Run(cipher.CipherName(), func(t *testing.T) {
  61. if cipher.CipherName() == noiseutil.CipherAESGCM.CipherName() && noiseutil.EncryptLockNeeded {
  62. t.Skip("AES-GCM requires encryption in nonce order")
  63. }
  64. sender, receiver := newFlowTestPair(t, cipher, stagedOn(4))
  65. delayCrypto(sender, func(n uint64) {
  66. if n%54 == 0 {
  67. time.Sleep(time.Millisecond)
  68. }
  69. })
  70. // Far slower than the reader, even under the race detector
  71. sender.conn.delay = 8 * time.Millisecond
  72. batches, want := flowTestBatches(60, 6, 3, 9)
  73. start := sender.hostinfo.ConnectionState.messageCounter.Load()
  74. tx := metrics.GetOrRegisterCounter("flow.tx.batches", nil)
  75. tx0 := tx.Count()
  76. sender.f.listenIn(newFlowBatchQueue(batches), 0)
  77. require.Equal(t, int64(len(batches)), tx.Count()-tx0, "every batch uses the ordered sender")
  78. require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()), "wire order must match tun read order")
  79. require.Equal(t, start+uint64(len(want)), sender.hostinfo.ConnectionState.messageCounter.Load())
  80. require.NotNil(t, sender.f.fatalErr.Load())
  81. })
  82. }
  83. }
  84. // TestFlowStagedTXCloseInFlight closes the interface and tun while batches are in flight, 20 times over.
  85. func TestFlowStagedTXCloseInFlight(t *testing.T) {
  86. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  87. for range 20 {
  88. sender, _ := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(3))
  89. sender.conn.delay = time.Millisecond
  90. batches, _ := flowTestBatches(400, 4, 2, 8)
  91. q := newFlowBatchQueue(batches)
  92. done := make(chan struct{})
  93. go func() { sender.f.listenIn(q, 0); close(done) }()
  94. require.Eventually(t, func() bool { return q.reads.Load() > 5 }, 5*time.Second, 100*time.Microsecond)
  95. sender.f.closed.Store(true)
  96. require.NoError(t, q.Close())
  97. <-done
  98. }
  99. }
  100. // TestFlowStagedTXExhausted drops every batch of a tunnel whose counter runs out, like inline sends do.
  101. func TestFlowStagedTXExhausted(t *testing.T) {
  102. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  103. sender, _ := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  104. ci := sender.hostinfo.ConnectionState
  105. ci.messageCounter.Store(RejectAfterMessages - 3)
  106. sender.f.listenIn(newFlowBatchQueue([][]tio.Packet{{flowTCPSuperpacket(0, 0, 10)}, {flowTCPSuperpacket(0, 1, 2)}}), 0)
  107. require.Empty(t, sender.conn.captured())
  108. require.Equal(t, RejectAfterMessages, ci.messageCounter.Load())
  109. require.Equal(t, int64(12), sender.f.messageMetrics.txExhausted.Count())
  110. }
  111. // TestFlowStagedTXRelay sends staged batches, and an empty read, through a relay: outer and inner layers both verify,
  112. // in order, and go to the relay.
  113. func TestFlowStagedTXRelay(t *testing.T) {
  114. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  115. for _, window := range []uint64{ReplayWindow} {
  116. t.Run(fmt.Sprintf("window=%d", window), func(t *testing.T) {
  117. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(2))
  118. sender.hostinfo.ConnectionState.window = NewBits(window)
  119. receiver.hostinfo.ConnectionState.window = NewBits(window)
  120. relayRemote, _, relayPeer := addTestRelay(t, sender, window)
  121. batches, want := flowTestBatches(2, 3, 1, 6)
  122. var payload []byte
  123. sender.f.listenIn(newFlowBatchQueue(append(batches, nil)), 0)
  124. wire := sender.conn.captured()
  125. require.Len(t, wire, len(want)+len(payload))
  126. var got []decodedSeg
  127. var tiny []byte
  128. var lastOuter, lastInner uint64
  129. for i, pkt := range wire {
  130. require.Equal(t, relayRemote, sender.conn.addrs[i])
  131. h := &header.H{}
  132. require.NoError(t, h.Parse(pkt))
  133. require.Greater(t, h.MessageCounter, lastOuter)
  134. lastOuter = h.MessageCounter
  135. require.NoError(t, relayPeer.VerifyRelay(nil, h.MessageCounter, pkt, make([]byte, 12)))
  136. pkt = pkt[header.Len : len(pkt)-16]
  137. require.NoError(t, h.Parse(pkt))
  138. require.Greater(t, h.MessageCounter, lastInner)
  139. lastInner = h.MessageCounter
  140. plain, err := receiver.hostinfo.ConnectionState.Decrypt(receiver.f.l, h.MessageCounter, pkt, make([]byte, 12))
  141. require.NoError(t, err)
  142. if len(plain) == 41 {
  143. tiny = append(tiny, plain[40])
  144. } else {
  145. got = append(got, decodeSeg(plain[40:]))
  146. }
  147. }
  148. require.Equal(t, want, got)
  149. require.Equal(t, payload, tiny)
  150. })
  151. }
  152. }
  153. // addTestRelay makes sender reach its peer only through a relay tunnel with replay windows of window. It returns the
  154. // relay's underlay address, its hostinfo and the relay's side of the tunnel.
  155. func addTestRelay(t testing.TB, sender *flowTestPeer, window uint64) (netip.AddrPort, *HostInfo, *ConnectionState) {
  156. relayInit, relayResp := runTestHandshake(t)
  157. relayCI, err := newConnectionStateFromResult(relayInit)
  158. require.NoError(t, err)
  159. relayPeer, err := newConnectionStateFromResult(relayResp)
  160. require.NoError(t, err)
  161. relayCI.window, relayPeer.window = NewBits(window), NewBits(window)
  162. relayAddr := netip.MustParseAddr("10.0.0.3")
  163. relayRemote := netip.MustParseAddrPort("192.0.2.3:4242")
  164. relay := &Relay{Type: TerminalType, State: Established, LocalIndex: 5, RemoteIndex: 6, PeerAddr: flowTestRemote}
  165. relayHI := &HostInfo{
  166. vpnAddrs: []netip.Addr{relayAddr},
  167. ConnectionState: relayCI,
  168. relayState: RelayState{
  169. relayForByAddr: map[netip.Addr]*Relay{flowTestRemote: relay},
  170. relayForByIdx: map[uint32]*Relay{relay.LocalIndex: relay},
  171. },
  172. }
  173. relayHI.remote.Store(&relayRemote)
  174. sender.f.hostMap.Hosts[relayAddr] = relayHI
  175. sender.hostinfo.remote.Store(&netip.AddrPort{})
  176. sender.hostinfo.relayState.relays = []netip.Addr{relayAddr}
  177. return relayRemote, relayHI, relayPeer
  178. }
  179. // affinityQueue makes every read start with an idle pipeline: before each read it waits until conn has sent what was
  180. // read before, recording how many sends that took, then samples the reader's actual thread affinity.
  181. type affinityQueue struct {
  182. *flowBatchQueue
  183. conn *affinityConn
  184. sent int
  185. boundaries []int
  186. masks []unix.CPUSet
  187. }
  188. func (q *affinityQueue) Read() ([]tio.Packet, error) {
  189. if n := q.reads.Load(); n > 0 {
  190. for _, p := range q.batches[n-1] {
  191. q.sent += int(segmentCount(p))
  192. }
  193. }
  194. q.boundaries = append(q.boundaries, q.conn.waitFor(q.sent))
  195. q.masks = append(q.masks, threadMask())
  196. return q.flowBatchQueue.Read()
  197. }
  198. // affinityConn captures every send, sampling the sending thread's actual affinity in each WriteBatch.
  199. type affinityConn struct {
  200. flowCaptureConn
  201. masks []unix.CPUSet
  202. }
  203. func (c *affinityConn) WriteBatch(p [][]byte, addrs []netip.AddrPort) (int, error) {
  204. mask := threadMask()
  205. c.mu.Lock()
  206. c.masks = append(c.masks, mask)
  207. c.mu.Unlock()
  208. return c.flowCaptureConn.WriteBatch(p, addrs)
  209. }
  210. // waitFor waits until n packets were sent and returns how many WriteBatch calls that took.
  211. func (c *affinityConn) waitFor(n int) int {
  212. for len(c.captured()) < n {
  213. time.Sleep(50 * time.Microsecond)
  214. }
  215. c.mu.Lock()
  216. defer c.mu.Unlock()
  217. return len(c.masks)
  218. }
  219. func threadMask() unix.CPUSet {
  220. var mask unix.CPUSet
  221. if err := unix.SchedGetaffinity(0, &mask); err != nil {
  222. panic(err)
  223. }
  224. return mask
  225. }
  226. // requireFlowCPU checks every mask is just cpu, or with cpu -1, the whole allowed set.
  227. func requireFlowCPU(t *testing.T, masks []unix.CPUSet, cpu int, allowed []int) {
  228. t.Helper()
  229. require.NotEmpty(t, masks)
  230. for _, mask := range masks {
  231. if cpu >= 0 {
  232. require.Equal(t, 1, mask.Count())
  233. require.True(t, mask.IsSet(cpu), "expected CPU %d", cpu)
  234. continue
  235. }
  236. require.Equal(t, len(allowed), mask.Count())
  237. for _, c := range allowed {
  238. require.True(t, mask.IsSet(c))
  239. }
  240. }
  241. }
  242. // TestFlowStagedTXAffinity sends large, small and large reads, each from an idle pipeline, and checks the order on the
  243. // wire and where the reader and each send actually ran, for every CPU placement, with one routine and with several. A
  244. // staged sender is only pinned to an explicit entry other than its reader's CPU, and inline sends stay on the reader.
  245. // The reader only reads again once its earlier reads are on the wire, so this also checks the sender never waits for
  246. // the reader. Pinning threads changes the process, so the test runs in a child.
  247. func TestFlowStagedTXAffinity(t *testing.T) {
  248. if os.Getenv("NEBULA_FLOW_AFFINITY_TEST") != "1" {
  249. cmd := exec.Command(os.Args[0], "-test.run=^TestFlowStagedTXAffinity$")
  250. cmd.Env = append(os.Environ(), "NEBULA_FLOW_AFFINITY_TEST=1")
  251. out, err := cmd.CombinedOutput()
  252. require.NoError(t, err, "%s", out)
  253. return
  254. }
  255. allowed, err := util.AllowedCPUs()
  256. require.NoError(t, err)
  257. for _, placement := range []string{"fallback", "automatic", "explicit", "explicit-single"} {
  258. for _, pin := range []bool{false, true} {
  259. for _, sc := range sendPaths() {
  260. for _, routines := range []int{1, 2} {
  261. t.Run(fmt.Sprintf("%s/pin=%t/%s/routines=%d", placement, pin, stagedName(sc), routines), func(t *testing.T) {
  262. if pin && len(allowed) < 2 {
  263. t.Skip("requires at least two allowed CPUs")
  264. }
  265. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, sc)
  266. f := sender.f
  267. f.pinThreads, f.routines = pin, routines
  268. switch placement {
  269. case "automatic":
  270. f.defaultCpuAffinity = cpupick.Default(routines, 4242, nil)
  271. case "explicit":
  272. f.cpuAffinity = []int{allowed[len(allowed)-1], allowed[0]}
  273. case "explicit-single":
  274. f.cpuAffinity = allowed[len(allowed)-1:]
  275. }
  276. readerCPU, sendCPU := -1, -1
  277. if pin {
  278. readerCPU = f.threadCPU(0)
  279. sendCPU = readerCPU
  280. if sc.tx {
  281. // Never the reader's CPU, and no automatic choice: unpinned unless an explicit entry fits
  282. sendCPU = -1
  283. if placement == "explicit" {
  284. sendCPU = f.cpuAffinity[1]
  285. }
  286. }
  287. }
  288. smallCPU := sendCPU
  289. conn := &affinityConn{}
  290. f.writers = []udp.Conn{conn}
  291. batches, want := flowTestBatches(3, 8, 1, 45)
  292. batches[1] = batches[1][:1]
  293. want = append(want[:405], want[720:]...)
  294. q := &affinityQueue{flowBatchQueue: newFlowBatchQueue(batches), conn: conn}
  295. f.listenIn(q, 0)
  296. require.Equal(t, want, decryptWire(t, receiver, conn.captured()))
  297. b := q.boundaries
  298. requireFlowCPU(t, q.masks, readerCPU, allowed)
  299. requireFlowCPU(t, conn.masks[:b[1]], sendCPU, allowed)
  300. requireFlowCPU(t, conn.masks[b[1]:b[2]], smallCPU, allowed)
  301. requireFlowCPU(t, conn.masks[b[2]:], sendCPU, allowed)
  302. })
  303. }
  304. }
  305. }
  306. }
  307. }
  308. // sendPaths is inline, staged and, with the threshold, adaptive transmit.
  309. func sendPaths() []stagedConfig {
  310. return []stagedConfig{{}, stagedOn(2)}
  311. }