Add user mode networking feature to Windows

Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
Jason T. Greene
2023-04-20 14:23:44 -05:00
parent fb3b92b969
commit 230ddbe0ca
16 changed files with 682 additions and 128 deletions

View File

@ -199,7 +199,7 @@ endif
# include this lightweight helper binary.
#
GV_GITURL=https://github.com/containers/gvisor-tap-vsock.git
GV_SHA=aab0ac9367fc5142f5857c36ac2352bcb3c60ab7
GV_SHA=407efb5dcdb0f4445935f7360535800b60447544
###
### Primary entry-point targets

View File

@ -27,10 +27,17 @@ var (
}
initOpts = machine.InitOptions{}
initOptionalFlags = InitOptionalFlags{}
defaultMachineName = machine.DefaultMachineName
now bool
defaultProvider = GetSystemDefaultProvider()
)
// Flags which have a meaning when unspecified that differs from the flag default
type InitOptionalFlags struct {
UserModeNetworking bool
}
// maxMachineNameSize is set to thirty to limit huge machine names primarily
// because macOS has a much smaller file size limit.
const maxMachineNameSize = 30
@ -110,6 +117,10 @@ func init() {
rootfulFlagName := "rootful"
flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution")
userModeNetFlagName := "user-mode-networking"
flags.BoolVar(&initOptionalFlags.UserModeNetworking, userModeNetFlagName, false,
"Whether this machine should use user-mode networking, routing traffic through a host user-space process")
}
func initMachine(cmd *cobra.Command, args []string) error {
@ -118,7 +129,7 @@ func initMachine(cmd *cobra.Command, args []string) error {
vm machine.VM
)
provider := GetSystemDefaultProvider()
provider := defaultProvider
initOpts.Name = defaultMachineName
if len(args) > 0 {
if len(args[0]) > maxMachineNameSize {
@ -132,6 +143,12 @@ func initMachine(cmd *cobra.Command, args []string) error {
for idx, vol := range initOpts.Volumes {
initOpts.Volumes[idx] = os.ExpandEnv(vol)
}
// Process optional flags (flags where unspecified / nil has meaning )
if cmd.Flags().Changed("user-mode-networking") {
initOpts.UserModeNetworking = &initOptionalFlags.UserModeNetworking
}
vm, err = provider.NewMachine(initOpts)
if err != nil {
return err

View File

@ -178,6 +178,7 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, err
response.RemoteUsername = vm.RemoteUsername
response.IdentityPath = vm.IdentityPath
response.Starting = vm.Starting
response.UserModeNetworking = vm.UserModeNetworking
machineResponses = append(machineResponses, response)
}

View File

@ -36,6 +36,7 @@ type SetFlags struct {
DiskSize uint64
Memory uint64
Rootful bool
UserModeNetworking bool
}
func init() {
@ -72,6 +73,10 @@ func init() {
"Memory in MB",
)
_ = setCmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone)
userModeNetFlagName := "user-mode-networking"
flags.BoolVar(&setFlags.UserModeNetworking, userModeNetFlagName, false, // defaults not-relevant due to use of Changed()
"Whether this machine should use user-mode networking, routing traffic through a host user-space process")
}
func setMachine(cmd *cobra.Command, args []string) error {
@ -102,6 +107,9 @@ func setMachine(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("disk-size") {
setOpts.DiskSize = &setFlags.DiskSize
}
if cmd.Flags().Changed("user-mode-networking") {
setOpts.UserModeNetworking = &setFlags.UserModeNetworking
}
setErrs, lasterr := vm.Set(vmName, setOpts)
for _, err := range setErrs {

View File

@ -19,7 +19,9 @@ podman-kube-play.1.md
podman-login.1.md
podman-logout.1.md
podman-logs.1.md
podman-machine-init.1.md
podman-machine-list.1.md
podman-machine-set.1.md
podman-manifest-add.1.md
podman-manifest-annotate.1.md
podman-manifest-create.1.md

View File

@ -0,0 +1,21 @@
####> This option file is used in:
####> podman machine init, machine set
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--user-mode-networking**
Whether this machine should relay traffic from the guest through a user-space
process running on the host. In some VPN configurations the VPN may drop
traffic from alternate network interfaces, including VM network devices. By
enabling user-mode networking (a setting of `true`), VPNs will observe all
podman machine traffic as coming from the host, bypassing the problem.
When the qemu backend is used (Linux, Mac), user-mode networking is
mandatory and the only allowed value is `true`. In contrast, The Windows/WSL
backend defaults to `false`, and follows the standard WSL network setup.
Changing this setting to `true` on Windows/WSL will inform Podman to replace
the WSL networking setup on start of this machine instance with a user-mode
networking distribution. Since WSL shares the same kernel across
distributions, all other running distributions will reuse this network.
Likewise, when the last machine instance with a `true` setting stops, the
original networking setup will be restored.

View File

@ -76,6 +76,8 @@ Set the timezone for the machine and containers. Valid values are `local` or
a `timezone` such as `America/Chicago`. A value of `local`, which is the default,
means to use the timezone of the machine host.
@@option user-mode-networking
#### **--username**
Username to use for executing commands in remote VM. Default value is `core`

View File

@ -31,6 +31,7 @@ Print results with a Go template.
| .Resources ... | Resources used by the machine |
| .SSHConfig ... | SSH configuration info for communitating with machine |
| .State ... | Machine state |
| .UserModeNetworking | Whether this machine uses user-mode networking |
#### **--help**

View File

@ -33,7 +33,7 @@ or a Go template.
Valid placeholders for the Go template are listed below:
| **Placeholder** | **Description** |
| --------------- | ------------------------------- |
| ------------------- | ----------------------------------------- |
| .CPUs | Number of CPUs |
| .Created | Time since VM creation |
| .Default | Is default machine |
@ -43,10 +43,11 @@ Valid placeholders for the Go template are listed below:
| .LastUp | Time since the VM was last run |
| .Memory | Allocated memory for machine |
| .Name | VM name |
| .Port | SSH Port to use to connect to VM|
| .Port | SSH Port to use to connect to VM |
| .RemoteUsername | VM Username for rootless Podman |
| .Running | Is machine running |
| .Stream | Stream name |
| .UserModeNetworking | Whether machine uses user-mode networking |
| .VMType | VM type |
#### **--help**

View File

@ -40,6 +40,8 @@ container execution. This option will also update the current podman
remote connection default if it is currently pointing at the specified
machine name (or `podman-machine-default` if no name is specified).
@@option user-mode-networking
Unlike [**podman system connection default**](podman-system-connection-default.1.md)
this option will also make the API socket, if available, forward to the rootful/rootless
socket in the VM.

View File

@ -17,6 +17,7 @@ type ListReporter struct {
Port int
RemoteUsername string
IdentityPath string
UserModeNetworking bool
}
// MachineInfo contains info on the machine host and version info

View File

@ -31,8 +31,8 @@ type InitOptions struct {
Username string
ReExec bool
Rootful bool
// The numerical userid of the user that called machine
UID string
UID string // uid of the user that called machine
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
}
type Status = string
@ -104,6 +104,7 @@ type ListResponse struct {
Port int
RemoteUsername string
IdentityPath string
UserModeNetworking bool
}
type SetOptions struct {
@ -111,6 +112,7 @@ type SetOptions struct {
DiskSize *uint64
Memory *uint64
Rootful *bool
UserModeNetworking *bool
}
type SSHOptions struct {
@ -160,6 +162,7 @@ type InspectInfo struct {
Resources ResourceConfig
SSHConfig SSHConfig
State Status
UserModeNetworking bool
}
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {

View File

@ -364,6 +364,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil {
return false, err
}
if opts.UserModeNetworking != nil && !*opts.UserModeNetworking {
logrus.Warn("ignoring init option to disable user-mode networking: this mode is not supported by the QEMU backend")
}
// If the user provides an ignition file, we need to
// copy it into the conf dir
if len(opts.IgnitionPath) > 0 {
@ -1145,6 +1150,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
listEntry.IdentityPath = vm.IdentityPath
listEntry.CreatedAt = vm.Created
listEntry.Starting = vm.Starting
listEntry.UserModeNetworking = true // always true
if listEntry.CreatedAt.IsZero() {
listEntry.CreatedAt = time.Now()
@ -1611,6 +1617,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
Resources: v.ResourceConfig,
SSHConfig: v.SSHConfig,
State: state,
UserModeNetworking: true, // always true
}, nil
}

View File

@ -0,0 +1,73 @@
//go:build windows
// +build windows
package wsl
import (
"io/fs"
"math"
"os"
"golang.org/x/sys/windows"
)
type fileLock struct {
file *os.File
}
// Locks a file path, creating or overwriting a file if necessary. This API only
// supports dedicated empty lock files. Locking is not advisory, once a file is
// locked, additional opens will block on read/write.
func lockFile(path string) (*fileLock, error) {
// In the future we may want to switch this to an async open vs the win32 API
// to bring support for timeouts, so we don't export the current underlying
// File object.
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
return nil, &fs.PathError{
Op: "lock",
Path: path,
Err: err,
}
}
const max = uint32(math.MaxUint32)
overlapped := new(windows.Overlapped)
lockType := windows.LOCKFILE_EXCLUSIVE_LOCK
// Lock largest possible length (all 64 bits (lo + hi) set)
err = windows.LockFileEx(windows.Handle(file.Fd()), uint32(lockType), 0, max, max, overlapped)
if err != nil {
file.Close()
return nil, &fs.PathError{
Op: "lock",
Path: file.Name(),
Err: err,
}
}
return &fileLock{file: file}, nil
}
func (flock *fileLock) unlock() error {
if flock == nil || flock.file == nil {
return nil
}
defer func() {
flock.file.Close()
flock.file = nil
}()
const max = uint32(math.MaxUint32)
overlapped := new(windows.Overlapped)
err := windows.UnlockFileEx(windows.Handle(flock.file.Fd()), 0, max, max, overlapped)
if err != nil {
return &fs.PathError{
Op: "unlock",
Path: flock.file.Name(),
Err: err,
}
}
return nil
}

View File

@ -116,6 +116,15 @@ const wslConf = `[user]
default=[USER]
`
const wslConfUserNet = `
[network]
generateResolvConf = false
`
const resolvConfUserNet = `
nameserver 192.168.127.1
`
// WSL kernel does not have sg and crypto_user modules
const overrideSysusers = `[Service]
LoadCredential=
@ -199,10 +208,12 @@ http://docs.microsoft.com/en-us/windows/wsl/install\
`
const (
gvProxy = "gvproxy.exe"
winSShProxy = "win-sshproxy.exe"
winSshProxyTid = "win-sshproxy.tid"
pipePrefix = "npipe:////./pipe/"
globalPipe = "docker_engine"
userModeDist = "podman-net-usermode"
)
type Virtualization struct {
@ -242,6 +253,8 @@ type MachineVM struct {
machine.SSHConfig
// machine version
Version int
// Whether to use user-mode networking
UserModeNetworking bool
}
type ExitCodeError struct {
@ -277,6 +290,11 @@ func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error
vm.Created = time.Now()
vm.LastUp = vm.Created
// Default is false
if opts.UserModeNetworking != nil {
vm.UserModeNetworking = *opts.UserModeNetworking
}
// Add a random port for ssh
port, err := utils.GetRandomPort()
if err != nil {
@ -396,11 +414,19 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return false, err
}
dist, err := provisionWSLDist(v)
const prompt = "Importing operating system into WSL (this may take a few minutes on a new WSL install)..."
dist, err := provisionWSLDist(v.Name, v.ImagePath, prompt)
if err != nil {
return false, err
}
if v.UserModeNetworking {
if err = installUserModeDist(dist, v.ImagePath); err != nil {
_ = unregisterDist(dist)
return false, err
}
}
fmt.Println("Configuring system...")
if err = configureSystem(v, dist); err != nil {
return false, err
@ -497,21 +523,21 @@ func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) err
return nil
}
func provisionWSLDist(v *MachineVM) (string, error) {
func provisionWSLDist(name string, imagePath string, prompt string) (string, error) {
vmDataDir, err := machine.GetDataDir(vmtype)
if err != nil {
return "", err
}
distDir := filepath.Join(vmDataDir, "wsldist")
distTarget := filepath.Join(distDir, v.Name)
distTarget := filepath.Join(distDir, name)
if err := os.MkdirAll(distDir, 0755); err != nil {
return "", fmt.Errorf("could not create wsldist directory: %w", err)
}
dist := toDist(v.Name)
fmt.Println("Importing operating system into WSL (this may take a few minutes on a new WSL install)...")
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath, "--version", "2"); err != nil {
dist := toDist(name)
fmt.Println(prompt)
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil {
return "", fmt.Errorf("the WSL import of guest OS failed: %w", err)
}
@ -520,15 +546,6 @@ func provisionWSLDist(v *MachineVM) (string, error) {
return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err)
}
// Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every
// operation when mount was not present on the initial start. Force a cycle so that it won't
// repeatedly complain.
if winVersionAtLeast(10, 0, 22000) {
if err := terminateDist(dist); err != nil {
logrus.Warnf("could not cycle WSL dist: %s", err.Error())
}
}
return dist, nil
}
@ -607,11 +624,7 @@ func configureSystem(v *MachineVM, dist string) error {
return fmt.Errorf("could not create podman-machine file for guest OS: %w", err)
}
if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil {
return fmt.Errorf("could not configure wsl config for guest OS: %w", err)
}
return nil
return changeDistUserModeNetworking(dist, user, "", v.UserModeNetworking)
}
func configureProxy(dist string, useProxy bool, quiet bool) error {
@ -695,6 +708,14 @@ func installScripts(dist string) error {
return nil
}
func writeWslConf(dist string, user string) error {
if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil {
return fmt.Errorf("could not configure wsl config for guest OS: %w", err)
}
return nil
}
func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
if wutil.IsWSLInstalled() {
return true, nil
@ -1016,6 +1037,27 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) {
setErrors = append(setErrors, errors.New("changing Disk Size not supported for WSL machines"))
}
if opts.UserModeNetworking != nil && *opts.UserModeNetworking != v.UserModeNetworking {
update := true
if v.isRunning() {
update = false
setErrors = append(setErrors, fmt.Errorf("user-mode networking can only be changed when the machine is not running"))
}
if update && *opts.UserModeNetworking {
dist := toDist(v.Name)
if err := changeDistUserModeNetworking(dist, v.RemoteUsername, v.ImagePath, *opts.UserModeNetworking); err != nil {
update = false
setErrors = append(setErrors, err)
}
}
if update {
v.UserModeNetworking = *opts.UserModeNetworking
}
}
return setErrors, v.writeConfig()
}
@ -1030,6 +1072,11 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
return err
}
// Startup user-mode networking if enabled
if err := v.startUserModeNetworking(); err != nil {
return err
}
err := wslInvoke(dist, "/root/bootstrap")
if err != nil {
return fmt.Errorf("the WSL bootstrap script failed: %w", err)
@ -1073,6 +1120,20 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
return err
}
func findExecutablePeer(name string) (string, error) {
exe, err := os.Executable()
if err != nil {
return "", err
}
exe, err = filepath.EvalSymlinks(exe)
if err != nil {
return "", err
}
return filepath.Join(filepath.Dir(exe), name), nil
}
func launchWinProxy(v *MachineVM) (bool, string, error) {
machinePipe := toDist(v.Name)
if !machine.PipeNameAvailable(machinePipe) {
@ -1084,17 +1145,11 @@ func launchWinProxy(v *MachineVM) (bool, string, error) {
globalName = true
}
exe, err := os.Executable()
command, err := findExecutablePeer(winSShProxy)
if err != nil {
return globalName, "", err
}
exe, err = filepath.EvalSymlinks(exe)
if err != nil {
return globalName, "", err
}
command := filepath.Join(filepath.Dir(exe), winSShProxy)
stateDir, err := getWinProxyStateDir(v)
if err != nil {
return globalName, "", err
@ -1149,27 +1204,49 @@ func IsWSLFeatureEnabled() bool {
}
func isWSLRunning(dist string) (bool, error) {
cmd := exec.Command("wsl", "-l", "--running", "--quiet")
out, err := cmd.StdoutPipe()
return wslCheckExists(dist, true)
}
func isWSLExist(dist string) (bool, error) {
return wslCheckExists(dist, false)
}
func wslCheckExists(dist string, running bool) (bool, error) {
all, err := getAllWSLDistros(running)
if err != nil {
return false, err
}
if err = cmd.Start(); err != nil {
return false, err
_, exists := all[dist]
return exists, nil
}
func getAllWSLDistros(running bool) (map[string]struct{}, error) {
args := []string{"-l", "--quiet"}
if running {
args = append(args, "--running")
}
cmd := exec.Command("wsl", args...)
out, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err = cmd.Start(); err != nil {
return nil, err
}
all := make(map[string]struct{})
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
result := false
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) > 0 && dist == fields[0] {
result = true
break
if len(fields) > 0 {
all[fields[0]] = struct{}{}
}
}
_ = cmd.Wait()
return result, nil
return all, nil
}
func isSystemdRunning(dist string) (bool, error) {
@ -1217,6 +1294,11 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
return fmt.Errorf("%q is not running", v.Name)
}
// Stop user-mode networking if enabled
if err := v.stopUserModeNetworking(dist); err != nil {
fmt.Fprintf(os.Stderr, "Could not cleanly stop user-mode networking: %s\n", err.Error())
}
_, _, _ = v.updateTimeStamps(true)
if err := stopWinProxy(v); err != nil {
@ -1246,6 +1328,11 @@ func terminateDist(dist string) error {
return cmd.Run()
}
func unregisterDist(dist string) error {
cmd := exec.Command("wsl", "--unregister", dist)
return cmd.Run()
}
func (v *MachineVM) State(bypass bool) (machine.Status, error) {
if v.isRunning() {
return machine.Running, nil
@ -1444,6 +1531,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.Port = vm.Port
listEntry.IdentityPath = vm.IdentityPath
listEntry.Starting = false
listEntry.UserModeNetworking = vm.UserModeNetworking
running := vm.isRunning()
listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
@ -1603,6 +1691,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
Resources: v.getResources(),
SSHConfig: v.SSHConfig,
State: state,
UserModeNetworking: v.UserModeNetworking,
}, nil
}

View File

@ -0,0 +1,326 @@
//go:build windows
// +build windows
package wsl
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/specgen"
"github.com/sirupsen/logrus"
)
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 /usr/local/bin/vm -iface podman-usermode -stop-if-exist ignore -url "stdio:$GVPROXY?listen-stdio=accept" > /var/log/vm.log 2> /var/log/vm.err < /dev/null &
echo $! > $STATE/vm.pid
sleep 1
ps -eo args | grep -q -m1 ^/usr/local/bin/vm || 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 (v *MachineVM) startUserModeNetworking() error {
if !v.UserModeNetworking {
return nil
}
exe, err := findExecutablePeer(gvProxy)
if err != nil {
return fmt.Errorf("could not locate %s, which is necessary for user-mode networking, please reinstall", gvProxy)
}
flock, err := v.obtainUserModeNetLock()
if err != nil {
return err
}
defer flock.unlock()
running, err := isWSLRunning(userModeDist)
if err != nil {
return err
}
running = running && isGvProxyVMRunning()
// Start or reuse
if !running {
if err := v.launchUserModeNetDist(exe); err != nil {
return err
}
}
if err := createUserModeResolvConf(toDist(v.Name)); err != nil {
return err
}
// Register in-use
err = v.addUserModeNetEntry()
if err != nil {
return err
}
return nil
}
func (v *MachineVM) stopUserModeNetworking(dist string) error {
if !v.UserModeNetworking {
return nil
}
flock, err := v.obtainUserModeNetLock()
if err != nil {
return err
}
defer flock.unlock()
err = v.removeUserModeNetEntry()
if err != nil {
return err
}
count, err := v.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 {
return wslInvoke(userModeDist, "bash", "-c", "ps -eo args | grep -q -m1 ^/usr/local/bin/vm || exit 42") == nil
}
func (v *MachineVM) 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\n%s", exe, 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 {
exists, err := isWSLExist(userModeDist)
if err != nil {
return err
}
if !exists {
if err := wslInvoke(dist, "test", "-f", "/usr/local/bin/vm"); 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 (v *MachineVM) getUserModeNetDir() (string, error) {
vmDataDir, err := machine.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 (v *MachineVM) getUserModeNetEntriesDir() (string, error) {
netDir, err := v.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 (v *MachineVM) addUserModeNetEntry() error {
entriesDir, err := v.getUserModeNetEntriesDir()
if err != nil {
return err
}
path := filepath.Join(entriesDir, toDist(v.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 (v *MachineVM) removeUserModeNetEntry() error {
entriesDir, err := v.getUserModeNetEntriesDir()
if err != nil {
return err
}
path := filepath.Join(entriesDir, toDist(v.Name))
return os.Remove(path)
}
func (v *MachineVM) cleanupAndCountNetEntries() (uint, error) {
entriesDir, err := v.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 (v *MachineVM) obtainUserModeNetLock() (*fileLock, error) {
dir, err := v.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 && len(image) > 0 {
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
}