flow.go 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package nebula
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "runtime"
  6. "sync"
  7. "time"
  8. "github.com/rcrowley/go-metrics"
  9. "github.com/slackhq/nebula/config"
  10. "github.com/slackhq/nebula/noiseutil"
  11. )
  12. // Staged processing (tun.staged.workers) moves crypto off each tun reader and udp listener onto worker goroutines,
  13. // behind which an ordered stage sends or writes every batch in read order. It reuses the inline packet handlers:
  14. // staging changes ownership and scheduling, not policy. Each routine has its own workers and ordered stages, so order
  15. // is per routine. Full queues block the reader. A read error drains the batches in flight, while shutdown waits for
  16. // the crypto and then drops packets whose destination has closed. TX uses eight slots and RX six; at listen.batch=64
  17. // with GRO their buffers take about 9 MiB and 24 MiB per routine. Small-MSS TX arenas can grow.
  18. //
  19. // Metrics are registered only for active pipelines. flow.{tx,rx}.batches counts reads handed to the ordered stage and
  20. // flow.{tx,rx}.inline those an idle reader handled itself (TX inline also counts oversized reads); their rates show
  21. // whether staging is used. flow.stage.<stage>.q<queue>.busy_ns locates the limiting stage: tx_read, tx_encrypt,
  22. // tx_send, rx_read, rx_decrypt or rx_write. Take rates; 1e9 ns/s fills a serial stage. These are elapsed times that
  23. // exclude read syscalls and downstream waits; descheduling inflates them and worker times add up across the pool.
  24. // Inline work counts at the reader; small RX batches sent to the writer include decryption in writer time.
  25. // stagedConfig is tun.staged. workers is per routine and direction; zero keeps processing inline.
  26. type stagedConfig struct {
  27. workers int
  28. tx bool
  29. rx bool
  30. // inlineBelow is the TX segment or RX packet count below which an idle reader skips the worker handoff
  31. inlineBelow int
  32. }
  33. // parseStagedConfig enables staging on Linux only; queues and conns that cannot hand off batches also stay inline.
  34. func parseStagedConfig(c *config.C, l *slog.Logger) stagedConfig {
  35. sc := stagedConfig{workers: c.GetInt("tun.staged.workers", 0)}
  36. if sc.workers <= 0 {
  37. return stagedConfig{}
  38. }
  39. if runtime.GOOS != "linux" {
  40. l.Warn("tun.staged is only supported on Linux; packet processing stays inline")
  41. return stagedConfig{}
  42. }
  43. sc.tx, sc.rx = true, true
  44. // 256 is the measured crossover
  45. sc.inlineBelow = 256
  46. if noiseutil.EncryptLockNeeded {
  47. l.Warn("tun.staged cannot encrypt in parallel with boringcrypto or fips140; transmit stays inline")
  48. sc.tx = false
  49. }
  50. l.Info("Staged packet processing enabled", "workers", sc.workers, "tx", sc.tx, "rx", sc.rx)
  51. return sc
  52. }
  53. // flowJob is one batch of crypto work. It owns its packet buffers until run returns; jobs and queues are reused.
  54. type flowJob interface{ run(nb []byte) }
  55. // flowPool runs one reader's crypto jobs. The reader is the only producer and closes the pool once its ordered stage
  56. // has drained.
  57. type flowPool struct {
  58. jobs chan flowJob
  59. wg sync.WaitGroup
  60. }
  61. func newFlowPool(workers, queueLen int, busy metrics.Counter) *flowPool {
  62. p := &flowPool{jobs: make(chan flowJob, queueLen)}
  63. for range workers {
  64. p.wg.Go(func() {
  65. nb := make([]byte, 12)
  66. for job := range p.jobs {
  67. t := time.Now()
  68. job.run(nb)
  69. busy.Inc(int64(time.Since(t)))
  70. }
  71. })
  72. }
  73. return p
  74. }
  75. func (p *flowPool) close() { close(p.jobs); p.wg.Wait() }
  76. // stageBusy is the busy time counter of one stage of routine q.
  77. func stageBusy(stage string, q int) metrics.Counter {
  78. return metrics.GetOrRegisterCounter(fmt.Sprintf("flow.stage.%s.q%d.busy_ns", stage, q), nil)
  79. }