| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- //go:build linux && !android && !e2e_testing
- package nebula
- import (
- "fmt"
- "testing"
- "time"
- "github.com/flynn/noise"
- "github.com/rcrowley/go-metrics"
- "github.com/slackhq/nebula/overlay/tio"
- "github.com/slackhq/nebula/udp"
- "github.com/stretchr/testify/require"
- "go.uber.org/goleak"
- )
- // gated is staging with an inline threshold of 4 segments and the given gate hold.
- func gated(hold int) stagedConfig {
- sc := adaptive(2)
- sc.inlineBelow, sc.gateHold = 4, hold
- return sc
- }
- // gateCounters snapshots the transmit counters a gate test checks.
- type gateCounters struct{ inline, batches, switches int64 }
- func readGateCounters() gateCounters {
- return gateCounters{
- inline: metrics.GetOrRegisterCounter("flow.tx.inline", nil).Count(),
- batches: metrics.GetOrRegisterCounter("flow.tx.batches", nil).Count(),
- switches: metrics.GetOrRegisterCounter("flow.tx.gate_switches", nil).Count(),
- }
- }
- func (c gateCounters) since(c0 gateCounters) gateCounters {
- return gateCounters{c.inline - c0.inline, c.batches - c0.batches, c.switches - c0.switches}
- }
- // gateReads builds reads of small (3 segments, below the threshold) or large (2 superpackets of 9) shape, in order,
- // for one flow, and the segments they carry.
- func gateReads(large ...bool) ([][]tio.Packet, []decodedSeg) {
- var reads [][]tio.Packet
- var want []decodedSeg
- seq := uint32(0)
- for _, l := range large {
- per, segs := 1, 3
- if l {
- per, segs = 2, 9
- }
- var b []tio.Packet
- for range per {
- b = append(b, flowTCPSuperpacket(0, seq, segs))
- for s := range segs {
- want = append(want, decodedSeg{seq: seq, idx: uint16(s)})
- }
- seq++
- }
- reads = append(reads, b)
- }
- return reads, want
- }
- func repeatBool(v bool, n int) []bool {
- out := make([]bool, n)
- for i := range out {
- out[i] = v
- }
- return out
- }
- // TestFlowStagedTXGateSwitch forces the gate from inline to fanout and back, with a slow socket and slow
- // workers so the fanout batches are still in flight when the small reads arrive: wire order is read order, counters
- // are contiguous, and each switch happens on exactly the gate_hold-th read that wants it.
- func TestFlowStagedTXGateSwitch(t *testing.T) {
- defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
- const hold = 3
- for _, routines := range []int{1, 2} {
- t.Run(fmt.Sprintf("routines=%d", routines), func(t *testing.T) {
- sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, gated(hold))
- sender.f.routines = routines
- sender.f.writers = []udp.Conn{sender.conn, sender.conn}
- delayCrypto(sender, func(n uint64) {
- if n%7 == 0 {
- time.Sleep(time.Millisecond)
- }
- })
- sender.conn.delay = 4 * time.Millisecond
- // 4 small inline, 2 large held inline, 6 large fanout from the switch on, 2 small held in fanout, then
- // the switch back drains and 3 small go inline
- shape := append(repeatBool(false, 4), repeatBool(true, 8)...)
- shape = append(shape, repeatBool(false, 5)...)
- reads, want := gateReads(shape...)
- ci := sender.hostinfo.ConnectionState
- start := ci.messageCounter.Load()
- c0 := readGateCounters()
- p := newStagedTx(sender.f, newFlowBatchQueue(nil), 0)
- for i, r := range reads {
- p.send(r)
- if i == 13 {
- require.Positive(t, p.inFlight, "fanout batches must still be in flight when the small reads start")
- }
- if i == 14 {
- require.Zero(t, p.inFlight, "switching to inline drains the pipeline")
- }
- }
- p.close()
- require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()), "wire order must match read order")
- require.Equal(t, start+uint64(len(want)), ci.messageCounter.Load(), "every reserved counter is used once")
- require.Equal(t, gateCounters{inline: 4 + 2 + 3, batches: 6 + 2, switches: 2}, readGateCounters().since(c0))
- require.Zero(t, ci.txBudget.state.Load())
- })
- }
- }
- // TestFlowStagedTXGateHold: the gate never switches before gate_hold consecutive reads want the other mode, and
- // a read that wants the current mode restarts the count.
- func TestFlowStagedTXGateHold(t *testing.T) {
- defer goleak.VerifyNone(t, goleak.IgnoreCurrent())
- const hold = 5
- sender, receiver := newFlowTestPair(t, noise.CipherChaChaPoly, gated(hold))
- shape := append(repeatBool(true, hold-1), false)
- shape = append(shape, repeatBool(true, hold-1)...)
- reads, want := gateReads(append(shape, true)...)
- c0 := readGateCounters()
- p := newStagedTx(sender.f, newFlowBatchQueue(nil), 0)
- for _, r := range reads[:len(reads)-1] {
- p.send(r)
- }
- require.Equal(t, gateCounters{inline: 2*hold - 1}, readGateCounters().since(c0), "no switch before gate_hold")
- p.send(reads[len(reads)-1])
- require.Equal(t, gateCounters{inline: 2*hold - 1, batches: 1, switches: 1}, readGateCounters().since(c0))
- p.close()
- require.Equal(t, want, decryptWire(t, receiver, sender.conn.captured()))
- }
|