Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 65 additions & 3 deletions pkg/driver/wsl2/vm_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
_ "embed"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -134,7 +135,7 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri
for {
<-ctx.Done()
logrus.Info("Context closed, stopping vm")
if status, err := getWslStatus(instanceName); err == nil &&
if status, err := getWslStatus(ctx, instanceName); err == nil &&
status == limatype.StatusRunning {
_ = stopVM(ctx, distroName)
}
Expand Down Expand Up @@ -179,13 +180,40 @@ func unregisterVM(ctx context.Context, distroName string) error {
return nil
}

func getWslStatus(instName string) (string, error) {
// GetWslStatus runs `wsl --list --verbose` and parses its output.
// There are several possible outputs, all listed with their whitespace preserved output below.
//
// (1) Expected output if at least one distro is installed:
// PS > wsl --list --verbose
//
// NAME STATE VERSION
//
// * Ubuntu Stopped 2
//
// (2) Expected output when no distros are installed, but WSL is configured properly:
// PS > wsl --list --verbose
// Windows Subsystem for Linux has no installed distributions.
//
// Use 'wsl.exe --list --online' to list available distributions
// and 'wsl.exe --install <Distro>' to install.
//
// Distributions can also be installed by visiting the Microsoft Store:
// https://aka.ms/wslstore
// Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND
//
// (3) Expected output when no distros are installed, and WSL2 has no kernel installed:
//
// PS > wsl --list --verbose
// Windows Subsystem for Linux has no installed distributions.
// Distributions can be installed by visiting the Microsoft Store:
// https://aka.ms/wslstore
func getWslStatus(ctx context.Context, instName string) (string, error) {
distroName := "lima-" + instName
out, err := executil.RunUTF16leCommand([]string{
"wsl.exe",
"--list",
"--verbose",
})
}, executil.WithContext(ctx))
if err != nil {
return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, out)
}
Expand Down Expand Up @@ -229,3 +257,37 @@ func getWslStatus(instName string) (string, error) {

return instState, nil
}

// GetSSHAddress runs a hostname command to get the IP from inside of a wsl2 VM.
//
// Expected output (whitespace preserved, [] for optional):
// PS > wsl -d <distroName> bash -c hostname -I | cut -d' ' -f1
// 168.1.1.1 [10.0.0.1]
// But busybox hostname does not implement --all-ip-addresses:
// hostname: unrecognized option: I
func getSSHAddress(ctx context.Context, instName string) (string, error) {
distroName := "lima-" + instName
// Ubuntu
cmd := exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "bash", "-c", `hostname -I | cut -d ' ' -f1`)
out, err := cmd.CombinedOutput()
if err == nil {
return strings.TrimSpace(string(out)), nil
}
// Alpine
cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "sh", "-c", `ip route get 1 | awk '{gsub("^.*src ",""); print $1; exit}'`)
out, err = cmd.CombinedOutput()
if err == nil {
return strings.TrimSpace(string(out)), nil
}
// fallback
cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "hostname", "-i")
out, err = cmd.CombinedOutput()
if err == nil {
ip := net.ParseIP(strings.TrimSpace(string(out)))
// some distributions use "127.0.1.1" as the host IP, but we want something that we can route to here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this command will always return 127.0.1.1. It is documented at https://learn.microsoft.com/en-us/windows/wsl/networking#accessing-a-wsl-2-distribution-from-your-local-area-network-lan

I can confirm that this is still true with the latest WSL2:

C:\Users\suse>wsl -d Ubuntu hostname -i
127.0.1.1

C:\Users\suse>wsl -d rancher-desktop hostname -i
127.0.1.1

So I don't know if this fallback will ever help; maybe it should be removed?

if ip != nil && !ip.IsLoopback() {
return strings.TrimSpace(string(out)), nil
}
}
return "", fmt.Errorf("failed to get hostname for instance %q, err: %w (out=%q)", instName, err, string(out))
}
8 changes: 4 additions & 4 deletions pkg/driver/wsl2/wsl_driver_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (l *LimaWslDriver) BootScripts() (map[string][]byte, error) {
}

func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string {
status, err := getWslStatus(inst.Name)
status, err := getWslStatus(ctx, inst.Name)
if err != nil {
inst.Status = limatype.StatusBroken
inst.Errors = append(inst.Errors, err)
Expand All @@ -193,7 +193,7 @@ func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instan
inst.SSHLocalPort = 22
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unsuman this old behaviour (from before, in pkg/store) is also a bit weird.

It starts out with "port 0" like any other driver, then go to "22" when running.
But connecting to 127.0.0.1:22 is probably never what you want, anyway...

Will follow up with a fix to that, when I know where the functions end up


if inst.Status == limatype.StatusRunning {
sshAddr, err := l.SSHAddress(ctx)
sshAddr, err := getSSHAddress(ctx, inst.Name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not implement the new logic in l.SSHAddress ?

Copy link
Member Author

@afbjorklund afbjorklund Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because SSHAddress does not check the Status, that is currently in InspectStatus

If we make XXXStatus part of the API instead, we could move the logic to store ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This happened when we added the "no-op" functions, that other drivers currently use.

func (l *LimaVzDriver) SSHAddress(_ context.Context) (string, error) {
        return "127.0.0.1", nil
}

func (l *LimaVzDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string {
        return ""
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally SSHAddress is never called, unless a specific driver feature is enabled.

        // WSL instance SSH address isn't known until after VM start
        if a.driver.Info().Features.DynamicSSHAddress {
                sshAddr, err := a.driver.SSHAddress(ctx)
                if err != nil {
                        return err
                }
                a.instSSHAddress = sshAddr
        }

And InspectStatus is not implemented, which means it will check the PID files.

func inspectStatus(ctx context.Context, instDir string, inst *limatype.Instance, y *limatype.LimaYAML) {
        status, err := driverutil.InspectStatus(ctx, inst)
        if err != nil {
                inst.Status = limatype.StatusBroken
                inst.Errors = append(inst.Errors, fmt.Errorf("failed to inspect status: %w", err))
                return
        }

        if status == "" {
                inspectStatusWithPIDFiles(instDir, inst, y)
                return
        }

        inst.Status = status
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the logic is not new, it was just moved from pkg/store (in 1.x) to the drivers (2.x).

https://github.com/lima-vm/lima/blob/release/1.2/pkg/store/instance_windows.go

if err == nil {
inst.SSHAddress = sshAddr
} else {
Expand All @@ -206,7 +206,7 @@ func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instan

func (l *LimaWslDriver) Delete(ctx context.Context) error {
distroName := "lima-" + l.Instance.Name
status, err := getWslStatus(l.Instance.Name)
status, err := getWslStatus(ctx, l.Instance.Name)
if err != nil {
return err
}
Expand All @@ -221,7 +221,7 @@ func (l *LimaWslDriver) Delete(ctx context.Context) error {

func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) {
logrus.Infof("Starting WSL VM")
status, err := getWslStatus(l.Instance.Name)
status, err := getWslStatus(ctx, l.Instance.Name)
if err != nil {
return nil, err
}
Expand Down
Loading