mirror of
https://github.com/containers/podman.git
synced 2025-06-03 12:17:13 +08:00
Add user mode networking feature to Windows
Signed-off-by: Jason T. Greene <jason.greene@redhat.com>
This commit is contained in:
2
Makefile
2
Makefile
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
2
docs/source/markdown/.gitignore
vendored
2
docs/source/markdown/.gitignore
vendored
@ -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
|
||||
|
21
docs/source/markdown/options/user-mode-networking.md
Normal file
21
docs/source/markdown/options/user-mode-networking.md
Normal 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.
|
@ -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`
|
@ -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**
|
||||
|
||||
|
@ -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**
|
||||
|
@ -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.
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
73
pkg/machine/wsl/filelock.go
Normal file
73
pkg/machine/wsl/filelock.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
326
pkg/machine/wsl/usermodenet.go
Normal file
326
pkg/machine/wsl/usermodenet.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user