package namespace import ( "bufio" "fmt" "os" "os/user" "strings" ) // Requirement defines a system check and the hint to provide if it fails. type Requirement struct { Name string Check func() (bool, string) Hint string } // Preflight handles system requirement checks and health diagnostics. type Preflight struct { FS FileSystem } // NewPreflight creates a new preflight checker with the provided filesystem. func NewPreflight(fs FileSystem) *Preflight { return &Preflight{FS: fs} } // CheckSystemRequirements verifies the absolute minimum requirements // that must be met for wg-wrap to function. func (p *Preflight) CheckSystemRequirements() error { // The only absolute hard requirement that isn't better caught by a syscall // is the existence and accessibility of the TUN device. if _, err := p.FS.Stat("/dev/net/tun"); os.IsNotExist(err) { return fmt.Errorf("critical requirement missing: /dev/net/tun not found. Please ensure the tun module is loaded (sudo modprobe tun)") } f, err := p.FS.Open("/dev/net/tun") if err != nil { return fmt.Errorf("critical requirement missing: /dev/net/tun is not accessible: %v", err) } _ = f.Close() return nil } // CheckSystemRequirements is the global wrapper for the preflight check. func CheckSystemRequirements() error { return (&Preflight{FS: DefaultFS}).CheckSystemRequirements() } // RunHealthCheck performs a comprehensive set of diagnostics to help users // configure their system for rootless network namespaces. func (p *Preflight) RunHealthCheck(uid int, username string) []HealthResult { isRoot := uid == 0 checks := []Requirement{ { Name: "TUN Device Accessibility", Check: func() (bool, string) { if _, err := p.FS.Stat("/dev/net/tun"); os.IsNotExist(err) { return false, "/dev/net/tun does not exist" } f, err := p.FS.Open("/dev/net/tun") if err != nil { return false, fmt.Sprintf("cannot open /dev/net/tun: %v", err) } _ = f.Close() return true, "" }, Hint: "Ensure the TUN module is loaded (sudo modprobe tun) and that your user has permissions to access /dev/net/tun", }, { Name: "Unprivileged User Namespaces (Debian/Ubuntu)", Check: func() (bool, string) { data, err := p.FS.ReadFile("/proc/sys/kernel/unprivileged_userns_clone") if err != nil { return true, "Not applicable (file not found)" } if strings.TrimSpace(string(data)) == "0" { return false, "unprivileged_userns_clone is disabled" } return true, "" }, Hint: "Enable unprivileged user namespaces by running: sudo sysctl -w kernel.unprivileged_userns_clone=1", }, { Name: "User Namespace Limit", Check: func() (bool, string) { data, err := p.FS.ReadFile("/proc/sys/user/max_user_namespaces") if err != nil { return true, "Not applicable (file not found)" } if strings.TrimSpace(string(data)) == "0" { return false, "max_user_namespaces is set to 0" } return true, "" }, Hint: "Increase the user namespace limit: sudo sysctl -w user.max_user_namespaces=1024", }, { Name: "SubUID/SubGID Mappings", Check: func() (bool, string) { if isRoot { return true, "Bypassed (Running as root)" } if username == "" { return false, "Could not determine current username" } data, err := p.FS.ReadFile("/etc/subuid") if err != nil { return false, fmt.Sprintf("failed to read /etc/subuid: %v", err) } scanner := bufio.NewScanner(strings.NewReader(string(data))) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, username+":") { return true, "" } } return false, fmt.Sprintf("no subuid mapping found for user %s", username) }, Hint: "Configure subuid and subgid for your user. Example: sudo usermod --add-subuids 100000-60000 --add-subgids 100000-60000 $USER", }, } var results []HealthResult for _, req := range checks { ok, msg := req.Check() results = append(results, HealthResult{ Name: req.Name, Passed: ok, Message: msg, Hint: req.Hint, }) } return results } // RunHealthCheck is the global wrapper for the health check. func RunHealthCheck() []HealthResult { u, _ := user.Current() username := "" if u != nil { username = u.Username } return (&Preflight{FS: DefaultFS}).RunHealthCheck(os.Getuid(), username) } // HealthResult describes the outcome of a single health check. type HealthResult struct { Name string Passed bool Message string Hint string }