diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index 18782c480..ce468df8e 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -94,6 +94,12 @@ func do(ctx context.Context) error { go reap.ReapChildren(nil, nil, nil, &reapLock) slog.InfoContext(ctx, "Child process reaper launched") + // Validate before opening the socket so the operator sees a clear + // message rather than the kernel's cryptic "bind: invalid argument". + if err := ateompath.ValidateAteomSocketPath(*podNamespace, *podName); err != nil { + return err + } + // Clean up any old socket. sockPath := ateompath.AteomSocketPath(*podNamespace, *podName) if err := os.RemoveAll(sockPath); err != nil { diff --git a/internal/ateompath/ateompath.go b/internal/ateompath/ateompath.go index a0a7ba669..3fae57228 100644 --- a/internal/ateompath/ateompath.go +++ b/internal/ateompath/ateompath.go @@ -16,9 +16,15 @@ package ateompath import ( + "fmt" "path/filepath" ) +// MaxUnixSocketPathLen is the practical Linux limit for unix-domain socket +// paths. The kernel's sockaddr_un.sun_path is 108 bytes including the trailing +// NUL, leaving 107 usable bytes. Bind fails with EINVAL above this. +const MaxUnixSocketPathLen = 107 + const ( // The base path. This is both the path of the root shared folder on the // host filesystem, and when it is mounted into ateom and atelet containers. @@ -49,6 +55,22 @@ func AteomSocketPath(ateomNamespace, ateomName string) string { ) } +// ValidateAteomSocketPath returns a descriptive error when the socket path +// derived from ateomNamespace and ateomName would exceed Linux's unix-socket +// limit. Calling net.Listen("unix", ...) with an over-limit path otherwise +// fails with the cryptic "bind: invalid argument". +func ValidateAteomSocketPath(ateomNamespace, ateomName string) error { + p := AteomSocketPath(ateomNamespace, ateomName) + if len(p) > MaxUnixSocketPathLen { + return fmt.Errorf( + "ateom socket path %q is %d bytes, exceeds Linux unix-socket limit of %d: shorten the namespace or pod name (%d + %d = %d chars used for namespace + name)", + p, len(p), MaxUnixSocketPathLen, + len(ateomNamespace), len(ateomName), len(ateomNamespace)+len(ateomName), + ) + } + return nil +} + func AteomNetNSName(ateomNamespace, ateomName string) string { return "ateom:" + ateomNamespace + ":" + ateomName } diff --git a/internal/ateompath/ateompath_test.go b/internal/ateompath/ateompath_test.go new file mode 100644 index 000000000..8cb8e9cd6 --- /dev/null +++ b/internal/ateompath/ateompath_test.go @@ -0,0 +1,71 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ateompath + +import ( + "strings" + "testing" +) + +func TestValidateAteomSocketPath(t *testing.T) { + tests := []struct { + name string + namespace string + podName string + wantErr bool + }{ + { + name: "short names well under the limit", + namespace: "ate-demo-counter", + podName: "counter-deployment-abcd1234-xyzw1", + wantErr: false, + }, + { + name: "exactly at the limit", + namespace: strings.Repeat("a", 25), + podName: strings.Repeat("b", 45), + wantErr: false, + }, + { + name: "one byte over the limit", + namespace: strings.Repeat("a", 25), + podName: strings.Repeat("b", 46), + wantErr: true, + }, + { + name: "the reproducer from the original bug report", + namespace: "ate-demo-lovable-sandbox", + podName: "lovable-sandbox-pool-deployment-5797879cd7-2n7wb", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateAteomSocketPath(tt.namespace, tt.podName) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateAteomSocketPath(%q, %q) err=%v, wantErr=%v", + tt.namespace, tt.podName, err, tt.wantErr) + } + if tt.wantErr && err != nil { + // Error message should mention the limit so an operator can + // figure out by how much to shorten. + msg := err.Error() + if !strings.Contains(msg, "107") { + t.Errorf("error message %q does not reference the limit (107)", msg) + } + } + }) + } +}