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
23 changes: 23 additions & 0 deletions pkg/driver/wsl2/sshaddress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package wsl2

import (
"net"
"strings"
)

// routableGuestIP returns the trimmed output when it is a routable IP address,
// or "" otherwise. The output comes from a command run inside the WSL2 guest
// and becomes inst.SSHAddress, which is passed to ssh/scp/rsync as the
// destination argument; a non-address value such as "-oProxyCommand=..." would
// be interpreted by ssh as an option, so it is validated here. Loopback is
// rejected because some distributions report 127.0.1.1, which is not routable.
func routableGuestIP(out []byte) string {
s := strings.TrimSpace(string(out))
if ip := net.ParseIP(s); ip == nil || ip.IsLoopback() {
return ""
}
return s
}
33 changes: 33 additions & 0 deletions pkg/driver/wsl2/sshaddress_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package wsl2

import (
"testing"

"gotest.tools/v3/assert"
)

func TestRoutableGuestIP(t *testing.T) {
tests := []struct {
name string
out string
want string
}{
{"ipv4", "192.168.5.10\n", "192.168.5.10"},
{"ipv4 trailing space", "10.0.0.5 \n", "10.0.0.5"},
{"ipv6", "fd00::1\n", "fd00::1"},
{"loopback rejected", "127.0.1.1\n", ""},
{"ipv6 loopback rejected", "::1", ""},
{"empty rejected", "", ""},
{"ssh option injection rejected", "-oProxyCommand=calc.exe\n", ""},
{"ssh flag injection rejected", "-J attacker.example:22", ""},
{"hostname rejected", "attacker.example.com", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, routableGuestIP([]byte(tt.out)), tt.want)
})
}
}
17 changes: 9 additions & 8 deletions pkg/driver/wsl2/vm_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"
_ "embed"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -269,23 +268,25 @@ func getSSHAddress(ctx context.Context, instName string) (string, error) {
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
if addr := routableGuestIP(out); addr != "" {
return addr, 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
if addr := routableGuestIP(out); addr != "" {
return addr, 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
if ip != nil && !ip.IsLoopback() {
return strings.TrimSpace(string(out)), nil
if addr := routableGuestIP(out); addr != "" {
return addr, nil
}
}
return "", fmt.Errorf("failed to get hostname for instance %#q, err: %w (out=%#q)", instName, err, string(out))
return "", fmt.Errorf("failed to get a routable address for instance %#q, err: %w (out=%#q)", instName, err, string(out))
}
Loading