summaryrefslogtreecommitdiff
path: root/internal/namespace/namespace.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/namespace/namespace.go')
-rw-r--r--internal/namespace/namespace.go152
1 files changed, 116 insertions, 36 deletions
diff --git a/internal/namespace/namespace.go b/internal/namespace/namespace.go
index 45eba73..368775f 100644
--- a/internal/namespace/namespace.go
+++ b/internal/namespace/namespace.go
@@ -32,6 +32,66 @@ import (
"golang.org/x/sys/unix"
)
+// MountOps abstracts the filesystem mount operations.
+type MountOps interface {
+ Mount(source, target, fstype string, flags uintptr, data string) error
+ Unmount(target string, flags int) error
+}
+
+// realMountOps is the production implementation using unix.Mount.
+type realMountOps struct{}
+
+func (r *realMountOps) Mount(source, target, fstype string, flags uintptr, data string) error {
+ return unix.Mount(source, target, fstype, flags, data)
+}
+func (r *realMountOps) Unmount(target string, flags int) error {
+ return unix.Unmount(target, flags)
+}
+
+// DefaultMountOps is the global instance used by the package functions.
+var DefaultMountOps MountOps = &realMountOps{}
+
+// FileSystem abstracts the basic filesystem operations used for isolation.
+type FileSystem interface {
+ Stat(name string) (os.FileInfo, error)
+ MkdirAll(path string, perm os.FileMode) error
+ CreateTemp(dir, pattern string) (*os.File, error)
+ MkdirTemp(dir, pattern string) (string, error)
+ Remove(name string) error
+}
+
+// realFS is the production implementation using the os package.
+type realFS struct{}
+
+func (r *realFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
+func (r *realFS) MkdirAll(path string, perm os.FileMode) error {
+ return os.MkdirAll(path, perm)
+}
+func (r *realFS) CreateTemp(dir, pattern string) (*os.File, error) {
+ return os.CreateTemp(dir, pattern)
+}
+func (r *realFS) MkdirTemp(dir, pattern string) (string, error) {
+ return os.MkdirTemp(dir, pattern)
+}
+func (r *realFS) Remove(name string) error { return os.Remove(name) }
+
+// DefaultFS is the global instance used by the package functions.
+var DefaultFS FileSystem = &realFS{}
+
+// ResetDefaults restores the default implementations of MountOps and FileSystem.
+func ResetDefaults() {
+ DefaultMountOps = &realMountOps{}
+ DefaultFS = &realFS{}
+}
+
+// BootstrapConfig contains the environment and arguments needed to execute the bootstrap launcher.
+type BootstrapConfig struct {
+ Args []string
+ Env []string
+ Fds []int
+ ExecPath string
+}
+
//go:embed launcher.bin
var launcherBytes []byte
@@ -104,48 +164,59 @@ func Bootstrap() (err error) {
return nil
}
- var fdsToClose []int
+ config, err := PrepareBootstrap()
+ if err != nil {
+ return err
+ }
+
defer func() {
- if err != nil {
- for _, fd := range fdsToClose {
- _ = syscall.Close(fd)
- }
+ for _, fd := range config.Fds {
+ _ = syscall.Close(fd)
}
}()
+ err = syscall.Exec(config.ExecPath, config.Args, config.Env)
+ if err != nil {
+ return fmt.Errorf("launcher exec failed: %w", err)
+ }
+
+ return nil
+}
+
+// PrepareBootstrap calculates the environment and arguments needed for the bootstrap launcher.
+func PrepareBootstrap() (*BootstrapConfig, error) {
// 0. Validate current arguments for null bytes before proceeding.
for i, arg := range os.Args {
for j := 0; j < len(arg); j++ {
if arg[j] == 0 {
- return fmt.Errorf("argument %d contains null byte at position %d", i, j)
+ return nil, fmt.Errorf("argument %d contains null byte at position %d", i, j)
}
}
}
self, err := os.Executable()
if err != nil {
- return fmt.Errorf("failed to get executable path: %w", err)
+ return nil, fmt.Errorf("failed to get executable path: %w", err)
}
execFd, err := prepareLauncher()
if err != nil {
- return err
+ return nil, err
}
- fdsToClose = append(fdsToClose, execFd)
// Clear close-on-exec
if flags, err := unix.FcntlInt(uintptr(execFd), unix.F_GETFD, 0); err == nil {
_, _ = unix.FcntlInt(uintptr(execFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC)
}
- // 3. Prepare arguments for the launcher.
+ // Prepare arguments for the launcher.
args := []string{self}
args = append(args, os.Args[1:]...)
for i, arg := range args {
for j := 0; j < len(arg); j++ {
if arg[j] == 0 {
- return fmt.Errorf("launcher argument %d contains null byte at position %d", i, j)
+ return nil, fmt.Errorf("launcher argument %d contains null byte at position %d", i, j)
}
}
}
@@ -153,9 +224,8 @@ func Bootstrap() (err error) {
// Open the host network namespace file descriptor before unsharing.
hostNetFd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0)
if err != nil {
- return fmt.Errorf("failed to open host netns: %w", err)
+ return nil, fmt.Errorf("failed to open host netns: %w", err)
}
- fdsToClose = append(fdsToClose, hostNetFd)
// Clear close-on-exec
if flags, err := unix.FcntlInt(uintptr(hostNetFd), unix.F_GETFD, 0); err == nil {
@@ -174,18 +244,17 @@ func Bootstrap() (err error) {
_, _ = unix.FcntlInt(hostSocketFd, unix.F_SETFD, flags&^unix.FD_CLOEXEC)
}
env = append(env, fmt.Sprintf("WG_WRAP_HOST_SOCKET_FD=%d", hostSocketFd))
- fdsToClose = append(fdsToClose, int(hostSocketFd))
_ = conn.Close()
}
}
}
- err = syscall.Exec(fmt.Sprintf("/proc/self/fd/%d", execFd), args, env)
- if err != nil {
- return fmt.Errorf("launcher exec failed: %w", err)
- }
-
- return nil
+ return &BootstrapConfig{
+ Args: args,
+ Env: env,
+ Fds: []int{execFd, hostNetFd},
+ ExecPath: fmt.Sprintf("/proc/self/fd/%d", execFd),
+ }, nil
}
// BootstrapJoin joins the namespaces of the target PID and replaces the current process.
@@ -194,33 +263,44 @@ func BootstrapJoin(targetPid int) (err error) {
return nil
}
- var fdsToClose []int
+ config, err := PrepareBootstrapJoin(targetPid)
+ if err != nil {
+ return err
+ }
+
defer func() {
- if err != nil {
- for _, fd := range fdsToClose {
- _ = syscall.Close(fd)
- }
+ for _, fd := range config.Fds {
+ _ = syscall.Close(fd)
}
}()
+ err = syscall.Exec(config.ExecPath, config.Args, config.Env)
+ if err != nil {
+ return fmt.Errorf("launcher exec failed: %w", err)
+ }
+
+ return nil
+}
+
+// PrepareBootstrapJoin calculates the environment and arguments needed to join a namespace.
+func PrepareBootstrapJoin(targetPid int) (*BootstrapConfig, error) {
for i, arg := range os.Args {
for j := 0; j < len(arg); j++ {
if arg[j] == 0 {
- return fmt.Errorf("argument %d contains null byte at position %d", i, j)
+ return nil, fmt.Errorf("argument %d contains null byte at position %d", i, j)
}
}
}
self, err := os.Executable()
if err != nil {
- return fmt.Errorf("failed to get executable path: %w", err)
+ return nil, fmt.Errorf("failed to get executable path: %w", err)
}
execFd, err := prepareLauncher()
if err != nil {
- return err
+ return nil, err
}
- fdsToClose = append(fdsToClose, execFd)
if flags, err := unix.FcntlInt(uintptr(execFd), unix.F_GETFD, 0); err == nil {
_, _ = unix.FcntlInt(uintptr(execFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC)
@@ -232,7 +312,7 @@ func BootstrapJoin(targetPid int) (err error) {
for i, arg := range args {
for j := 0; j < len(arg); j++ {
if arg[j] == 0 {
- return fmt.Errorf("launcher argument %d contains null byte at position %d", i, j)
+ return nil, fmt.Errorf("launcher argument %d contains null byte at position %d", i, j)
}
}
}
@@ -242,12 +322,12 @@ func BootstrapJoin(targetPid int) (err error) {
"WG_WRAP_JOINED=1",
)
- err = syscall.Exec(fmt.Sprintf("/proc/self/fd/%d", execFd), args, env)
- if err != nil {
- return fmt.Errorf("launcher exec failed: %w", err)
- }
-
- return nil
+ return &BootstrapConfig{
+ Args: args,
+ Env: env,
+ Fds: []int{execFd},
+ ExecPath: fmt.Sprintf("/proc/self/fd/%d", execFd),
+ }, nil
}
func prepareLauncher() (int, error) {