-
Notifications
You must be signed in to change notification settings - Fork 745
Use the VM’s IP address on the same subnet as the host OS. #4175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
da4d8eb
1a74835
9ca9f67
2ef79c9
f813d4d
097efc6
e6a7f6a
a3877fe
f7881d8
7a7b379
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -516,10 +516,18 @@ jobs: | |
| matrix: | ||
| template: | ||
| - default.yaml | ||
| create_arg: | ||
| - "" | ||
| - "--network=vzNAT" | ||
| include: | ||
| - template: default.yaml | ||
| create_arg: "--network=vzNAT" | ||
| additional_env: | | ||
| _LIMA_DIRECT_IP_PORT_FORWARDER=true | ||
| steps: | ||
| - name: "Adjust LIMACTL_CREATE_ARGS" | ||
| # --cpus=1 is needed for running vz on GHA: https://github.com/lima-vm/lima/pull/1511#issuecomment-1574937888 | ||
| run: echo "LIMACTL_CREATE_ARGS=${LIMACTL_CREATE_ARGS} --cpus 1 --memory 1" >>$GITHUB_ENV | ||
| run: echo "LIMACTL_CREATE_ARGS=${LIMACTL_CREATE_ARGS} --cpus 1 --memory 1 ${{ matrix.create_arg }}" >>$GITHUB_ENV | ||
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
| - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 | ||
| with: | ||
|
|
@@ -536,12 +544,15 @@ jobs: | |
| run: brew install bash coreutils w3m socat | ||
| - name: Uninstall qemu | ||
| run: brew uninstall --ignore-dependencies --force qemu | ||
| - name: Set additional environment variables | ||
| if: matrix.additional_env != null | ||
| run: echo "${{ matrix.additional_env }}" >>$GITHUB_ENV | ||
| - name: Test | ||
| run: ./hack/test-templates.sh templates/${{ matrix.template }} | ||
| - if: failure() | ||
| uses: ./.github/actions/upload_failure_logs_if_exists | ||
| with: | ||
| suffix: ${{ matrix.template }} | ||
| suffix: ${{ matrix.template }}${{ matrix.create_arg }} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs |
||
|
|
||
| # gomodjail is a library sandbox for Go | ||
| # https://github.com/AkihiroSuda/gomodjail | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -83,6 +83,11 @@ type HostAgent struct { | |
|
|
||
| statusMu sync.RWMutex | ||
| currentStatus events.Status | ||
|
|
||
| // Guest IP address on the same subnet as the host. | ||
| guestIPv4 net.IP | ||
| guestIPv6 net.IP | ||
| guestIPMu sync.RWMutex | ||
| } | ||
|
|
||
| type options struct { | ||
|
|
@@ -258,6 +263,27 @@ func New(ctx context.Context, instName string, stdout io.Writer, signalCh chan o | |
| return a, nil | ||
| } | ||
|
|
||
| func (a *HostAgent) WriteSSHConfigFile(ctx context.Context) error { | ||
| sshExe, err := sshutil.NewSSHExe() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| sshOpts, err := sshutil.SSHOpts( | ||
| ctx, | ||
| sshExe, | ||
| a.instDir, | ||
| *a.instConfig.User.Name, | ||
| *a.instConfig.SSH.LoadDotSSHPubKeys, | ||
| *a.instConfig.SSH.ForwardAgent, | ||
| *a.instConfig.SSH.ForwardX11, | ||
| *a.instConfig.SSH.ForwardX11Trusted) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| sshAddress, sshPort := a.sshAddressPort() | ||
| return writeSSHConfigFile(sshExe.Exe, a.instName, a.instDir, sshAddress, sshPort, sshOpts) | ||
| } | ||
|
|
||
| func writeSSHConfigFile(sshPath, instName, instDir, instSSHAddress string, sshLocalPort int, sshOpts []string) error { | ||
| if instDir == "" { | ||
| return fmt.Errorf("directory is unknown for the instance %q", instName) | ||
|
|
@@ -336,6 +362,17 @@ func (a *HostAgent) emitCloudInitProgressEvent(ctx context.Context, progress *ev | |
| a.emitEvent(ctx, ev) | ||
| } | ||
|
|
||
| func (a *HostAgent) emitGuestIPEvent(ctx context.Context, ip string) { | ||
| a.statusMu.RLock() | ||
| currentStatus := a.currentStatus | ||
| a.statusMu.RUnlock() | ||
|
|
||
| currentStatus.GuestIP = net.ParseIP(ip) | ||
|
|
||
| ev := events.Event{Status: currentStatus} | ||
| a.emitEvent(ctx, ev) | ||
| } | ||
|
|
||
| func generatePassword(length int) (string, error) { | ||
| // avoid any special symbols, to make it easier to copy/paste | ||
| return password.Generate(length, length/4, 0, false, false) | ||
|
|
@@ -481,9 +518,31 @@ func (a *HostAgent) startRoutinesAndWait(ctx context.Context, errCh <-chan error | |
| return a.driver.Stop(ctx) | ||
| } | ||
|
|
||
| // GuestIP returns the guest's IPv4 address if available; otherwise the IPv6 address. | ||
| // It returns nil if the guest is not reachable by a direct IP. | ||
| func (a *HostAgent) GuestIP() net.IP { | ||
| a.guestIPMu.RLock() | ||
| defer a.guestIPMu.RUnlock() | ||
| if a.guestIPv4 != nil { | ||
| return a.guestIPv4 | ||
| } else if a.guestIPv6 != nil { | ||
| return a.guestIPv6 | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // GuestIPs returns the guest's IPv4 and IPv6 addresses if available; otherwise nil. | ||
| func (a *HostAgent) GuestIPs() (ipv4, ipv6 net.IP) { | ||
| a.guestIPMu.RLock() | ||
| defer a.guestIPMu.RUnlock() | ||
| return a.guestIPv4, a.guestIPv6 | ||
| } | ||
|
|
||
| func (a *HostAgent) Info(_ context.Context) (*hostagentapi.Info, error) { | ||
| guestIP := a.GuestIP() | ||
| info := &hostagentapi.Info{ | ||
| AutoStartedIdentifier: autostart.AutoStartedIdentifier(), | ||
| GuestIP: guestIP, | ||
| SSHLocalPort: a.sshLocalPort, | ||
| } | ||
| return info, nil | ||
|
|
@@ -492,6 +551,12 @@ func (a *HostAgent) Info(_ context.Context) (*hostagentapi.Info, error) { | |
| func (a *HostAgent) sshAddressPort() (sshAddress string, sshPort int) { | ||
| sshAddress = a.instSSHAddress | ||
| sshPort = a.sshLocalPort | ||
| guestIP := a.GuestIP() | ||
| if guestIP != nil { | ||
| sshAddress = guestIP.String() | ||
| sshPort = 22 | ||
| logrus.Debugf("Using the guest IP address %q directly", sshAddress) | ||
| } | ||
| return sshAddress, sshPort | ||
| } | ||
|
|
||
|
|
@@ -513,7 +578,8 @@ func (a *HostAgent) startHostAgentRoutines(ctx context.Context) error { | |
| return nil | ||
| } | ||
| logrus.Debugf("shutting down the SSH master") | ||
| if exitMasterErr := ssh.ExitMaster(a.instSSHAddress, a.sshLocalPort, a.sshConfig); exitMasterErr != nil { | ||
| sshAddress, sshPort := a.sshAddressPort() | ||
| if exitMasterErr := ssh.ExitMaster(sshAddress, sshPort, a.sshConfig); exitMasterErr != nil { | ||
| logrus.WithError(exitMasterErr).Warn("failed to exit SSH master") | ||
| } | ||
| return nil | ||
|
|
@@ -529,7 +595,8 @@ sudo mkdir -p -m 700 /run/host-services | |
| sudo ln -sf "${SSH_AUTH_SOCK}" /run/host-services/ssh-auth.sock | ||
| sudo chown -R "${USER}" /run/host-services` | ||
| faDesc := "linking ssh auth socket to static location /run/host-services/ssh-auth.sock" | ||
| stdout, stderr, err := ssh.ExecuteScript(a.instSSHAddress, a.sshLocalPort, a.sshConfig, faScript, faDesc) | ||
| sshAddress, sshPort := a.sshAddressPort() | ||
| stdout, stderr, err := ssh.ExecuteScript(sshAddress, sshPort, a.sshConfig, faScript, faDesc) | ||
| logrus.Debugf("stdout=%q, stderr=%q, err=%v", stdout, stderr, err) | ||
| if err != nil { | ||
| errs = append(errs, fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err)) | ||
|
|
@@ -836,7 +903,53 @@ func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client *guestag | |
| if useSSHFwd { | ||
| a.portForwarder.OnEvent(ctx, ev) | ||
| } else { | ||
| dialContext := portfwd.DialContextToGRPCTunnel(client) | ||
| useDirectIPPortForwarding := false | ||
| if envVar := os.Getenv("_LIMA_DIRECT_IP_PORT_FORWARDER"); envVar != "" { | ||
| b, err := strconv.ParseBool(envVar) | ||
| if err != nil { | ||
| logrus.WithError(err).Warnf("invalid _LIMA_DIRECT_IP_PORT_FORWARDER value %q", envVar) | ||
| } else { | ||
| useDirectIPPortForwarding = b | ||
| } | ||
| } | ||
| var dialContext func(ctx context.Context, network string, guestAddress string) (net.Conn, error) | ||
| if useDirectIPPortForwarding { | ||
| logrus.Warn("Direct IP Port forwarding is enabled. It may fall back to GRPC Port Forwarding in some cases.") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also support falling back to SSH ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we should rather remove the env var and define the YAML to specify the candidates of the forwarder implementation per protocols (TCP, UDP, ...) portForwarderTypes:
tcp: [direct, ssh]
udp: [none]Similar to:
Probably we have no time to cover this in v2.0, so let me postpone this PR to v2.1+, sorry |
||
| dialContext = func(ctx context.Context, network, guestAddress string) (net.Conn, error) { | ||
| guestIPv4, guestIPv6 := a.GuestIPs() | ||
| if guestIPv4 == nil && guestIPv6 == nil { | ||
| return portfwd.DialContextToGRPCTunnel(client)(ctx, network, guestAddress) | ||
| } | ||
| // Check if the host part of guestAddress is either unspecified address or matches the known guest IP. | ||
| // If so, replace it with the known guest IP to avoid issues with dual-stack setups and DNS resolution. | ||
| // Otherwise, fall back to the gRPC tunnel. | ||
| if host, _, err := net.SplitHostPort(guestAddress); err != nil { | ||
| return nil, err | ||
| } else if ip := net.ParseIP(host); ip.IsUnspecified() || ip.Equal(guestIPv4) || ip.Equal(guestIPv6) { | ||
| if ip.To4() != nil { | ||
| if guestIPv4 != nil { | ||
| conn, err := DialContextToGuestIP(guestIPv4)(ctx, network, guestAddress) | ||
| if err == nil { | ||
| return conn, nil | ||
| } | ||
| logrus.WithError(err).Warn("failed to connect to the guest IPv4 directly, falling back to gRPC tunnel") | ||
| } | ||
| } else if ip.To16() != nil { | ||
| if guestIPv6 != nil { | ||
| conn, err := DialContextToGuestIP(guestIPv6)(ctx, network, guestAddress) | ||
| if err == nil { | ||
| return conn, nil | ||
| } | ||
| logrus.WithError(err).Warn("failed to connect to the guest IPv6 directly, falling back to gRPC tunnel") | ||
| } | ||
| } | ||
| // If we reach here, it means we couldn't find a suitable guest IP | ||
| } | ||
| return portfwd.DialContextToGRPCTunnel(client)(ctx, network, guestAddress) | ||
| } | ||
| } else { | ||
| dialContext = portfwd.DialContextToGRPCTunnel(client) | ||
| } | ||
| a.grpcPortForwarder.OnEvent(ctx, dialContext, ev) | ||
| } | ||
| } | ||
|
|
@@ -850,6 +963,24 @@ func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client *guestag | |
| return io.EOF | ||
| } | ||
|
|
||
| // DialContextToGuestIP returns a DialContext function that connects to the guest IP directly. | ||
| // If the guest IP is not known, it returns nil. | ||
| func DialContextToGuestIP(guestIP net.IP) func(ctx context.Context, network, address string) (net.Conn, error) { | ||
| if guestIP == nil { | ||
| return nil | ||
| } | ||
| return func(ctx context.Context, network, address string) (net.Conn, error) { | ||
| var d net.Dialer | ||
| _, port, err := net.SplitHostPort(address) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| // Host part of address is ignored, because it already has been checked by forwarding rules | ||
| // and we want to connect to the guest IP directly. | ||
| return d.DialContext(ctx, network, net.JoinHostPort(guestIP.String(), port)) | ||
| } | ||
| } | ||
|
|
||
| const ( | ||
| verbForward = "forward" | ||
| verbCancel = "cancel" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.