|
|
@@ -0,0 +1,124 @@
|
|
|
+//go:build linux && !android && !e2e_testing
|
|
|
+
|
|
|
+package udp
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "fmt"
|
|
|
+ "log/slog"
|
|
|
+ "net/netip"
|
|
|
+ "os"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/slackhq/nebula/config"
|
|
|
+)
|
|
|
+
|
|
|
+func readSysctlInt(t *testing.T, path string) int {
|
|
|
+ t.Helper()
|
|
|
+ b, err := os.ReadFile(path)
|
|
|
+ if err != nil {
|
|
|
+ t.Skipf("can't read %s: %v", path, err)
|
|
|
+ }
|
|
|
+ n, err := strconv.Atoi(strings.TrimSpace(string(b)))
|
|
|
+ if err != nil {
|
|
|
+ t.Skipf("can't parse %s: %v", path, err)
|
|
|
+ }
|
|
|
+ return n
|
|
|
+}
|
|
|
+
|
|
|
+// TestSetBufferAboveSysctlMax asks for more than net.core.{r,w}mem_max. With
|
|
|
+// CAP_NET_ADMIN the FORCE variant takes it as is; without it we must fall back
|
|
|
+// to the plain option and get the clamped size instead of EPERM.
|
|
|
+func TestSetBufferAboveSysctlMax(t *testing.T) {
|
|
|
+ cases := []struct {
|
|
|
+ name string
|
|
|
+ sysctl string
|
|
|
+ set func(*StdConn, int) error
|
|
|
+ get func(*StdConn) (int, error)
|
|
|
+ }{
|
|
|
+ {"read", "/proc/sys/net/core/rmem_max", (*StdConn).SetRecvBuffer, (*StdConn).GetRecvBuffer},
|
|
|
+ {"write", "/proc/sys/net/core/wmem_max", (*StdConn).SetSendBuffer, (*StdConn).GetSendBuffer},
|
|
|
+ }
|
|
|
+ for _, tc := range cases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ sysMax := readSysctlInt(t, tc.sysctl)
|
|
|
+ c, err := NewListener(testLogger(), Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 1})
|
|
|
+ if err != nil {
|
|
|
+ t.Skipf("NewListener: %v", err)
|
|
|
+ }
|
|
|
+ sc := c.(*StdConn)
|
|
|
+ defer sc.Close()
|
|
|
+
|
|
|
+ want := sysMax + 1<<20
|
|
|
+ if err := tc.set(sc, want); err != nil {
|
|
|
+ t.Fatalf("set %d: %v", want, err)
|
|
|
+ }
|
|
|
+ got, err := tc.get(sc)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("get: %v", err)
|
|
|
+ }
|
|
|
+ // The kernel doubles the value; either path gives at least the clamped size.
|
|
|
+ if got < 2*sysMax {
|
|
|
+ t.Fatalf("effective size %d, want >= %d", got, 2*sysMax)
|
|
|
+ }
|
|
|
+ t.Logf("requested %d, %s=%d, effective %d (forced=%v)", want, tc.sysctl, sysMax, got, got >= 2*want)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestReloadConfigBufferLogLevel checks that a clamped buffer is a Warn, and a
|
|
|
+// buffer that fits is still the Info it always was.
|
|
|
+func TestReloadConfigBufferLogLevel(t *testing.T) {
|
|
|
+ sysMax := readSysctlInt(t, "/proc/sys/net/core/rmem_max")
|
|
|
+ if wmax := readSysctlInt(t, "/proc/sys/net/core/wmem_max"); wmax < sysMax {
|
|
|
+ sysMax = wmax
|
|
|
+ }
|
|
|
+ cases := []struct {
|
|
|
+ name string
|
|
|
+ size int
|
|
|
+ want []string
|
|
|
+ }{
|
|
|
+ {"fits", 64 * 1024, []string{"level=INFO msg=\"listen.read_buffer was set\"", "level=INFO msg=\"listen.write_buffer was set\""}},
|
|
|
+ {"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\""}},
|
|
|
+ }
|
|
|
+ for _, tc := range cases {
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ var buf bytes.Buffer
|
|
|
+ l := slog.New(slog.NewTextHandler(&buf, nil))
|
|
|
+ c, err := NewListener(l, Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Batch: 1})
|
|
|
+ if err != nil {
|
|
|
+ t.Skipf("NewListener: %v", err)
|
|
|
+ }
|
|
|
+ sc := c.(*StdConn)
|
|
|
+ defer sc.Close()
|
|
|
+
|
|
|
+ if tc.size > sysMax {
|
|
|
+ // With CAP_NET_ADMIN the FORCE variant takes any size, nothing gets limited.
|
|
|
+ if err := sc.SetRecvBuffer(tc.size); err == nil {
|
|
|
+ if got, _ := sc.GetRecvBuffer(); got >= 2*tc.size {
|
|
|
+ t.Skip("FORCE path took the full size, can't observe the limit")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cfg := config.NewC(l)
|
|
|
+ if err := cfg.LoadString(fmt.Sprintf("listen:\n read_buffer: %d\n write_buffer: %d\n", tc.size, tc.size)); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ buf.Reset()
|
|
|
+ sc.ReloadConfig(cfg)
|
|
|
+ out := buf.String()
|
|
|
+ for _, w := range tc.want {
|
|
|
+ if !strings.Contains(out, w) {
|
|
|
+ t.Errorf("missing %s in:\n%s", w, out)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if strings.Contains(out, "level=ERROR") {
|
|
|
+ t.Errorf("unexpected error log:\n%s", out)
|
|
|
+ }
|
|
|
+ t.Log(out)
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|