//go:build windows package wsl import ( "errors" "fmt" "os" "os/exec" "path/filepath" "github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine/env" "github.com/containers/podman/v5/pkg/machine/vmconfigs" "github.com/containers/podman/v5/pkg/machine/wsl/wutil" "github.com/containers/podman/v5/pkg/specgen" "github.com/sirupsen/logrus" ) const gvForwarderPath = "/usr/libexec/podman/gvforwarder" const startUserModeNet = ` set -e STATE=/mnt/wsl/podman-usermodenet mkdir -p $STATE cp -f /mnt/wsl/resolv.conf $STATE/resolv.orig ip route show default > $STATE/route.dat ROUTE=$(<$STATE/route.dat) if [[ $ROUTE =~ .*192\.168\.127\.1.* ]]; then exit 2 fi if [[ ! $ROUTE =~ default\ via ]]; then exit 3 fi nohup $GVFORWARDER -iface podman-usermode -stop-if-exist ignore -url "stdio:$GVPROXY?listen-stdio=accept&ssh-port=-1" > /var/log/vm.log 2> /var/log/vm.err < /dev/null & echo $! > $STATE/vm.pid sleep 1 ps -eo args | grep -q -m1 ^$GVFORWARDER || exit 42 ` const stopUserModeNet = ` STATE=/mnt/wsl/podman-usermodenet if [[ ! -f "$STATE/vm.pid" || ! -f "$STATE/route.dat" ]]; then exit 2 fi cp -f $STATE/resolv.orig /mnt/wsl/resolv.conf GPID=$(<$STATE/vm.pid) kill $GPID > /dev/null while kill -0 $GPID > /dev/null 2>&1; do sleep 1 done ip route del default > /dev/null 2>&1 ROUTE=$(<$STATE/route.dat) if [[ ! $ROUTE =~ default\ via ]]; then exit 3 fi ip route add $ROUTE rm -rf /mnt/wsl/podman-usermodenet ` func verifyWSLUserModeCompat() error { if wutil.IsWSLStoreVersionInstalled() { return nil } prefix := "" if !winVersionAtLeast(10, 0, 19043) { prefix = "upgrade to 22H2, " } return fmt.Errorf("user-mode networking requires a newer version of WSL: "+ "%sapply all outstanding windows updates, and then run `wsl --update`", prefix) } func startUserModeNetworking(mc *vmconfigs.MachineConfig) error { if !mc.WSLHypervisor.UserModeNetworking { return nil } exe, err := machine.FindExecutablePeer(gvProxy) if err != nil { return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy) } flock, err := obtainUserModeNetLock() if err != nil { return err } defer func() { _ = flock.unlock() }() running, err := isWSLRunning(userModeDist) if err != nil { return err } running = running && isGvProxyVMRunning() // Start or reuse if !running { if err := launchUserModeNetDist(exe); err != nil { return err } } if err := createUserModeResolvConf(env.WithPodmanPrefix(mc.Name)); err != nil { return err } // Register in-use err = addUserModeNetEntry(mc) if err != nil { return err } return nil } func stopUserModeNetworking(mc *vmconfigs.MachineConfig) error { if !mc.WSLHypervisor.UserModeNetworking { return nil } flock, err := obtainUserModeNetLock() if err != nil { return err } defer func() { _ = flock.unlock() }() err = removeUserModeNetEntry(mc.Name) if err != nil { return err } count, err := cleanupAndCountNetEntries() if err != nil { return err } // Leave running if still in-use if count > 0 { return nil } fmt.Println("Stopping user-mode networking...") err = wslPipe(stopUserModeNet, userModeDist, "bash") if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { switch exitErr.ExitCode() { case 2: err = fmt.Errorf("startup state was missing") case 3: err = fmt.Errorf("route state is missing a default route") } } logrus.Warnf("problem tearing down user-mode networking cleanly, forcing: %s", err.Error()) } return terminateDist(userModeDist) } func isGvProxyVMRunning() bool { cmd := fmt.Sprintf("ps -eo args | grep -q -m1 ^%s || exit 42", gvForwarderPath) return wslInvoke(userModeDist, "bash", "-c", cmd) == nil } func launchUserModeNetDist(exeFile string) error { fmt.Println("Starting user-mode networking...") exe, err := specgen.ConvertWinMountPath(exeFile) if err != nil { return err } cmdStr := fmt.Sprintf("GVPROXY=%q\nGVFORWARDER=%q\n%s", exe, gvForwarderPath, startUserModeNet) if err := wslPipe(cmdStr, userModeDist, "bash"); err != nil { _ = terminateDist(userModeDist) if exitErr, ok := err.(*exec.ExitError); ok { switch exitErr.ExitCode() { case 2: return fmt.Errorf("another user-mode network is running, only one can be used at a time: shut down all machines and run wsl --shutdown if this is unexpected") case 3: err = fmt.Errorf("route state is missing a default route: shutdown all machines and run wsl --shutdown to recover") } } return fmt.Errorf("error setting up user-mode networking: %w", err) } return nil } func installUserModeDist(dist string, imagePath string) error { if err := verifyWSLUserModeCompat(); err != nil { return err } exists, err := isWSLExist(userModeDist) if err != nil { return err } if exists { if err := wslInvoke(userModeDist, "test", "-f", gvForwarderPath); err != nil { fmt.Println("Replacing old user-mode distribution...") _ = terminateDist(userModeDist) if err := unregisterDist(userModeDist); err != nil { return err } exists = false } } if !exists { if err := wslInvoke(dist, "test", "-f", gvForwarderPath); err != nil { return fmt.Errorf("existing machine is too old, can't install user-mode networking dist until machine is reinstalled (using podman machine rm, then podman machine init)") } const prompt = "Installing user-mode networking distribution..." if _, err := provisionWSLDist(userModeDist, imagePath, prompt); err != nil { return err } _ = terminateDist(userModeDist) } return nil } func createUserModeResolvConf(dist string) error { err := wslPipe(resolvConfUserNet, dist, "bash", "-c", "(rm -f /etc/resolv.conf; cat > /etc/resolv.conf)") if err != nil { return fmt.Errorf("could not create resolv.conf: %w", err) } return err } func getUserModeNetDir() (string, error) { vmDataDir, err := env.GetDataDir(vmtype) if err != nil { return "", err } dir := filepath.Join(vmDataDir, userModeDist) if err := os.MkdirAll(dir, 0755); err != nil { return "", fmt.Errorf("could not create %s directory: %w", userModeDist, err) } return dir, nil } func getUserModeNetEntriesDir() (string, error) { netDir, err := getUserModeNetDir() if err != nil { return "", err } dir := filepath.Join(netDir, "entries") if err := os.MkdirAll(dir, 0755); err != nil { return "", fmt.Errorf("could not create %s/entries directory: %w", userModeDist, err) } return dir, nil } func addUserModeNetEntry(mc *vmconfigs.MachineConfig) error { entriesDir, err := getUserModeNetEntriesDir() if err != nil { return err } path := filepath.Join(entriesDir, env.WithPodmanPrefix(mc.Name)) file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) if err != nil { return fmt.Errorf("could not add user-mode networking registration: %w", err) } file.Close() return nil } func removeUserModeNetEntry(name string) error { entriesDir, err := getUserModeNetEntriesDir() if err != nil { return err } path := filepath.Join(entriesDir, env.WithPodmanPrefix(name)) return os.Remove(path) } func cleanupAndCountNetEntries() (uint, error) { entriesDir, err := getUserModeNetEntriesDir() if err != nil { return 0, err } allDists, err := getAllWSLDistros(true) if err != nil { return 0, err } var count uint = 0 files, err := os.ReadDir(entriesDir) if err != nil { return 0, err } for _, file := range files { _, running := allDists[file.Name()] if !running { _ = os.Remove(filepath.Join(entriesDir, file.Name())) continue } count++ } return count, nil } func obtainUserModeNetLock() (*fileLock, error) { dir, err := getUserModeNetDir() if err != nil { return nil, err } var flock *fileLock lockPath := filepath.Join(dir, "podman-usermodenet.lck") if flock, err = lockFile(lockPath); err != nil { return nil, fmt.Errorf("could not lock user-mode networking lock file: %w", err) } return flock, nil } func changeDistUserModeNetworking(dist string, user string, image string, enable bool) error { // Only install if user-mode is being enabled and there was an image path passed if enable { if len(image) == 0 { return errors.New("existing machine configuration is corrupt, no image is defined") } if err := installUserModeDist(dist, image); err != nil { return err } } if err := writeWslConf(dist, user); err != nil { return err } if enable { return appendDisableAutoResolve(dist) } return nil } func appendDisableAutoResolve(dist string) error { if err := wslPipe(wslConfUserNet, dist, "sh", "-c", "cat >> /etc/wsl.conf"); err != nil { return fmt.Errorf("could not append resolv config to wsl.conf: %w", err) } return nil }