From fd1cc09139ba046dccd7d2e3799909f3fa070df2 Mon Sep 17 00:00:00 2001 From: David Bond Date: Wed, 14 Oct 2026 11:43:53 +0100 Subject: [PATCH] k8s-operator/reconciler/peerrelay: add NET_ADMIN to peer relay pods (#21762) The relay server force-sets its UDP socket buffer sizes. The kernel requires the NET_ADMIN capability for this. Without it, tailscaled logs a warning and uses the kernel defaults. Dogfooding showed lower throughput and more dropped packets in that state. This commit adds NET_ADMIN to the tailscaled container in peer relay StatefulSets. A ProxyClass that sets a security context on the Tailscale container replaces the default, as for other proxy types. Fixes #21745 Change-Id: Ic84e243d7e6184afa4d0bb0c38c4be8eb899261b Signed-off-by: David Bond --- .../reconciler/peerrelay/peerrelay_test.go | 17 +++++++++++++++++ .../reconciler/peerrelay/statefulset.go | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/k8s-operator/reconciler/peerrelay/peerrelay_test.go b/k8s-operator/reconciler/peerrelay/peerrelay_test.go index 42915a590..19c29e18c 100644 --- a/k8s-operator/reconciler/peerrelay/peerrelay_test.go +++ b/k8s-operator/reconciler/peerrelay/peerrelay_test.go @@ -1228,6 +1228,18 @@ func assertStatefulSet(t *testing.T, fc client.Client, prName string, want *stat }) { t.Errorf("expected container to declare TCP port 9002 for the health check, got ports %v", c.Ports) } + + // The relay server needs NET_ADMIN to force-set its UDP socket buffer sizes. + if !hasCapability(c, "NET_ADMIN") { + t.Errorf("expected container to add the NET_ADMIN capability, got security context %+v", c.SecurityContext) + } +} + +func hasCapability(c corev1.Container, capability corev1.Capability) bool { + if c.SecurityContext == nil || c.SecurityContext.Capabilities == nil { + return false + } + return slices.Contains(c.SecurityContext.Capabilities.Add, capability) } func assertConfigSecrets(t *testing.T, fc client.Client, prName string, want []string) { @@ -2030,4 +2042,9 @@ func TestReconciler_AppliesProxyClass(t *testing.T) { if !foundEnv { t.Errorf("expected TS_DEBUG_FIREWALL_MODE=auto env, container env is %+v", c.Env) } + + // The ProxyClass does not set a security context, so the default NET_ADMIN capability must survive. + if !hasCapability(c, "NET_ADMIN") { + t.Errorf("expected container to keep the NET_ADMIN capability, got security context %+v", c.SecurityContext) + } } diff --git a/k8s-operator/reconciler/peerrelay/statefulset.go b/k8s-operator/reconciler/peerrelay/statefulset.go index 275a29459..13303e971 100644 --- a/k8s-operator/reconciler/peerrelay/statefulset.go +++ b/k8s-operator/reconciler/peerrelay/statefulset.go @@ -84,6 +84,14 @@ func (r *Reconciler) peerRelayStatefulSet(pr *tsapi.PeerRelay, replicas int32, p }, }) + // The relay server force-sets its UDP socket buffer sizes, which requires NET_ADMIN. Without it, tailscaled falls + // back to the kernel defaults and relay throughput suffers. A ProxyClass security context replaces this entirely. + ss.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{ + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{"NET_ADMIN"}, + }, + } + return tailscaled.ApplyProxyClass(ss, pc, managedLabelKeys, nil) }