mirror of
https://github.com/containers/podman.git
synced 2025-06-05 22:31:06 +08:00
Merge pull request #18303 from n1hility/user-mode
Add user-mode networking feature to Windows/WSL
This commit is contained in:
2
Makefile
2
Makefile
@ -199,7 +199,7 @@ endif
|
|||||||
# include this lightweight helper binary.
|
# include this lightweight helper binary.
|
||||||
#
|
#
|
||||||
GV_GITURL=https://github.com/containers/gvisor-tap-vsock.git
|
GV_GITURL=https://github.com/containers/gvisor-tap-vsock.git
|
||||||
GV_SHA=aab0ac9367fc5142f5857c36ac2352bcb3c60ab7
|
GV_SHA=407efb5dcdb0f4445935f7360535800b60447544
|
||||||
|
|
||||||
###
|
###
|
||||||
### Primary entry-point targets
|
### Primary entry-point targets
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/containers/podman/v4/pkg/machine/wsl"
|
"github.com/containers/podman/v4/pkg/machine/wsl/wutil"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sys/windows/svc/eventlog"
|
"golang.org/x/sys/windows/svc/eventlog"
|
||||||
)
|
)
|
||||||
@ -49,7 +49,7 @@ func installWslKernel() error {
|
|||||||
)
|
)
|
||||||
backoff := 500 * time.Millisecond
|
backoff := 500 * time.Millisecond
|
||||||
for i := 1; i < 6; i++ {
|
for i := 1; i < 6; i++ {
|
||||||
err = wsl.SilentExec("wsl", "--update")
|
err = wutil.SilentExec("wsl", "--update")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func warn(title string, caption string) int {
|
|||||||
func main() {
|
func main() {
|
||||||
args := os.Args
|
args := os.Args
|
||||||
setupLogging(path.Base(args[0]))
|
setupLogging(path.Base(args[0]))
|
||||||
if wsl.IsWSLInstalled() {
|
if wutil.IsWSLInstalled() {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
logrus.Info("WSL Kernel already installed")
|
logrus.Info("WSL Kernel already installed")
|
||||||
return
|
return
|
||||||
|
@ -27,10 +27,17 @@ var (
|
|||||||
}
|
}
|
||||||
|
|
||||||
initOpts = machine.InitOptions{}
|
initOpts = machine.InitOptions{}
|
||||||
|
initOptionalFlags = InitOptionalFlags{}
|
||||||
defaultMachineName = machine.DefaultMachineName
|
defaultMachineName = machine.DefaultMachineName
|
||||||
now bool
|
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
|
// maxMachineNameSize is set to thirty to limit huge machine names primarily
|
||||||
// because macOS has a much smaller file size limit.
|
// because macOS has a much smaller file size limit.
|
||||||
const maxMachineNameSize = 30
|
const maxMachineNameSize = 30
|
||||||
@ -110,6 +117,10 @@ func init() {
|
|||||||
|
|
||||||
rootfulFlagName := "rootful"
|
rootfulFlagName := "rootful"
|
||||||
flags.BoolVar(&initOpts.Rootful, rootfulFlagName, false, "Whether this machine should prefer rootful container execution")
|
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 {
|
func initMachine(cmd *cobra.Command, args []string) error {
|
||||||
@ -118,7 +129,7 @@ func initMachine(cmd *cobra.Command, args []string) error {
|
|||||||
vm machine.VM
|
vm machine.VM
|
||||||
)
|
)
|
||||||
|
|
||||||
provider := GetSystemDefaultProvider()
|
provider := defaultProvider
|
||||||
initOpts.Name = defaultMachineName
|
initOpts.Name = defaultMachineName
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
if len(args[0]) > maxMachineNameSize {
|
if len(args[0]) > maxMachineNameSize {
|
||||||
@ -132,6 +143,12 @@ func initMachine(cmd *cobra.Command, args []string) error {
|
|||||||
for idx, vol := range initOpts.Volumes {
|
for idx, vol := range initOpts.Volumes {
|
||||||
initOpts.Volumes[idx] = os.ExpandEnv(vol)
|
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)
|
vm, err = provider.NewMachine(initOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -178,6 +178,7 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*entities.ListReporter, err
|
|||||||
response.RemoteUsername = vm.RemoteUsername
|
response.RemoteUsername = vm.RemoteUsername
|
||||||
response.IdentityPath = vm.IdentityPath
|
response.IdentityPath = vm.IdentityPath
|
||||||
response.Starting = vm.Starting
|
response.Starting = vm.Starting
|
||||||
|
response.UserModeNetworking = vm.UserModeNetworking
|
||||||
|
|
||||||
machineResponses = append(machineResponses, response)
|
machineResponses = append(machineResponses, response)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ type SetFlags struct {
|
|||||||
DiskSize uint64
|
DiskSize uint64
|
||||||
Memory uint64
|
Memory uint64
|
||||||
Rootful bool
|
Rootful bool
|
||||||
|
UserModeNetworking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -72,6 +73,10 @@ func init() {
|
|||||||
"Memory in MB",
|
"Memory in MB",
|
||||||
)
|
)
|
||||||
_ = setCmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone)
|
_ = 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 {
|
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") {
|
if cmd.Flags().Changed("disk-size") {
|
||||||
setOpts.DiskSize = &setFlags.DiskSize
|
setOpts.DiskSize = &setFlags.DiskSize
|
||||||
}
|
}
|
||||||
|
if cmd.Flags().Changed("user-mode-networking") {
|
||||||
|
setOpts.UserModeNetworking = &setFlags.UserModeNetworking
|
||||||
|
}
|
||||||
|
|
||||||
setErrs, lasterr := vm.Set(vmName, setOpts)
|
setErrs, lasterr := vm.Set(vmName, setOpts)
|
||||||
for _, err := range setErrs {
|
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-login.1.md
|
||||||
podman-logout.1.md
|
podman-logout.1.md
|
||||||
podman-logs.1.md
|
podman-logs.1.md
|
||||||
|
podman-machine-init.1.md
|
||||||
podman-machine-list.1.md
|
podman-machine-list.1.md
|
||||||
|
podman-machine-set.1.md
|
||||||
podman-manifest-add.1.md
|
podman-manifest-add.1.md
|
||||||
podman-manifest-annotate.1.md
|
podman-manifest-annotate.1.md
|
||||||
podman-manifest-create.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,
|
a `timezone` such as `America/Chicago`. A value of `local`, which is the default,
|
||||||
means to use the timezone of the machine host.
|
means to use the timezone of the machine host.
|
||||||
|
|
||||||
|
@@option user-mode-networking
|
||||||
|
|
||||||
#### **--username**
|
#### **--username**
|
||||||
|
|
||||||
Username to use for executing commands in remote VM. Default value is `core`
|
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 |
|
| .Resources ... | Resources used by the machine |
|
||||||
| .SSHConfig ... | SSH configuration info for communitating with machine |
|
| .SSHConfig ... | SSH configuration info for communitating with machine |
|
||||||
| .State ... | Machine state |
|
| .State ... | Machine state |
|
||||||
|
| .UserModeNetworking | Whether this machine uses user-mode networking |
|
||||||
|
|
||||||
#### **--help**
|
#### **--help**
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ or a Go template.
|
|||||||
Valid placeholders for the Go template are listed below:
|
Valid placeholders for the Go template are listed below:
|
||||||
|
|
||||||
| **Placeholder** | **Description** |
|
| **Placeholder** | **Description** |
|
||||||
| --------------- | ------------------------------- |
|
| ------------------- | ----------------------------------------- |
|
||||||
| .CPUs | Number of CPUs |
|
| .CPUs | Number of CPUs |
|
||||||
| .Created | Time since VM creation |
|
| .Created | Time since VM creation |
|
||||||
| .Default | Is default machine |
|
| .Default | Is default machine |
|
||||||
@ -47,6 +47,7 @@ Valid placeholders for the Go template are listed below:
|
|||||||
| .RemoteUsername | VM Username for rootless Podman |
|
| .RemoteUsername | VM Username for rootless Podman |
|
||||||
| .Running | Is machine running |
|
| .Running | Is machine running |
|
||||||
| .Stream | Stream name |
|
| .Stream | Stream name |
|
||||||
|
| .UserModeNetworking | Whether machine uses user-mode networking |
|
||||||
| .VMType | VM type |
|
| .VMType | VM type |
|
||||||
|
|
||||||
#### **--help**
|
#### **--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
|
remote connection default if it is currently pointing at the specified
|
||||||
machine name (or `podman-machine-default` if no name is 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)
|
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
|
this option will also make the API socket, if available, forward to the rootful/rootless
|
||||||
socket in the VM.
|
socket in the VM.
|
@ -17,6 +17,7 @@ type ListReporter struct {
|
|||||||
Port int
|
Port int
|
||||||
RemoteUsername string
|
RemoteUsername string
|
||||||
IdentityPath string
|
IdentityPath string
|
||||||
|
UserModeNetworking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MachineInfo contains info on the machine host and version info
|
// MachineInfo contains info on the machine host and version info
|
||||||
|
@ -31,8 +31,8 @@ type InitOptions struct {
|
|||||||
Username string
|
Username string
|
||||||
ReExec bool
|
ReExec bool
|
||||||
Rootful bool
|
Rootful bool
|
||||||
// The numerical userid of the user that called machine
|
UID string // uid of the user that called machine
|
||||||
UID string
|
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status = string
|
type Status = string
|
||||||
@ -104,6 +104,7 @@ type ListResponse struct {
|
|||||||
Port int
|
Port int
|
||||||
RemoteUsername string
|
RemoteUsername string
|
||||||
IdentityPath string
|
IdentityPath string
|
||||||
|
UserModeNetworking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetOptions struct {
|
type SetOptions struct {
|
||||||
@ -111,6 +112,7 @@ type SetOptions struct {
|
|||||||
DiskSize *uint64
|
DiskSize *uint64
|
||||||
Memory *uint64
|
Memory *uint64
|
||||||
Rootful *bool
|
Rootful *bool
|
||||||
|
UserModeNetworking *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHOptions struct {
|
type SSHOptions struct {
|
||||||
@ -160,6 +162,7 @@ type InspectInfo struct {
|
|||||||
Resources ResourceConfig
|
Resources ResourceConfig
|
||||||
SSHConfig SSHConfig
|
SSHConfig SSHConfig
|
||||||
State Status
|
State Status
|
||||||
|
UserModeNetworking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url.URL {
|
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 {
|
if err := v.resizeDisk(opts.DiskSize, originalDiskSize>>(10*3)); err != nil {
|
||||||
return false, err
|
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
|
// If the user provides an ignition file, we need to
|
||||||
// copy it into the conf dir
|
// copy it into the conf dir
|
||||||
if len(opts.IgnitionPath) > 0 {
|
if len(opts.IgnitionPath) > 0 {
|
||||||
@ -1152,6 +1157,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
|
|||||||
listEntry.IdentityPath = vm.IdentityPath
|
listEntry.IdentityPath = vm.IdentityPath
|
||||||
listEntry.CreatedAt = vm.Created
|
listEntry.CreatedAt = vm.Created
|
||||||
listEntry.Starting = vm.Starting
|
listEntry.Starting = vm.Starting
|
||||||
|
listEntry.UserModeNetworking = true // always true
|
||||||
|
|
||||||
if listEntry.CreatedAt.IsZero() {
|
if listEntry.CreatedAt.IsZero() {
|
||||||
listEntry.CreatedAt = time.Now()
|
listEntry.CreatedAt = time.Now()
|
||||||
@ -1618,6 +1624,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
|
|||||||
Resources: v.ResourceConfig,
|
Resources: v.ResourceConfig,
|
||||||
SSHConfig: v.SSHConfig,
|
SSHConfig: v.SSHConfig,
|
||||||
State: state,
|
State: state,
|
||||||
|
UserModeNetworking: true, // always true
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/podman/v4/pkg/machine"
|
"github.com/containers/podman/v4/pkg/machine"
|
||||||
|
"github.com/containers/podman/v4/pkg/machine/wsl/wutil"
|
||||||
"github.com/containers/podman/v4/utils"
|
"github.com/containers/podman/v4/utils"
|
||||||
"github.com/containers/storage/pkg/homedir"
|
"github.com/containers/storage/pkg/homedir"
|
||||||
"github.com/containers/storage/pkg/ioutils"
|
"github.com/containers/storage/pkg/ioutils"
|
||||||
@ -115,6 +116,15 @@ const wslConf = `[user]
|
|||||||
default=[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
|
// WSL kernel does not have sg and crypto_user modules
|
||||||
const overrideSysusers = `[Service]
|
const overrideSysusers = `[Service]
|
||||||
LoadCredential=
|
LoadCredential=
|
||||||
@ -198,10 +208,12 @@ http://docs.microsoft.com/en-us/windows/wsl/install\
|
|||||||
`
|
`
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
gvProxy = "gvproxy.exe"
|
||||||
winSShProxy = "win-sshproxy.exe"
|
winSShProxy = "win-sshproxy.exe"
|
||||||
winSshProxyTid = "win-sshproxy.tid"
|
winSshProxyTid = "win-sshproxy.tid"
|
||||||
pipePrefix = "npipe:////./pipe/"
|
pipePrefix = "npipe:////./pipe/"
|
||||||
globalPipe = "docker_engine"
|
globalPipe = "docker_engine"
|
||||||
|
userModeDist = "podman-net-usermode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Virtualization struct {
|
type Virtualization struct {
|
||||||
@ -241,6 +253,8 @@ type MachineVM struct {
|
|||||||
machine.SSHConfig
|
machine.SSHConfig
|
||||||
// machine version
|
// machine version
|
||||||
Version int
|
Version int
|
||||||
|
// Whether to use user-mode networking
|
||||||
|
UserModeNetworking bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExitCodeError struct {
|
type ExitCodeError struct {
|
||||||
@ -276,6 +290,11 @@ func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error
|
|||||||
vm.Created = time.Now()
|
vm.Created = time.Now()
|
||||||
vm.LastUp = vm.Created
|
vm.LastUp = vm.Created
|
||||||
|
|
||||||
|
// Default is false
|
||||||
|
if opts.UserModeNetworking != nil {
|
||||||
|
vm.UserModeNetworking = *opts.UserModeNetworking
|
||||||
|
}
|
||||||
|
|
||||||
// Add a random port for ssh
|
// Add a random port for ssh
|
||||||
port, err := utils.GetRandomPort()
|
port, err := utils.GetRandomPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -395,11 +414,19 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
|
|||||||
return false, err
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.UserModeNetworking {
|
||||||
|
if err = installUserModeDist(dist, v.ImagePath); err != nil {
|
||||||
|
_ = unregisterDist(dist)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Configuring system...")
|
fmt.Println("Configuring system...")
|
||||||
if err = configureSystem(v, dist); err != nil {
|
if err = configureSystem(v, dist); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -496,21 +523,21 @@ func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) err
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func provisionWSLDist(v *MachineVM) (string, error) {
|
func provisionWSLDist(name string, imagePath string, prompt string) (string, error) {
|
||||||
vmDataDir, err := machine.GetDataDir(vmtype)
|
vmDataDir, err := machine.GetDataDir(vmtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
distDir := filepath.Join(vmDataDir, "wsldist")
|
distDir := filepath.Join(vmDataDir, "wsldist")
|
||||||
distTarget := filepath.Join(distDir, v.Name)
|
distTarget := filepath.Join(distDir, name)
|
||||||
if err := os.MkdirAll(distDir, 0755); err != nil {
|
if err := os.MkdirAll(distDir, 0755); err != nil {
|
||||||
return "", fmt.Errorf("could not create wsldist directory: %w", err)
|
return "", fmt.Errorf("could not create wsldist directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dist := toDist(v.Name)
|
dist := toDist(name)
|
||||||
fmt.Println("Importing operating system into WSL (this may take a few minutes on a new WSL install)...")
|
fmt.Println(prompt)
|
||||||
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath, "--version", "2"); err != nil {
|
if err = runCmdPassThrough("wsl", "--import", dist, distTarget, imagePath, "--version", "2"); err != nil {
|
||||||
return "", fmt.Errorf("the WSL import of guest OS failed: %w", err)
|
return "", fmt.Errorf("the WSL import of guest OS failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,15 +546,6 @@ func provisionWSLDist(v *MachineVM) (string, error) {
|
|||||||
return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err)
|
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
|
return dist, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,11 +624,7 @@ func configureSystem(v *MachineVM, dist string) error {
|
|||||||
return fmt.Errorf("could not create podman-machine file for guest OS: %w", err)
|
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 changeDistUserModeNetworking(dist, user, "", v.UserModeNetworking)
|
||||||
return fmt.Errorf("could not configure wsl config for guest OS: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureProxy(dist string, useProxy bool, quiet bool) error {
|
func configureProxy(dist string, useProxy bool, quiet bool) error {
|
||||||
@ -694,8 +708,16 @@ func installScripts(dist string) error {
|
|||||||
return nil
|
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) {
|
func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
|
||||||
if IsWSLInstalled() {
|
if wutil.IsWSLInstalled() {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1015,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"))
|
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()
|
return setErrors, v.writeConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1029,6 +1072,11 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Startup user-mode networking if enabled
|
||||||
|
if err := v.startUserModeNetworking(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := wslInvoke(dist, "/root/bootstrap")
|
err := wslInvoke(dist, "/root/bootstrap")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("the WSL bootstrap script failed: %w", err)
|
return fmt.Errorf("the WSL bootstrap script failed: %w", err)
|
||||||
@ -1072,6 +1120,20 @@ func (v *MachineVM) Start(name string, opts machine.StartOptions) error {
|
|||||||
return err
|
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) {
|
func launchWinProxy(v *MachineVM) (bool, string, error) {
|
||||||
machinePipe := toDist(v.Name)
|
machinePipe := toDist(v.Name)
|
||||||
if !machine.PipeNameAvailable(machinePipe) {
|
if !machine.PipeNameAvailable(machinePipe) {
|
||||||
@ -1083,17 +1145,11 @@ func launchWinProxy(v *MachineVM) (bool, string, error) {
|
|||||||
globalName = true
|
globalName = true
|
||||||
}
|
}
|
||||||
|
|
||||||
exe, err := os.Executable()
|
command, err := findExecutablePeer(winSShProxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return globalName, "", err
|
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)
|
stateDir, err := getWinProxyStateDir(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return globalName, "", err
|
return globalName, "", err
|
||||||
@ -1143,59 +1199,54 @@ func getWinProxyStateDir(v *MachineVM) (string, error) {
|
|||||||
return stateDir, nil
|
return stateDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsWSLInstalled() bool {
|
|
||||||
cmd := SilentExecCmd("wsl", "--status")
|
|
||||||
out, err := cmd.StdoutPipe()
|
|
||||||
cmd.Stderr = nil
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if err = cmd.Start(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
|
||||||
result := true
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
// Windows 11 does not set an error exit code when a kernel is not avail
|
|
||||||
if strings.Contains(line, "kernel file is not found") {
|
|
||||||
result = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := cmd.Wait(); !result || err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsWSLFeatureEnabled() bool {
|
func IsWSLFeatureEnabled() bool {
|
||||||
return SilentExec("wsl", "--set-default-version", "2") == nil
|
return wutil.SilentExec("wsl", "--set-default-version", "2") == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWSLRunning(dist string) (bool, error) {
|
func isWSLRunning(dist string) (bool, error) {
|
||||||
cmd := exec.Command("wsl", "-l", "--running", "--quiet")
|
return wslCheckExists(dist, true)
|
||||||
out, err := cmd.StdoutPipe()
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return false, err
|
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()))
|
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
||||||
result := false
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
fields := strings.Fields(scanner.Text())
|
fields := strings.Fields(scanner.Text())
|
||||||
if len(fields) > 0 && dist == fields[0] {
|
if len(fields) > 0 {
|
||||||
result = true
|
all[fields[0]] = struct{}{}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = cmd.Wait()
|
_ = cmd.Wait()
|
||||||
|
|
||||||
return result, nil
|
return all, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSystemdRunning(dist string) (bool, error) {
|
func isSystemdRunning(dist string) (bool, error) {
|
||||||
@ -1243,6 +1294,11 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
|
|||||||
return fmt.Errorf("%q is not running", v.Name)
|
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)
|
_, _, _ = v.updateTimeStamps(true)
|
||||||
|
|
||||||
if err := stopWinProxy(v); err != nil {
|
if err := stopWinProxy(v); err != nil {
|
||||||
@ -1272,6 +1328,11 @@ func terminateDist(dist string) error {
|
|||||||
return cmd.Run()
|
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) {
|
func (v *MachineVM) State(bypass bool) (machine.Status, error) {
|
||||||
if v.isRunning() {
|
if v.isRunning() {
|
||||||
return machine.Running, nil
|
return machine.Running, nil
|
||||||
@ -1470,6 +1531,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
|
|||||||
listEntry.Port = vm.Port
|
listEntry.Port = vm.Port
|
||||||
listEntry.IdentityPath = vm.IdentityPath
|
listEntry.IdentityPath = vm.IdentityPath
|
||||||
listEntry.Starting = false
|
listEntry.Starting = false
|
||||||
|
listEntry.UserModeNetworking = vm.UserModeNetworking
|
||||||
|
|
||||||
running := vm.isRunning()
|
running := vm.isRunning()
|
||||||
listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
|
listEntry.CreatedAt, listEntry.LastUp, _ = vm.updateTimeStamps(running)
|
||||||
@ -1629,6 +1691,7 @@ func (v *MachineVM) Inspect() (*machine.InspectInfo, error) {
|
|||||||
Resources: v.getResources(),
|
Resources: v.getResources(),
|
||||||
SSHConfig: v.SSHConfig,
|
SSHConfig: v.SSHConfig,
|
||||||
State: state,
|
State: state,
|
||||||
|
UserModeNetworking: v.UserModeNetworking,
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -345,17 +344,3 @@ func sendQuit(tid uint32) {
|
|||||||
postMessage := user32.NewProc("PostThreadMessageW")
|
postMessage := user32.NewProc("PostThreadMessageW")
|
||||||
postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
|
postMessage.Call(uintptr(tid), WM_QUIT, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SilentExec(command string, args ...string) error {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func SilentExecCmd(command string, args ...string) *exec.Cmd {
|
|
||||||
cmd := exec.Command(command, args...)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
55
pkg/machine/wsl/wutil/wutil.go
Normal file
55
pkg/machine/wsl/wutil/wutil.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package wutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SilentExec(command string, args ...string) error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
||||||
|
cmd.Stdout = nil
|
||||||
|
cmd.Stderr = nil
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SilentExecCmd(command string, args ...string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsWSLInstalled() bool {
|
||||||
|
cmd := SilentExecCmd("wsl", "--status")
|
||||||
|
out, err := cmd.StdoutPipe()
|
||||||
|
cmd.Stderr = nil
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
||||||
|
result := true
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// Windows 11 does not set an error exit code when a kernel is not avail
|
||||||
|
if strings.Contains(line, "kernel file is not found") {
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := cmd.Wait(); !result || err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
Reference in New Issue
Block a user