mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-11 10:23:17 +08:00
fix(ateompath): validate ateom socket path length (#92)
## Symptom When `<namespace>:<pod-name>` exceeds 107 bytes, `ateom-gvisor` exits with `bind: invalid argument` and the controller surfaces a generic `DeadlineExceeded`. ## Cause Linux limits unix-socket paths to 107 bytes (`sockaddr_un.sun_path`). A pod's ReplicaSet hash can jiggle between 8 and 10 chars across rollouts, so a demo that fits at 8 silently breaks at 10 with no clear error. ## Fix Add `ValidateAteomSocketPath` (with unit tests) and call it from `ateom-gvisor` before the bind, so the operator sees the real cause in the pod log instead of a kernel EINVAL.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user