diff --git a/cmd/lima-guestagent/daemon_linux.go b/cmd/lima-guestagent/daemon_linux.go index 4e029415c003..e77f448f2fe4 100644 --- a/cmd/lima-guestagent/daemon_linux.go +++ b/cmd/lima-guestagent/daemon_linux.go @@ -11,7 +11,6 @@ import ( "github.com/lima-vm/lima/pkg/guestagent" "github.com/lima-vm/lima/pkg/guestagent/api/server" "github.com/lima-vm/lima/pkg/guestagent/serialport" - "github.com/lima-vm/lima/pkg/store/filenames" "github.com/mdlayher/vsock" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -25,26 +24,23 @@ func newDaemonCommand() *cobra.Command { } daemonCommand.Flags().Duration("tick", 3*time.Second, "tick for polling events") daemonCommand.Flags().Int("vsock-port", 0, "use vsock server instead a UNIX socket") + daemonCommand.Flags().String("virtio-port", "", "use virtio server instead a UNIX socket") return daemonCommand } -var ( - vSockPort = 0 - - virtioPort = "/dev/virtio-ports/" + filenames.VirtioPort -) - func daemonAction(cmd *cobra.Command, _ []string) error { + socket := "/run/lima-guestagent.sock" tick, err := cmd.Flags().GetDuration("tick") if err != nil { return err } - vSockPortOverride, err := cmd.Flags().GetInt("vsock-port") + vSockPort, err := cmd.Flags().GetInt("vsock-port") if err != nil { return err } - if vSockPortOverride != 0 { - vSockPort = vSockPortOverride + virtioPort, err := cmd.Flags().GetString("virtio-port") + if err != nil { + return err } if tick == 0 { return errors.New("tick must be specified") @@ -72,10 +68,14 @@ func daemonAction(cmd *cobra.Command, _ []string) error { r := mux.NewRouter() server.AddRoutes(r, backend) srv := &http.Server{Handler: r} + err = os.RemoveAll(socket) + if err != nil { + return err + } var l net.Listener - if _, err := os.Stat(virtioPort); err == nil { - qemuL, err := serialport.Listen(virtioPort) + if virtioPort != "" { + qemuL, err := serialport.Listen("/dev/virtio-ports/" + virtioPort) if err != nil { return err } @@ -88,6 +88,16 @@ func daemonAction(cmd *cobra.Command, _ []string) error { } l = vsockL logrus.Infof("serving the guest agent on vsock port: %d", vSockPort) + } else { + socketL, err := net.Listen("unix", socket) + if err != nil { + return err + } + if err := os.Chmod(socket, 0o777); err != nil { + return err + } + l = socketL + logrus.Infof("serving the guest agent on %q", socket) } return srv.Serve(l) } diff --git a/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh b/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh index 968fc20e06f5..04d97c64a8ed 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh +++ b/pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh @@ -28,7 +28,7 @@ name="lima-guestagent" description="Forward ports to the lima-hostagent" command=${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent -command_args="daemon --vsock-port "${LIMA_CIDATA_VSOCK_PORT}"" +command_args="daemon --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}"" command_background=true pidfile="/run/lima-guestagent.pid" EOF @@ -40,5 +40,11 @@ else # Remove legacy systemd service rm -f "${LIMA_CIDATA_HOME}/.config/systemd/user/lima-guestagent.service" - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" + if [ "${LIMA_CIDATA_VSOCK_PORT}" != "0" ]; then + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" + elif [ "${LIMA_CIDATA_VIRTIO_PORT}" != "" ]; then + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}" + else + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd + fi fi diff --git a/pkg/cidata/cidata.TEMPLATE.d/lima.env b/pkg/cidata/cidata.TEMPLATE.d/lima.env index e43a33291752..c0c8c7d11ee3 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/lima.env +++ b/pkg/cidata/cidata.TEMPLATE.d/lima.env @@ -41,6 +41,7 @@ LIMA_CIDATA_SKIP_DEFAULT_DEPENDENCY_RESOLUTION= {{- end}} LIMA_CIDATA_VMTYPE={{ .VMType }} LIMA_CIDATA_VSOCK_PORT={{ .VSockPort }} +LIMA_CIDATA_VIRTIO_PORT={{ .VirtioPort}} {{- if .Plain}} LIMA_CIDATA_PLAIN=1 {{- else}} diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 40d8187f7a03..9c7342b6a1b6 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -110,7 +110,7 @@ func setupEnv(y *limayaml.LimaYAML, args TemplateArgs) (map[string]string, error return env, nil } -func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int) error { +func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string) error { if err := limayaml.Validate(*y, false); err != nil { return err } @@ -135,6 +135,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort RosettaBinFmt: *y.Rosetta.BinFmt, VMType: *y.VMType, VSockPort: vsockPort, + VirtioPort: virtioPort, Plain: *y.Plain, } diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index 089a085f76ba..649220117710 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -81,6 +81,7 @@ type TemplateArgs struct { SkipDefaultDependencyResolution bool VMType string VSockPort int + VirtioPort string Plain bool } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 8677249f458a..06e00167e16b 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -73,6 +73,7 @@ type BaseDriver struct { SSHLocalPort int VSockPort int + VirtioPort string } var _ Driver = (*BaseDriver)(nil) @@ -138,5 +139,5 @@ func (d *BaseDriver) ListSnapshots(_ context.Context) (string, error) { } func (d *BaseDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { - return nil, fmt.Errorf("unimplemented") + return nil, nil } diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index cb6fddfdf335..a31c6cdfe4f5 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -53,7 +53,8 @@ type HostAgent struct { eventEnc *json.Encoder eventEncMu sync.Mutex - vSockPort int + vSockPort int + virtioPort string clientMu sync.RWMutex client guestagentclient.GuestAgentClient @@ -114,6 +115,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt } vSockPort := 0 + virtioPort := "" if *y.VMType == limayaml.VZ { vSockPort = 2222 } else if *y.VMType == limayaml.WSL2 { @@ -122,9 +124,11 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt logrus.WithError(err).Error("failed to get free VSock port") } vSockPort = port + } else if *y.VMType == limayaml.QEMU { + virtioPort = filenames.VirtioPort } - if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort); err != nil { + if err := cidata.GenerateISO9660(inst.Dir, instName, y, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort, virtioPort); err != nil { return nil, err } @@ -157,6 +161,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt Yaml: y, SSHLocalPort: sshLocalPort, VSockPort: vSockPort, + VirtioPort: virtioPort, }) a := &HostAgent{ @@ -173,6 +178,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt sigintCh: sigintCh, eventEnc: json.NewEncoder(stdout), vSockPort: vSockPort, + virtioPort: virtioPort, } return a, nil } @@ -528,8 +534,6 @@ func (a *HostAgent) close() error { } func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { - // TODO: use vSock (when QEMU for macOS gets support for vSock) - // Setup all socket forwards and defer their teardown if *a.y.VMType != limayaml.WSL2 { logrus.Debugf("Forwarding unix sockets") @@ -541,6 +545,12 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { } } + // if needed, forward the guest agent unix socket + d := a.driver.(*driver.BaseDriver) + forwardUnix := d.VSockPort == 0 && d.VirtioPort == "" + localUnix := filepath.Join(a.instDir, filenames.GuestAgentSock) + remoteUnix := "/run/lima-guestagent.sock" + a.onClose = append(a.onClose, func() error { logrus.Debugf("Stop forwarding unix sockets") var errs []error @@ -553,9 +563,19 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { } } } + if forwardUnix { + if err := forwardSSH(context.Background(), a.sshConfig, a.sshLocalPort, localUnix, remoteUnix, verbCancel, false); err != nil { + errs = append(errs, err) + } + } return errors.Join(errs...) }) for { + if a.client == nil || !isGuestAgentSocketAccessible(ctx, a.client) { + if forwardUnix { + _ = forwardSSH(ctx, a.sshConfig, a.sshLocalPort, localUnix, remoteUnix, verbForward, false) + } + } client, err := a.getOrCreateClient(ctx) if err == nil { if err := a.processGuestAgentEvents(ctx, client); err != nil { @@ -590,8 +610,18 @@ func (a *HostAgent) getOrCreateClient(ctx context.Context) (guestagentclient.Gue return a.client, err } -func (a *HostAgent) createClient(ctx context.Context) (guestagentclient.GuestAgentClient, error) { +func (a *HostAgent) createConnection(ctx context.Context) (net.Conn, error) { conn, err := a.driver.GuestAgentConn(ctx) + // default to forwarded sock + if conn == nil && err == nil { + var d net.Dialer + conn, err = d.DialContext(ctx, "unix", filepath.Join(a.instDir, filenames.GuestAgentSock)) + } + return conn, err +} + +func (a *HostAgent) createClient(ctx context.Context) (guestagentclient.GuestAgentClient, error) { + conn, err := a.createConnection(ctx) if err != nil { return nil, err } diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go index 8f786bf87a3f..6c48a297059b 100644 --- a/pkg/hostagent/requirements.go +++ b/pkg/hostagent/requirements.go @@ -6,7 +6,6 @@ import ( "time" "github.com/lima-vm/lima/pkg/limayaml" - "github.com/lima-vm/lima/pkg/store/filenames" "github.com/lima-vm/sshocker/pkg/ssh" "github.com/sirupsen/logrus" ) @@ -136,7 +135,7 @@ Also see "/var/log/cloud-init-output.log" in the guest. A possible workaround is to run "lima-guestagent install-systemd" in the guest. `, a.vSockPort), }) - } else { + } else if a.virtioPort != "" { req = append(req, requirement{ description: "the guest agent to be running", script: fmt.Sprintf(`#!/bin/bash @@ -146,12 +145,29 @@ if ! timeout 30s bash -c "until sudo fuser \"${sock}\" || sudo lsof \"${sock}\"; echo >&2 "lima-guestagent is not installed yet" exit 1 fi -`, filenames.VirtioPort), +`, a.virtioPort), debugHint: fmt.Sprintf(`The guest agent with serialport /dev/virtio-ports/%s does not seem running. Make sure that you are using an officially supported image. Also see "/var/log/cloud-init-output.log" in the guest. A possible workaround is to run "lima-guestagent install-systemd" in the guest. -`, filenames.VirtioPort), +`, a.virtioPort), + }) + } else { + req = append(req, requirement{ + description: "the guest agent to be running", + script: `#!/bin/bash +set -eux -o pipefail +sock="/run/lima-guestagent.sock" +if ! timeout 30s bash -c "until [ -S \"${sock}\" ]; do sleep 3; done"; then + echo >&2 "lima-guestagent is not installed yet" + exit 1 +fi +`, + debugHint: `The guest agent (/run/lima-guestagent.sock) does not seem running. +Make sure that you are using an officially supported image. +Also see "/var/log/cloud-init-output.log" in the guest. +A possible workaround is to run "lima-guestagent install-systemd" in the guest. +`, }) } return req diff --git a/website/content/en/docs/dev/Internals/_index.md b/website/content/en/docs/dev/Internals/_index.md index add318d24cbb..f4151b295908 100644 --- a/website/content/en/docs/dev/Internals/_index.md +++ b/website/content/en/docs/dev/Internals/_index.md @@ -72,10 +72,12 @@ VNC: Guest agent: -Each drivers use their own mode of communication +Each drivers use their own mode of communication - `qemu`: uses virtio-port `io.lima-vm.guest_agent.0` - `vz`: uses vsock port 2222 - `wsl2`: uses free random vsock port +The fallback is to use port forward over ssh port +- `ga.sock`: Forwarded to `/run/lima-guestagent.sock` in the guest, via SSH Host agent: - `ha.pid`: hostagent PID