udp_linux_buffer_test.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. //go:build linux && !android && !e2e_testing
  2. package udp
  3. import (
  4. "bytes"
  5. "fmt"
  6. "log/slog"
  7. "net/netip"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "testing"
  12. "github.com/slackhq/nebula/config"
  13. )
  14. func readSysctlInt(t *testing.T, path string) int {
  15. t.Helper()
  16. b, err := os.ReadFile(path)
  17. if err != nil {
  18. t.Skipf("can't read %s: %v", path, err)
  19. }
  20. n, err := strconv.Atoi(strings.TrimSpace(string(b)))
  21. if err != nil {
  22. t.Skipf("can't parse %s: %v", path, err)
  23. }
  24. return n
  25. }
  26. // TestSetBufferAboveSysctlMax asks for more than net.core.{r,w}mem_max. With
  27. // CAP_NET_ADMIN the FORCE variant takes it as is; without it we must fall back
  28. // to the plain option and get the clamped size instead of EPERM.
  29. func TestSetBufferAboveSysctlMax(t *testing.T) {
  30. cases := []struct {
  31. name string
  32. sysctl string
  33. set func(*StdConn, int) error
  34. get func(*StdConn) (int, error)
  35. }{
  36. {"read", "/proc/sys/net/core/rmem_max", (*StdConn).SetRecvBuffer, (*StdConn).GetRecvBuffer},
  37. {"write", "/proc/sys/net/core/wmem_max", (*StdConn).SetSendBuffer, (*StdConn).GetSendBuffer},
  38. }
  39. for _, tc := range cases {
  40. t.Run(tc.name, func(t *testing.T) {
  41. sysMax := readSysctlInt(t, tc.sysctl)
  42. c, err := NewListener(testLogger(), Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 1})
  43. if err != nil {
  44. t.Skipf("NewListener: %v", err)
  45. }
  46. sc := c.(*StdConn)
  47. defer sc.Close()
  48. want := sysMax + 1<<20
  49. if err := tc.set(sc, want); err != nil {
  50. t.Fatalf("set %d: %v", want, err)
  51. }
  52. got, err := tc.get(sc)
  53. if err != nil {
  54. t.Fatalf("get: %v", err)
  55. }
  56. // The kernel doubles the value; either path gives at least the clamped size.
  57. if got < 2*sysMax {
  58. t.Fatalf("effective size %d, want >= %d", got, 2*sysMax)
  59. }
  60. t.Logf("requested %d, %s=%d, effective %d (forced=%v)", want, tc.sysctl, sysMax, got, got >= 2*want)
  61. })
  62. }
  63. }
  64. // TestReloadConfigBufferLogLevel checks that a clamped buffer is a Warn, and a
  65. // buffer that fits is still the Info it always was.
  66. func TestReloadConfigBufferLogLevel(t *testing.T) {
  67. sysMax := readSysctlInt(t, "/proc/sys/net/core/rmem_max")
  68. if wmax := readSysctlInt(t, "/proc/sys/net/core/wmem_max"); wmax < sysMax {
  69. sysMax = wmax
  70. }
  71. cases := []struct {
  72. name string
  73. size int
  74. want []string
  75. }{
  76. {"fits", 64 * 1024, []string{"level=INFO msg=\"listen.read_buffer was set\"", "level=INFO msg=\"listen.write_buffer was set\""}},
  77. {"too big", sysMax + 1<<20, []string{"level=WARN msg=\"listen.read_buffer was limited by the system\"", "level=WARN msg=\"listen.write_buffer was limited by the system\""}},
  78. }
  79. for _, tc := range cases {
  80. t.Run(tc.name, func(t *testing.T) {
  81. var buf bytes.Buffer
  82. l := slog.New(slog.NewTextHandler(&buf, nil))
  83. c, err := NewListener(l, Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 1})
  84. if err != nil {
  85. t.Skipf("NewListener: %v", err)
  86. }
  87. sc := c.(*StdConn)
  88. defer sc.Close()
  89. if tc.size > sysMax {
  90. // With CAP_NET_ADMIN the FORCE variant takes any size, nothing gets limited.
  91. if err := sc.SetRecvBuffer(tc.size); err == nil {
  92. if got, _ := sc.GetRecvBuffer(); got >= 2*tc.size {
  93. t.Skip("FORCE path took the full size, can't observe the limit")
  94. }
  95. }
  96. }
  97. cfg := config.NewC(l)
  98. if err := cfg.LoadString(fmt.Sprintf("listen:\n read_buffer: %d\n write_buffer: %d\n", tc.size, tc.size)); err != nil {
  99. t.Fatal(err)
  100. }
  101. buf.Reset()
  102. sc.ReloadConfig(cfg)
  103. out := buf.String()
  104. for _, w := range tc.want {
  105. if !strings.Contains(out, w) {
  106. t.Errorf("missing %s in:\n%s", w, out)
  107. }
  108. }
  109. if strings.Contains(out, "level=ERROR") {
  110. t.Errorf("unexpected error log:\n%s", out)
  111. }
  112. t.Log(out)
  113. })
  114. }
  115. }