Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to 20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии, на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его бампом. За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go, stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack. Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии: handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h, а performHandshake так же зануляет h и отпускает мьютекс до Close(). Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк). Red/green проверен на новой базе: без guard'а тест падает с той же nil-паникой, что в полевом крашдампе; с ним зелёный.
165 lines
4.7 KiB
Go
165 lines
4.7 KiB
Go
// Copyright 2026 The gVisor Authors.
|
|
//
|
|
// 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
|
|
//
|
|
// https://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 sandbox
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// BundleConfig holds configuration for creating an OCI bundle.
|
|
type BundleConfig struct {
|
|
ID string
|
|
RuntimeDir string
|
|
EnableNetworking bool
|
|
Mounts []Mount
|
|
Env []string
|
|
Annotations map[string]string
|
|
WorkingDir string
|
|
Hostname string
|
|
}
|
|
|
|
// NewBundle creates a temporary OCI bundle on the fly with the given configuration.
|
|
func NewBundle(cfg BundleConfig) (string, error) {
|
|
// Create a bundle directory for the sandbox.
|
|
bundleDir := filepath.Join(cfg.RuntimeDir, cfg.ID)
|
|
rootfsDir := filepath.Join(bundleDir, "rootfs")
|
|
|
|
if err := os.MkdirAll(rootfsDir, 0o755); err != nil {
|
|
return "", fmt.Errorf("failed to create bundle directories: %w", err)
|
|
}
|
|
|
|
// Define the OCI Specification programmatically.
|
|
namespaces := []specs.LinuxNamespace{
|
|
{Type: specs.PIDNamespace},
|
|
{Type: specs.MountNamespace},
|
|
{Type: specs.UTSNamespace},
|
|
{Type: specs.IPCNamespace},
|
|
}
|
|
|
|
if os.Geteuid() != 0 {
|
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UserNamespace})
|
|
}
|
|
if cfg.EnableNetworking {
|
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace})
|
|
}
|
|
|
|
spec := &specs.Spec{
|
|
Version: "1.0.0",
|
|
Annotations: cfg.Annotations,
|
|
Root: &specs.Root{
|
|
Path: "rootfs",
|
|
Readonly: false,
|
|
},
|
|
Process: &specs.Process{
|
|
Terminal: false,
|
|
User: specs.User{UID: 0, GID: 0},
|
|
// Keeps the sandbox alive in the background.
|
|
Args: []string{"sleep", "infinity"},
|
|
Cwd: cfg.WorkingDir,
|
|
},
|
|
Mounts: []specs.Mount{
|
|
// Mandatory Linux API Filesystems
|
|
{Destination: "/proc", Type: "proc", Source: "proc"},
|
|
{Destination: "/dev", Type: "tmpfs", Source: "tmpfs"},
|
|
},
|
|
// enable basic namespaces for gVisor.
|
|
Linux: &specs.Linux{
|
|
Namespaces: namespaces,
|
|
},
|
|
Hostname: cfg.Hostname,
|
|
}
|
|
|
|
baseEnv := []string{"PATH=/bin:/usr/bin:/usr/local/bin"}
|
|
spec.Process.Env = append(baseEnv, cfg.Env...)
|
|
|
|
if os.Geteuid() != 0 {
|
|
spec.Linux.UIDMappings = []specs.LinuxIDMapping{
|
|
{ContainerID: 0, HostID: uint32(os.Geteuid()), Size: 1},
|
|
}
|
|
spec.Linux.GIDMappings = []specs.LinuxIDMapping{
|
|
{ContainerID: 0, HostID: uint32(os.Getegid()), Size: 1},
|
|
}
|
|
}
|
|
|
|
// Map host binaries & libraries as readonly. The binaries will be
|
|
// executed in gVisor sandbox, not on the host.
|
|
for _, p := range []string{"/bin", "/usr", "/lib", "/lib64", "/etc/alternatives"} {
|
|
if _, err := os.Stat(p); err == nil {
|
|
opts := []string{"rbind", "ro", "nosuid", "nodev"}
|
|
if p == "/etc/alternatives" {
|
|
opts = []string{"rbind", "ro"}
|
|
}
|
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
|
Destination: p,
|
|
Type: "bind",
|
|
Source: p,
|
|
Options: opts,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Add custom mounts. Custom mounts overriding default host mounts create duplicate OCI
|
|
// entries. The later entry overrides the earlier one, as expected by OCI specs.
|
|
for _, m := range cfg.Mounts {
|
|
switch m.Type {
|
|
case MountTypeBind:
|
|
opts := []string{"rbind"}
|
|
if m.ReadOnly {
|
|
opts = append(opts, "ro")
|
|
} else {
|
|
opts = append(opts, "rw")
|
|
}
|
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
|
Destination: filepath.Clean(m.Destination),
|
|
Source: filepath.Clean(m.Source),
|
|
Type: "bind",
|
|
Options: opts,
|
|
})
|
|
case MountTypeTmpfs:
|
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
|
Destination: filepath.Clean(m.Destination),
|
|
Source: "tmpfs",
|
|
Type: "tmpfs",
|
|
})
|
|
case MountTypeProc:
|
|
spec.Mounts = append(spec.Mounts, specs.Mount{
|
|
Destination: filepath.Clean(m.Destination),
|
|
Source: "proc",
|
|
Type: "proc",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Write the spec to config.json
|
|
configPath := filepath.Join(bundleDir, "config.json")
|
|
configFile, err := os.Create(configPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create config.json: %w", err)
|
|
}
|
|
defer configFile.Close()
|
|
|
|
encoder := json.NewEncoder(configFile)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(spec); err != nil {
|
|
return "", fmt.Errorf("failed to encode config.json: %w", err)
|
|
}
|
|
|
|
return bundleDir, nil
|
|
}
|