//go:build linux package namespace import ( "fmt" "os" "path/filepath" "strconv" "git.theodohertyfamily.com/tools/wg-wrap/internal/paths" "golang.org/x/sys/unix" ) // PinNamespace binds the current network namespace to the profile's namespace path. // This prevents the kernel from destroying the namespace when all processes exit. func PinNamespace(pm *paths.PathManager, profile string) error { nsPath := GetProfileNamespacePath(pm, profile) profilesDir := filepath.Dir(nsPath) if err := os.MkdirAll(profilesDir, 0755); err != nil { return fmt.Errorf("failed to create profiles directory: %w", err) } // 1. Create an empty file to serve as the mount point if err := os.WriteFile(nsPath, []byte(""), 0644); err != nil { return fmt.Errorf("failed to create namespace pin file: %w", err) } // 2. Bind-mount the current network namespace to the file. // This increments the kernel's reference count for the namespace. if err := unix.Mount("/proc/self/ns/net", nsPath, "", unix.MS_BIND, ""); err != nil { return fmt.Errorf("failed to bind-mount network namespace: %w", err) } return nil } // UnpinNamespace unmounts and removes the pinned namespace file. func UnpinNamespace(pm *paths.PathManager, profile string) error { nsPath := GetProfileNamespacePath(pm, profile) if _, err := os.Stat(nsPath); os.IsNotExist(err) { return nil } // 1. Unmount the namespace first. // If this is the last reference to the namespace, the kernel will destroy it. if err := unix.Unmount(nsPath, 0); err != nil { return fmt.Errorf("failed to unmount namespace %s: %w", nsPath, err) } // 2. Remove the mount point file. if err := os.Remove(nsPath); err != nil { return fmt.Errorf("failed to remove pin file %s: %w", nsPath, err) } pidsDir := GetPidsDirPath(pm, profile) // Try to remove pids directory and empty parent directories _ = os.Remove(pidsDir) _ = os.Remove(filepath.Dir(pidsDir)) _ = os.Remove(filepath.Dir(filepath.Dir(pidsDir))) return nil } // FindActiveProfilePid looks for an active PID running under the specified profile. // Returns 0 if no active process is found. func FindActiveProfilePid(pm *paths.PathManager, profile string) (int, error) { if err := PruneStalePids(pm, profile); err != nil { return 0, fmt.Errorf("failed to prune stale pids: %w", err) } pidsDir := GetPidsDirPath(pm, profile) files, err := os.ReadDir(pidsDir) if err != nil { if os.IsNotExist(err) { return 0, nil } return 0, fmt.Errorf("failed to read pids dir: %w", err) } for _, file := range files { pid, err := strconv.Atoi(file.Name()) if err != nil { continue } // Since we already pruned stale pids, the first file we find is an active pid! return pid, nil } return 0, nil }