mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-15 12:56:52 +01:00
SO_RCVBUFFORCE/SO_SNDBUFFORCE need CAP_NET_ADMIN. Without it (non-root,
or root under Docker's default caps) listen.read_buffer/write_buffer
failed with EPERM and the socket kept the system default. On EPERM retry
with the plain option, which the kernel clamps to net.core.{r,w}mem_max,
and warn when the effective size is smaller than requested.
125 lines
3.6 KiB
Go
125 lines
3.6 KiB
Go
//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)
|
|
})
|
|
}
|
|
}
|