podman/pkg/machine/wsl/usermodenet.go

370 lines
8.6 KiB
Go

//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
}