flow_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. //go:build linux && !android && !e2e_testing
  2. package nebula
  3. import (
  4. "encoding/binary"
  5. "io"
  6. "math"
  7. "net/netip"
  8. "os"
  9. "sync"
  10. "sync/atomic"
  11. "testing"
  12. "time"
  13. "github.com/flynn/noise"
  14. "github.com/gaissmai/bart"
  15. "github.com/rcrowley/go-metrics"
  16. "github.com/slackhq/nebula/cert"
  17. "github.com/slackhq/nebula/config"
  18. "github.com/slackhq/nebula/firewall"
  19. "github.com/slackhq/nebula/header"
  20. "github.com/slackhq/nebula/iputil"
  21. "github.com/slackhq/nebula/noiseutil"
  22. "github.com/slackhq/nebula/overlay/batch"
  23. "github.com/slackhq/nebula/overlay/tio"
  24. "github.com/slackhq/nebula/test"
  25. "github.com/slackhq/nebula/udp"
  26. "github.com/stretchr/testify/require"
  27. "go.uber.org/goleak"
  28. )
  29. // flowTestMSS is the segment payload size of the synthetic TSO superpackets.
  30. const flowTestMSS = 1200
  31. var (
  32. flowTestLocal = netip.MustParseAddr("10.0.0.1")
  33. flowTestRemote = netip.MustParseAddr("10.0.0.2")
  34. flowTestUnder = netip.MustParseAddrPort("192.0.2.2:4242")
  35. )
  36. // flowTCPSuperpacket builds an IPv4/TCP TSO superpacket from local to remote carrying segs segments. Segment i's
  37. // payload starts with the flow id, the superpacket seq and i, so a receiver can check what it got and in which order.
  38. func flowTCPSuperpacket(flow uint16, seq uint32, segs int) tio.Packet {
  39. const hdr = 40
  40. pay := segs * flowTestMSS
  41. b := make([]byte, hdr+pay)
  42. b[0] = 0x45
  43. binary.BigEndian.PutUint16(b[2:4], uint16(min(hdr+pay, 65535)))
  44. b[8] = 64
  45. b[9] = iputil.IPProtocolTCP
  46. copy(b[12:16], flowTestLocal.AsSlice())
  47. copy(b[16:20], flowTestRemote.AsSlice())
  48. binary.BigEndian.PutUint16(b[20:22], 10000+flow)
  49. binary.BigEndian.PutUint16(b[22:24], 80)
  50. binary.BigEndian.PutUint32(b[24:28], seq*uint32(pay))
  51. b[32] = 0x50
  52. b[33] = 0x10 // ACK
  53. binary.BigEndian.PutUint16(b[34:36], 65535)
  54. for i := range segs {
  55. p := b[hdr+i*flowTestMSS:]
  56. binary.BigEndian.PutUint16(p[0:2], flow)
  57. binary.BigEndian.PutUint32(p[2:6], seq)
  58. binary.BigEndian.PutUint16(p[6:8], uint16(i))
  59. }
  60. if segs == 1 {
  61. return tio.Packet{Bytes: b}
  62. }
  63. return tio.Packet{Bytes: b, GSO: tio.GSOInfo{Size: flowTestMSS, HdrLen: hdr, CsumStart: 20, Proto: tio.GSOProtoTCP}}
  64. }
  65. // decodedSeg identifies one segment by the marker flowTCPSuperpacket put in its payload.
  66. type decodedSeg struct {
  67. flow uint16
  68. seq uint32
  69. idx uint16
  70. }
  71. func decodeSeg(p []byte) decodedSeg {
  72. return decodedSeg{flow: binary.BigEndian.Uint16(p[0:2]), seq: binary.BigEndian.Uint32(p[2:6]), idx: binary.BigEndian.Uint16(p[6:8])}
  73. }
  74. // flowTestBatches builds n reads of per superpackets of segs segments, across flows interleaved flows, and the
  75. // segments they carry in order.
  76. func flowTestBatches(n, per, flows, segs int) ([][]tio.Packet, []decodedSeg) {
  77. var batches [][]tio.Packet
  78. var want []decodedSeg
  79. seq := make([]uint32, flows)
  80. for range n {
  81. var b []tio.Packet
  82. for j := range per {
  83. flow := uint16(j % flows)
  84. b = append(b, flowTCPSuperpacket(flow, seq[flow], segs))
  85. for s := range segs {
  86. want = append(want, decodedSeg{flow: flow, seq: seq[flow], idx: uint16(s)})
  87. }
  88. seq[flow]++
  89. }
  90. batches = append(batches, b)
  91. }
  92. return batches, want
  93. }
  94. // flowTestPeer is one end of a tunnel: the interface, the hostinfo for the other end, and its capture points.
  95. type flowTestPeer struct {
  96. f *Interface
  97. hostinfo *HostInfo
  98. conn *flowCaptureConn
  99. tun *flowTunCapture
  100. }
  101. // newFlowTestPair builds two interfaces joined by one real tunnel (keys from an actual handshake). sender sends from
  102. // flowTestLocal to flowTestRemote, receiver is the far side.
  103. func newFlowTestPair(t testing.TB, cipher noise.CipherFunc, sc stagedConfig) (sender, receiver *flowTestPeer) {
  104. t.Helper()
  105. initR, respR := runTestHandshakeWith(t, cipher)
  106. sci, err := newConnectionStateFromResult(initR)
  107. require.NoError(t, err)
  108. rci, err := newConnectionStateFromResult(respR)
  109. require.NoError(t, err)
  110. sender = newFlowTestPeer(t, flowTestLocal, flowTestRemote, sci, 100, 200)
  111. receiver = newFlowTestPeer(t, flowTestRemote, flowTestLocal, rci, 200, 100)
  112. sender.f.staged, receiver.f.staged = sc, sc
  113. return sender, receiver
  114. }
  115. func newFlowTestPeer(t testing.TB, me, them netip.Addr, ci *ConnectionState, localIdx, remoteIdx uint32) *flowTestPeer {
  116. l := test.NewLogger()
  117. hm := newHostMap(l)
  118. hm.preferredRanges.Store(&[]netip.Prefix{})
  119. myNets, myAddrs := &bart.Lite{}, &bart.Lite{}
  120. myNets.Insert(netip.PrefixFrom(me, 24))
  121. myAddrs.Insert(netip.PrefixFrom(me, 32))
  122. myCert := &dummyCert{version: cert.Version1, networks: []netip.Prefix{netip.PrefixFrom(me, 24)}}
  123. theirCert := &dummyCert{version: cert.Version1, networks: []netip.Prefix{netip.PrefixFrom(them, 24)}, name: "peer"}
  124. ci.peerCert = &cert.CachedCertificate{Certificate: theirCert, InvertedGroups: map[string]struct{}{}}
  125. fw := NewFirewall(l, time.Minute, time.Minute, time.Minute, myCert)
  126. require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  127. require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
  128. conn, tun := &flowCaptureConn{}, &flowTunCapture{}
  129. lh := newTestLighthouse()
  130. lh.remoteAllowList.Store(&RemoteAllowList{})
  131. f := &Interface{
  132. hostMap: hm,
  133. outside: conn,
  134. firewall: fw,
  135. lightHouse: lh,
  136. pki: &PKI{},
  137. myVpnNetworksTable: myNets,
  138. myVpnAddrsTable: myAddrs,
  139. routines: 1,
  140. writers: []udp.Conn{conn},
  141. messageMetrics: &MessageMetrics{txExhausted: metrics.NewCounter()},
  142. metricTxDropped: metrics.NilCounter{},
  143. l: l,
  144. }
  145. f.ctx = t.Context()
  146. f.pki.caPool.Store(cert.NewCAPool())
  147. // Never promote or requery, those would put extra test packets on the wire
  148. f.tryPromoteEvery.Store(math.MaxUint32)
  149. f.reQueryEvery.Store(math.MaxUint32)
  150. f.batchers = []*batch.MultiCoalescer{batch.NewMultiCoalescer(tun, l)}
  151. f.connectionManager = &connectionManager{
  152. intf: f,
  153. relayUsed: map[uint32]struct{}{},
  154. relayUsedLock: &sync.RWMutex{},
  155. trafficTimer: NewLockingTimerWheel[uint32](time.Second, time.Minute),
  156. }
  157. f.handshakeManager = NewHandshakeManager(l, hm, lh, conn, defaultHandshakeConfig)
  158. f.handshakeManager.f = f
  159. hostinfo := &HostInfo{
  160. ConnectionState: ci,
  161. vpnAddrs: []netip.Addr{them},
  162. localIndexId: localIdx,
  163. remoteIndexId: remoteIdx,
  164. relayState: RelayState{relayForByAddr: map[netip.Addr]*Relay{}, relayForByIdx: map[uint32]*Relay{}},
  165. }
  166. hostinfo.remote.Store(&flowTestUnder)
  167. hostinfo.buildNetworks(myNets, theirCert)
  168. hm.unlockedAddHostInfo(hostinfo, f)
  169. return &flowTestPeer{f: f, hostinfo: hostinfo, conn: conn, tun: tun}
  170. }
  171. // flowCaptureConn records every packet sent, in order, as copies.
  172. type flowCaptureConn struct {
  173. udp.NoopConn
  174. mu sync.Mutex
  175. pkts [][]byte
  176. addrs []netip.AddrPort
  177. // delay, when set, slows every WriteBatch down so batches pile up in flight
  178. delay time.Duration
  179. }
  180. func (c *flowCaptureConn) WriteBatch(bufs [][]byte, addrs []netip.AddrPort) (int, error) {
  181. time.Sleep(c.delay)
  182. c.mu.Lock()
  183. defer c.mu.Unlock()
  184. for i, b := range bufs {
  185. c.pkts = append(c.pkts, append([]byte(nil), b...))
  186. c.addrs = append(c.addrs, addrs[i])
  187. }
  188. return len(bufs), nil
  189. }
  190. func (c *flowCaptureConn) WriteTo(b []byte, addr netip.AddrPort) error {
  191. _, err := c.WriteBatch([][]byte{b}, []netip.AddrPort{addr})
  192. return err
  193. }
  194. func (c *flowCaptureConn) captured() [][]byte {
  195. c.mu.Lock()
  196. defer c.mu.Unlock()
  197. return append([][]byte(nil), c.pkts...)
  198. }
  199. // flowTunCapture is a tun queue that records every plaintext write.
  200. type flowTunCapture struct {
  201. mu sync.Mutex
  202. writes [][]byte
  203. // delay, when set, slows every write down
  204. delay time.Duration
  205. }
  206. func (c *flowTunCapture) Read() ([]tio.Packet, error) { return nil, io.EOF }
  207. func (c *flowTunCapture) Close() error { return nil }
  208. func (c *flowTunCapture) Write(b []byte) (int, error) {
  209. time.Sleep(c.delay)
  210. c.mu.Lock()
  211. defer c.mu.Unlock()
  212. c.writes = append(c.writes, append([]byte(nil), b...))
  213. return len(b), nil
  214. }
  215. // segments decodes what reached the tun. The coalescer may have merged segments back into superpackets, so this
  216. // walks each payload in MSS steps.
  217. func (c *flowTunCapture) segments(t testing.TB) []decodedSeg {
  218. t.Helper()
  219. c.mu.Lock()
  220. defer c.mu.Unlock()
  221. var out []decodedSeg
  222. for _, w := range c.writes {
  223. pay := w[40:]
  224. require.Zero(t, len(pay)%flowTestMSS, "tun write payload %d bytes", len(pay))
  225. for off := 0; off < len(pay); off += flowTestMSS {
  226. out = append(out, decodeSeg(pay[off:]))
  227. }
  228. }
  229. return out
  230. }
  231. // decryptWire checks every wire packet decrypts on the receiver, in order, with strictly increasing counters, and
  232. // returns what each one carried. It uses its own replay window and copies, so the wire can still be fed to the
  233. // receiver afterwards.
  234. func decryptWire(t testing.TB, receiver *flowTestPeer, wire [][]byte) []decodedSeg {
  235. t.Helper()
  236. peer := receiver.hostinfo.ConnectionState
  237. ci := &ConnectionState{dKey: peer.dKey, window: NewBits(peer.window.length)}
  238. nb := make([]byte, 12)
  239. var last uint64
  240. out := make([]decodedSeg, 0, len(wire))
  241. for i, pkt := range wire {
  242. h := &header.H{}
  243. require.NoError(t, h.Parse(pkt))
  244. require.Equal(t, header.Message, h.Type)
  245. require.Equal(t, receiver.hostinfo.localIndexId, h.RemoteIndex)
  246. require.Greater(t, h.MessageCounter, last, "wire packet %d: counter %d after %d", i, h.MessageCounter, last)
  247. last = h.MessageCounter
  248. plain, err := ci.Decrypt(receiver.f.l, h.MessageCounter, append([]byte(nil), pkt...), nb)
  249. require.NoError(t, err, "wire packet %d", i)
  250. out = append(out, decodeSeg(plain[40:]))
  251. }
  252. return out
  253. }
  254. // flowBatchQueue is a handoffQueue that hands out scripted Read batches, then fails with io.EOF. Every batch is copied
  255. // into the current receive buffer, so a test sees the buffer ownership the Offload queue has.
  256. type flowBatchQueue struct {
  257. batches [][]tio.Packet
  258. rxBuf []byte
  259. out []tio.Packet
  260. closed chan struct{}
  261. // reads counts the batches handed out
  262. reads atomic.Int64
  263. }
  264. func newFlowBatchQueue(batches [][]tio.Packet) *flowBatchQueue {
  265. return &flowBatchQueue{batches: batches, rxBuf: make([]byte, 8*65536), closed: make(chan struct{})}
  266. }
  267. func (q *flowBatchQueue) Read() ([]tio.Packet, error) {
  268. select {
  269. case <-q.closed:
  270. return nil, os.ErrClosed
  271. default:
  272. }
  273. n := int(q.reads.Load())
  274. if n == len(q.batches) {
  275. return nil, io.EOF
  276. }
  277. q.out = q.out[:0]
  278. off := 0
  279. for _, p := range q.batches[n] {
  280. l := copy(q.rxBuf[off:], p.Bytes)
  281. q.out = append(q.out, tio.Packet{Bytes: q.rxBuf[off : off+l : off+l], GSO: p.GSO})
  282. off += l
  283. }
  284. q.reads.Add(1)
  285. return q.out, nil
  286. }
  287. func (q *flowBatchQueue) SwapRxBuf(buf []byte) []byte {
  288. if buf == nil {
  289. buf = make([]byte, len(q.rxBuf))
  290. }
  291. old := q.rxBuf
  292. q.rxBuf = buf
  293. // Scribble over the buffer we are handed, a staged batch that still pointed into it would be corrupted
  294. for i := range buf {
  295. buf[i] = 0xa5
  296. }
  297. return old
  298. }
  299. func (q *flowBatchQueue) Write(b []byte) (int, error) { return len(b), nil }
  300. func (q *flowBatchQueue) Close() error { close(q.closed); return nil }
  301. // delayedCipher changes scheduling through the real cipher interface, only in tests.
  302. type delayedCipher struct {
  303. noiseutil.CipherState
  304. before func(uint64)
  305. }
  306. func (c delayedCipher) EncryptDanger(out, ad, plain []byte, n uint64, nb []byte) ([]byte, error) {
  307. c.before(n)
  308. return c.CipherState.EncryptDanger(out, ad, plain, n, nb)
  309. }
  310. func (c delayedCipher) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {
  311. c.before(n)
  312. return c.CipherState.DecryptDanger(out, ad, ciphertext, n, nb)
  313. }
  314. func delayCrypto(peer *flowTestPeer, before func(uint64)) {
  315. ci := peer.hostinfo.ConnectionState
  316. ci.eKey = delayedCipher{ci.eKey, before}
  317. ci.dKey = delayedCipher{ci.dKey, before}
  318. }
  319. // stagedOn enables staging with n workers.
  320. func stagedOn(n int) stagedConfig {
  321. return stagedConfig{workers: n, tx: true, rx: true, txSlots: stagedTxSlots, rxSlots: 6}
  322. }
  323. // adaptive is staging with the inline threshold and gate hold parseStagedConfig sets.
  324. func adaptive(n int) stagedConfig {
  325. return stagedConfig{workers: n, tx: true, rx: true, inlineBelow: 256, gateHold: stagedGateHold,
  326. txSlots: stagedTxSlots, rxSlots: 6}
  327. }
  328. func stagedName(sc stagedConfig) string {
  329. switch {
  330. case sc.workers == 0:
  331. return "inline"
  332. case sc.inlineBelow > 0:
  333. return "adaptive"
  334. default:
  335. return "staged"
  336. }
  337. }
  338. // send takes one read through the pipeline, as run does.
  339. func (p *stagedTx) send(pkts []tio.Packet) {
  340. p.handle(pkts, &firewall.ParsedPacket{}, make([]byte, mtu), nil)
  341. }
  342. // drain waits until every batch p has in flight is on the wire.
  343. func (p *stagedTx) drain() {
  344. for p.inFlight > 0 {
  345. p.release(<-p.returned)
  346. }
  347. }
  348. // TestFlowStagedFallback: a tun queue that cannot hand off its buffer, or a conn that cannot hand off its batches,
  349. // keeps that direction on the inline path.
  350. func TestFlowStagedFallback(t *testing.T) {
  351. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  352. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, stagedOn(3))
  353. batches, want := flowTestBatches(10, 2, 1, 4)
  354. tx := metrics.GetOrRegisterCounter("flow.tx.batches", nil)
  355. tx0, rx0 := tx.Count(), rxBatches.Count()
  356. sender.f.listenIn(plainQueue{newFlowBatchQueue(batches)}, 0)
  357. wire := sender.conn.captured()
  358. require.Equal(t, want, decryptWire(t, receiver, wire))
  359. require.Equal(t, tx0, tx.Count())
  360. receiver.receive(plainConn{&rxConn{wire: wire, batchLen: 16}})
  361. require.Equal(t, want, receiver.tun.segments(t))
  362. require.Equal(t, rx0, rxBatches.Count())
  363. }
  364. // plainQueue and plainConn hide SwapRxBuf and ListenOutHandoff, so they cannot hand off their batches.
  365. type plainQueue struct{ tio.Queue }
  366. // wireFor produces the wire packets the sender emits for batches, inline.
  367. func wireFor(t testing.TB, sender *flowTestPeer, batches [][]tio.Packet) [][]byte {
  368. t.Helper()
  369. sc := sender.f.staged
  370. sender.f.staged = stagedConfig{}
  371. sender.f.listenIn(newFlowBatchQueue(batches), 0)
  372. sender.f.staged = sc
  373. wire := sender.conn.captured()
  374. sender.conn.pkts, sender.conn.addrs = nil, nil
  375. return wire
  376. }
  377. // rxConn plays wire back to listenOut through the buffer handoff, in batches of batchLen or of the scripted sizes
  378. // (the last one repeating), and captures what is sent back. Every buffer handed back is scribbled over before it is
  379. // read into, so a stage still using one shows corruption. from, when set, picks each packet's source address by its
  380. // index in wire.
  381. type rxConn struct {
  382. flowCaptureConn
  383. wire [][]byte
  384. batchLen int
  385. sizes []int
  386. from func(i int) netip.AddrPort
  387. swaps int
  388. }
  389. func (c *rxConn) ListenOut(r udp.EncReader, flush func()) error {
  390. return c.ListenOutHandoff(r, func(bufs [][]byte) [][]byte {
  391. flush()
  392. return bufs
  393. })
  394. }
  395. func (c *rxConn) ListenOutHandoff(r udp.EncReader, handoff func(bufs [][]byte) [][]byte) error {
  396. n := c.batchLen
  397. for _, size := range c.sizes {
  398. n = max(n, size)
  399. }
  400. bufs := make([][]byte, n)
  401. for i := range bufs {
  402. bufs[i] = make([]byte, udp.MTU)
  403. }
  404. for off, k := 0, 0; off < len(c.wire); k++ {
  405. size := c.batchLen
  406. if len(c.sizes) > 0 {
  407. size = c.sizes[min(k, len(c.sizes)-1)]
  408. }
  409. end := min(off+size, len(c.wire))
  410. for i, p := range c.wire[off:end] {
  411. n := copy(bufs[i], p)
  412. from := flowTestUnder
  413. if c.from != nil {
  414. from = c.from(off + i)
  415. }
  416. r(from, bufs[i][:n:n])
  417. }
  418. next := handoff(bufs)
  419. if len(next) != len(bufs) {
  420. panic("handoff returned the wrong number of buffers")
  421. }
  422. bufs = next
  423. for _, b := range bufs {
  424. for j := range b[:udp.MTU] {
  425. b[j] = 0x5a
  426. }
  427. }
  428. c.swaps++
  429. off = end
  430. }
  431. return os.ErrClosed
  432. }
  433. type plainConn struct{ udp.Conn }
  434. var rxBatches = metrics.GetOrRegisterCounter("flow.rx.batches", nil)
  435. // receive runs the peer's listenOut over conn until conn runs out.
  436. func (p *flowTestPeer) receive(conn udp.Conn) {
  437. p.f.writers = []udp.Conn{conn}
  438. p.f.outside = conn
  439. p.f.listenOut(0)
  440. }
  441. // TestFlowStagedFirewallParity: both paths apply the same outbound and inbound policy. An authenticated inbound
  442. // denial still consumes its replay counter, exactly as inline Decrypt does.
  443. func TestFlowStagedFirewallParity(t *testing.T) {
  444. for _, sc := range []stagedConfig{{}, stagedOn(2)} {
  445. t.Run(stagedName(sc), func(t *testing.T) {
  446. batches, _ := flowTestBatches(4, 3, 1, 8)
  447. sender, _ := newFlowTestPair(t, noise.CipherChaChaPoly, sc)
  448. sender.f.firewall.OutRules = newFirewallTable()
  449. sender.f.listenIn(newFlowBatchQueue(batches), 0)
  450. require.Empty(t, sender.conn.captured())
  451. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, sc)
  452. wire := wireFor(t, sender, batches)
  453. receiver.f.firewall.InRules = newFirewallTable()
  454. receiver.receive(&rxConn{wire: wire, batchLen: 64})
  455. require.Empty(t, receiver.tun.writes)
  456. for _, pkt := range wire {
  457. h := &header.H{}
  458. require.NoError(t, h.Parse(pkt))
  459. require.False(t, receiver.hostinfo.ConnectionState.window.Check(receiver.f.l, h.MessageCounter))
  460. }
  461. })
  462. }
  463. }
  464. // TestFlowStagedCipherFallback runs the parsed configuration with the real AES cipher: transmit falls back to inline
  465. // when the build needs nonce-ordered encryption (BoringCrypto, FIPS), receive decryption stays staged.
  466. func TestFlowStagedCipherFallback(t *testing.T) {
  467. l := test.NewLogger()
  468. c := config.NewC(l)
  469. c.Settings = map[string]any{"tun": map[string]any{"staged": map[string]any{"workers": 2}}}
  470. sender, receiver := newFlowTestPair(t, noiseutil.CipherAESGCM, parseStagedConfig(c, l))
  471. require.Equal(t, !noiseutil.EncryptLockNeeded, sender.f.staged.tx)
  472. require.True(t, sender.f.staged.rx)
  473. batches, want := flowTestBatches(4, 8, 1, 45)
  474. tx := metrics.GetOrRegisterCounter("flow.tx.batches", nil)
  475. tx0, rx0 := tx.Count(), rxBatches.Count()
  476. sender.f.listenIn(newFlowBatchQueue(batches), 0)
  477. if noiseutil.EncryptLockNeeded {
  478. require.Equal(t, tx0, tx.Count())
  479. }
  480. receiver.receive(&rxConn{wire: sender.conn.captured(), batchLen: 512})
  481. require.Greater(t, rxBatches.Count(), rx0)
  482. require.Equal(t, want, receiver.tun.segments(t))
  483. }