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) }