1
0

flow_gate_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. //go:build linux && !android && !e2e_testing
  2. package nebula
  3. import (
  4. "fmt"
  5. "testing"
  6. "time"
  7. "github.com/flynn/noise"
  8. "github.com/rcrowley/go-metrics"
  9. "github.com/slackhq/nebula/overlay/tio"
  10. "github.com/slackhq/nebula/udp"
  11. "github.com/stretchr/testify/require"
  12. "go.uber.org/goleak"
  13. )
  14. // gated is staging with an inline threshold of 4 segments and the given gate hold.
  15. func gated(hold int) stagedConfig {
  16. sc := adaptive(2)
  17. sc.inlineBelow, sc.gateHold = 4, hold
  18. return sc
  19. }
  20. // gateCounters snapshots the transmit counters a gate test checks.
  21. type gateCounters struct{ inline, batches, switches int64 }
  22. func readGateCounters() gateCounters {
  23. return gateCounters{
  24. inline: metrics.GetOrRegisterCounter("flow.tx.inline", nil).Count(),
  25. batches: metrics.GetOrRegisterCounter("flow.tx.batches", nil).Count(),
  26. switches: metrics.GetOrRegisterCounter("flow.tx.gate_switches", nil).Count(),
  27. }
  28. }
  29. func (c gateCounters) since(c0 gateCounters) gateCounters {
  30. return gateCounters{c.inline - c0.inline, c.batches - c0.batches, c.switches - c0.switches}
  31. }
  32. // gateReads builds reads of small (3 segments, below the threshold) or large (2 superpackets of 9) shape, in order,
  33. // for one flow, and the segments they carry.
  34. func gateReads(large ...bool) ([][]tio.Packet, []decodedSeg) {
  35. var reads [][]tio.Packet
  36. var want []decodedSeg
  37. seq := uint32(0)
  38. for _, l := range large {
  39. per, segs := 1, 3
  40. if l {
  41. per, segs = 2, 9
  42. }
  43. var b []tio.Packet
  44. for range per {
  45. b = append(b, flowTCPSuperpacket(0, seq, segs))
  46. for s := range segs {
  47. want = append(want, decodedSeg{seq: seq, idx: uint16(s)})
  48. }
  49. seq++
  50. }
  51. reads = append(reads, b)
  52. }
  53. return reads, want
  54. }
  55. func repeatBool(v bool, n int) []bool {
  56. out := make([]bool, n)
  57. for i := range out {
  58. out[i] = v
  59. }
  60. return out
  61. }
  62. // TestFlowStagedTXGateSwitch forces the gate from inline to fanout and back, with a slow socket and slow
  63. // workers so the fanout batches are still in flight when the small reads arrive: wire order is read order, counters
  64. // are contiguous, and each switch happens on exactly the gate_hold-th read that wants it.
  65. func TestFlowStagedTXGateSwitch(t *testing.T) {
  66. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  67. const hold = 3
  68. for _, routines := range []int{1, 2} {
  69. t.Run(fmt.Sprintf("routines=%d", routines), func(t *testing.T) {
  70. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, gated(hold))
  71. sender.f.routines = routines
  72. sender.f.writers = []udp.Conn{sender.conn, sender.conn}
  73. delayCrypto(sender, func(n uint64) {
  74. if n%7 == 0 {
  75. time.Sleep(time.Millisecond)
  76. }
  77. })
  78. sender.conn.delay = 4 * time.Millisecond
  79. // 4 small inline, 2 large held inline, 6 large fanout from the switch on, 2 small held in fanout, then
  80. // the switch back drains and 3 small go inline
  81. shape := append(repeatBool(false, 4), repeatBool(true, 8)...)
  82. shape = append(shape, repeatBool(false, 5)...)
  83. reads, want := gateReads(shape...)
  84. ci := sender.hostinfo.ConnectionState
  85. start := ci.messageCounter.Load()
  86. c0 := readGateCounters()
  87. p := newStagedTx(sender.f, newFlowBatchQueue(nil), 0)
  88. for i, r := range reads {
  89. p.send(r)
  90. if i == 13 {
  91. require.Positive(t, p.inFlight, "fanout batches must still be in flight when the small reads start")
  92. }
  93. if i == 14 {
  94. require.Zero(t, p.inFlight, "switching to inline drains the pipeline")
  95. }
  96. }
  97. p.close()
  98. require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()), "wire order must match read order")
  99. require.Equal(t, start+uint64(len(want)), ci.messageCounter.Load(), "every reserved counter is used once")
  100. require.Equal(t, gateCounters{inline: 4 + 2 + 3, batches: 6 + 2, switches: 2}, readGateCounters().since(c0))
  101. require.Zero(t, ci.txBudget.state.Load())
  102. })
  103. }
  104. }
  105. // TestFlowStagedTXGateHold: the gate never switches before gate_hold consecutive reads want the other mode, and
  106. // a read that wants the current mode restarts the count.
  107. func TestFlowStagedTXGateHold(t *testing.T) {
  108. defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
  109. const hold = 5
  110. sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, gated(hold))
  111. shape := append(repeatBool(true, hold-1), false)
  112. shape = append(shape, repeatBool(true, hold-1)...)
  113. reads, want := gateReads(append(shape, true)...)
  114. c0 := readGateCounters()
  115. p := newStagedTx(sender.f, newFlowBatchQueue(nil), 0)
  116. for _, r := range reads[:len(reads)-1] {
  117. p.send(r)
  118. }
  119. require.Equal(t, gateCounters{inline: 2*hold - 1}, readGateCounters().since(c0), "no switch before gate_hold")
  120. p.send(reads[len(reads)-1])
  121. require.Equal(t, gateCounters{inline: 2*hold - 1, batches: 1, switches: 1}, readGateCounters().since(c0))
  122. p.close()
  123. require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()))
  124. }