From f947de0f837f419b40f9d0dea5b6d4e1a82a18bb Mon Sep 17 00:00:00 2001 From: Balaji Vijayakumar Date: Tue, 14 Nov 2023 15:22:28 +0530 Subject: [PATCH] Support for native guest agent connection for each driver Signed-off-by: Balaji Vijayakumar --- cmd/lima-guestagent/daemon_linux.go | 37 +++++----- cmd/lima-guestagent/install_systemd_linux.go | 1 + .../boot/25-guestagent-base.sh | 8 +-- pkg/driver/driver.go | 8 +++ pkg/guestagent/api/client/client.go | 35 ++------- pkg/guestagent/api/client/client_others.go | 15 ---- pkg/guestagent/api/client/client_windows.go | 16 ----- pkg/guestagent/serialport/serialconn_linux.go | 56 +++++++++++++++ .../serialport/seriallistener_linux.go | 63 ++++++++++++++++ pkg/guestagent/serialport/serialport_linux.go | 43 +++++++++++ pkg/hostagent/hostagent.go | 72 +++++++++---------- pkg/hostagent/requirements.go | 22 +++--- pkg/httpclientutil/httpclientutil.go | 14 ++++ pkg/httpclientutil/httpclientutil_others.go | 15 ---- pkg/httpclientutil/httpclientutil_windows.go | 36 ---------- pkg/qemu/qemu.go | 10 ++- pkg/qemu/qemu_driver.go | 6 ++ pkg/store/filenames/filenames.go | 1 + pkg/vz/vz_driver_darwin.go | 14 +++- pkg/wsl2/vm_windows.go | 12 ++-- pkg/wsl2/wsl_driver_windows.go | 6 ++ .../content/en/docs/dev/Internals/_index.md | 6 +- 22 files changed, 301 insertions(+), 195 deletions(-) delete mode 100644 pkg/guestagent/api/client/client_others.go delete mode 100644 pkg/guestagent/api/client/client_windows.go create mode 100644 pkg/guestagent/serialport/serialconn_linux.go create mode 100644 pkg/guestagent/serialport/seriallistener_linux.go create mode 100644 pkg/guestagent/serialport/serialport_linux.go diff --git a/cmd/lima-guestagent/daemon_linux.go b/cmd/lima-guestagent/daemon_linux.go index 47a3d27656a..4e029415c00 100644 --- a/cmd/lima-guestagent/daemon_linux.go +++ b/cmd/lima-guestagent/daemon_linux.go @@ -10,6 +10,8 @@ import ( "github.com/gorilla/mux" "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" @@ -26,16 +28,24 @@ func newDaemonCommand() *cobra.Command { 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 } - vSockPort, err := cmd.Flags().GetInt("vsock-port") + vSockPortOverride, err := cmd.Flags().GetInt("vsock-port") if err != nil { return err } + if vSockPortOverride != 0 { + vSockPort = vSockPortOverride + } if tick == 0 { return errors.New("tick must be specified") } @@ -62,29 +72,22 @@ 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 vSockPort != 0 { - vsockL, err := vsock.Listen(uint32(vSockPort), nil) + if _, err := os.Stat(virtioPort); err == nil { + qemuL, err := serialport.Listen(virtioPort) if err != nil { return err } - l = vsockL - logrus.Infof("serving the guest agent on vsock port: %d", vSockPort) - } else { - socketL, err := net.Listen("unix", socket) + l = qemuL + logrus.Infof("serving the guest agent on qemu serial file: %s", virtioPort) + } else if vSockPort != 0 { + vsockL, err := vsock.Listen(uint32(vSockPort), nil) 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) + l = vsockL + logrus.Infof("serving the guest agent on vsock port: %d", vSockPort) } return srv.Serve(l) } diff --git a/cmd/lima-guestagent/install_systemd_linux.go b/cmd/lima-guestagent/install_systemd_linux.go index 984edcee19a..400b3cfe76c 100644 --- a/cmd/lima-guestagent/install_systemd_linux.go +++ b/cmd/lima-guestagent/install_systemd_linux.go @@ -49,6 +49,7 @@ func installSystemdAction(cmd *cobra.Command, _ []string) error { args := [][]string{ {"daemon-reload"}, {"enable", "--now", "lima-guestagent.service"}, + {"try-restart", "lima-guestagent.service"}, } for _, args := range args { cmd := exec.Command("systemctl", append([]string{"--system"}, args...)...) 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 379f9c4b3bf..968fc20e06f 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" +command_args="daemon --vsock-port "${LIMA_CIDATA_VSOCK_PORT}"" command_background=true pidfile="/run/lima-guestagent.pid" EOF @@ -40,9 +40,5 @@ else # Remove legacy systemd service rm -f "${LIMA_CIDATA_HOME}/.config/systemd/user/lima-guestagent.service" - if [ "$LIMA_CIDATA_VMTYPE" = "wsl2" ]; then - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" - else - sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd - fi + sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --vsock-port "${LIMA_CIDATA_VSOCK_PORT}" fi diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 837a16e5ca5..8677249f458 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -3,6 +3,7 @@ package driver import ( "context" "fmt" + "net" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/store" @@ -62,6 +63,8 @@ type Driver interface { DeleteSnapshot(_ context.Context, tag string) error ListSnapshots(_ context.Context) (string, error) + + GuestAgentConn(_ context.Context) (net.Conn, error) } type BaseDriver struct { @@ -69,6 +72,7 @@ type BaseDriver struct { Yaml *limayaml.LimaYAML SSHLocalPort int + VSockPort int } var _ Driver = (*BaseDriver)(nil) @@ -132,3 +136,7 @@ func (d *BaseDriver) DeleteSnapshot(_ context.Context, _ string) error { func (d *BaseDriver) ListSnapshots(_ context.Context) (string, error) { return "", fmt.Errorf("unimplemented") } + +func (d *BaseDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { + return nil, fmt.Errorf("unimplemented") +} diff --git a/pkg/guestagent/api/client/client.go b/pkg/guestagent/api/client/client.go index d58aaeefe19..4450cdd2680 100644 --- a/pkg/guestagent/api/client/client.go +++ b/pkg/guestagent/api/client/client.go @@ -9,7 +9,6 @@ import ( "fmt" "net" "net/http" - "strconv" "github.com/lima-vm/lima/pkg/guestagent/api" "github.com/lima-vm/lima/pkg/httpclientutil" @@ -21,39 +20,13 @@ type GuestAgentClient interface { Events(context.Context, func(api.Event)) error } -type Proto = string - -const ( - UNIX Proto = "unix" - VSOCK Proto = "vsock" -) - // NewGuestAgentClient creates a client. // remote is a path to the UNIX socket, without unix:// prefix or a remote hostname/IP address. -func NewGuestAgentClient(remote string, proto Proto, instanceName string) (GuestAgentClient, error) { - var hc *http.Client - switch proto { - case UNIX: - hcSock, err := httpclientutil.NewHTTPClientWithSocketPath(remote) - if err != nil { - return nil, err - } - hc = hcSock - case VSOCK: - _, p, err := net.SplitHostPort(remote) - if err != nil { - return nil, err - } - port, err := strconv.Atoi(p) - if err != nil { - return nil, err - } - hc, err = newVSockGuestAgentClient(port, instanceName) - if err != nil { - return nil, err - } +func NewGuestAgentClient(conn net.Conn) (GuestAgentClient, error) { + hc, err := httpclientutil.NewHTTPClientWithConn(conn) + if err != nil { + return nil, err } - return NewGuestAgentClientWithHTTPClient(hc), nil } diff --git a/pkg/guestagent/api/client/client_others.go b/pkg/guestagent/api/client/client_others.go deleted file mode 100644 index 3c94fb24453..00000000000 --- a/pkg/guestagent/api/client/client_others.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !windows - -package client - -import ( - "net/http" - - "github.com/lima-vm/lima/pkg/httpclientutil" -) - -func newVSockGuestAgentClient(port int, _ string) (*http.Client, error) { - hc := httpclientutil.NewHTTPClientWithVSockPort(port) - - return hc, nil -} diff --git a/pkg/guestagent/api/client/client_windows.go b/pkg/guestagent/api/client/client_windows.go deleted file mode 100644 index a8a70ee6cc3..00000000000 --- a/pkg/guestagent/api/client/client_windows.go +++ /dev/null @@ -1,16 +0,0 @@ -package client - -import ( - "net/http" - - "github.com/lima-vm/lima/pkg/httpclientutil" -) - -func newVSockGuestAgentClient(port int, instanceName string) (*http.Client, error) { - hc, err := httpclientutil.NewHTTPClientWithVSockPort(instanceName, port) - if err != nil { - return nil, err - } - - return hc, nil -} diff --git a/pkg/guestagent/serialport/serialconn_linux.go b/pkg/guestagent/serialport/serialconn_linux.go new file mode 100644 index 00000000000..73fffceb8f7 --- /dev/null +++ b/pkg/guestagent/serialport/serialconn_linux.go @@ -0,0 +1,56 @@ +package serialport + +import ( + "net" + "time" +) + +type SerialConn struct { + serialDevice string + port *Port +} + +var _ net.Conn = (*SerialConn)(nil) + +func DialSerial(serialDevice string) (*SerialConn, error) { + s, err := openPort(serialDevice) + if err != nil { + return nil, err + } + + return &SerialConn{port: s, serialDevice: serialDevice}, nil +} + +func (c *SerialConn) Read(b []byte) (n int, err error) { + return c.port.Read(b) +} + +func (c *SerialConn) Write(b []byte) (n int, err error) { + return c.port.Write(b) +} + +func (c *SerialConn) Close() error { + // There is no need to close the serial port every time. + // So just do nothing. + return nil +} + +func (c *SerialConn) LocalAddr() net.Addr { + return &net.UnixAddr{Name: "virtio-port:" + c.serialDevice, Net: "virtio"} +} + +func (c *SerialConn) RemoteAddr() net.Addr { + return &net.UnixAddr{Name: "qemu-host", Net: "virtio"} +} + +func (c *SerialConn) SetDeadline(_ time.Time) error { + return nil +} + +func (c *SerialConn) SetReadDeadline(_ time.Time) error { + return nil +} + +func (c *SerialConn) SetWriteDeadline(_ time.Time) error { + return nil +} diff --git a/pkg/guestagent/serialport/seriallistener_linux.go b/pkg/guestagent/serialport/seriallistener_linux.go new file mode 100644 index 00000000000..1a2ec61765c --- /dev/null +++ b/pkg/guestagent/serialport/seriallistener_linux.go @@ -0,0 +1,63 @@ +package serialport + +import ( + "net" + "sync" + "syscall" + + "golang.org/x/net/netutil" +) + +type SerialListener struct { + mu sync.Mutex + conn *SerialConn + closed bool +} + +func Listen(serialDevice string) (net.Listener, error) { + c, err := DialSerial(serialDevice) + if err != nil { + return nil, &net.OpError{Op: "dial", Net: "virtio", Source: c.LocalAddr(), Addr: nil, Err: err} + } + + return netutil.LimitListener(&SerialListener{conn: c}, 1), nil +} + +func (ln *SerialListener) ok() bool { + return ln != nil && ln.conn != nil && !ln.closed +} + +func (ln *SerialListener) Accept() (net.Conn, error) { + ln.mu.Lock() + defer ln.mu.Unlock() + + if !ln.ok() { + return nil, syscall.EINVAL + } + + return ln.conn, nil +} + +func (ln *SerialListener) Close() error { + ln.mu.Lock() + defer ln.mu.Unlock() + + if !ln.ok() { + return syscall.EINVAL + } + + if ln.closed { + return nil + } + ln.closed = true + + return nil +} + +func (ln *SerialListener) Addr() net.Addr { + if ln.ok() { + return ln.conn.LocalAddr() + } + + return nil +} diff --git a/pkg/guestagent/serialport/serialport_linux.go b/pkg/guestagent/serialport/serialport_linux.go new file mode 100644 index 00000000000..63b4c303239 --- /dev/null +++ b/pkg/guestagent/serialport/serialport_linux.go @@ -0,0 +1,43 @@ +package serialport + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func openPort(name string) (p *Port, err error) { + f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0o666) + if err != nil { + return nil, err + } + + defer func() { + if err != nil && f != nil { + f.Close() + } + }() + + fd := f.Fd() + if err = unix.SetNonblock(int(fd), false); err != nil { + return nil, err + } + + return &Port{f: f}, nil +} + +type Port struct { + f *os.File +} + +func (p *Port) Read(b []byte) (n int, err error) { + return p.f.Read(b) +} + +func (p *Port) Write(b []byte) (n int, err error) { + return p.f.Write(b) +} + +func (p *Port) Close() (err error) { + return p.f.Close() +} diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index 5893a669282..cb6fddfdf33 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -46,7 +46,6 @@ type HostAgent struct { sshConfig *ssh.SSHConfig portForwarder *portForwarder onClose []func() error // LIFO - guestAgentProto guestagentclient.Proto driver driver.Driver sigintCh chan os.Signal @@ -55,6 +54,9 @@ type HostAgent struct { eventEncMu sync.Mutex vSockPort int + + clientMu sync.RWMutex + client guestagentclient.GuestAgentClient } type options struct { @@ -111,13 +113,10 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt } } - guestAgentProto := guestagentclient.UNIX - if *y.VMType == limayaml.WSL2 { - guestAgentProto = guestagentclient.VSOCK - } - vSockPort := 0 - if guestAgentProto == guestagentclient.VSOCK { + if *y.VMType == limayaml.VZ { + vSockPort = 2222 + } else if *y.VMType == limayaml.WSL2 { port, err := getFreeVSockPort() if err != nil { logrus.WithError(err).Error("failed to get free VSock port") @@ -157,6 +156,7 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt Instance: inst, Yaml: y, SSHLocalPort: sshLocalPort, + VSockPort: vSockPort, }) a := &HostAgent{ @@ -173,7 +173,6 @@ func New(instName string, stdout io.Writer, sigintCh chan os.Signal, opts ...Opt sigintCh: sigintCh, eventEnc: json.NewEncoder(stdout), vSockPort: vSockPort, - guestAgentProto: guestAgentProto, } return a, nil } @@ -542,9 +541,6 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { } } - 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 @@ -557,27 +553,18 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { } } } - if err := forwardSSH(context.Background(), a.sshConfig, a.sshLocalPort, localUnix, remoteUnix, verbCancel, false); err != nil { - errs = append(errs, err) - } return errors.Join(errs...) }) - - guestSocketAddr := localUnix - if a.guestAgentProto == guestagentclient.VSOCK { - guestSocketAddr = fmt.Sprintf("0.0.0.0:%d", a.vSockPort) - } - for { - if !isGuestAgentSocketAccessible(ctx, guestSocketAddr, a.guestAgentProto, a.instName) { - if a.guestAgentProto != guestagentclient.VSOCK { - _ = forwardSSH(ctx, a.sshConfig, a.sshLocalPort, localUnix, remoteUnix, verbForward, false) - } - } - if err := a.processGuestAgentEvents(ctx, guestSocketAddr, a.guestAgentProto, a.instName); err != nil { - if !errors.Is(err, context.Canceled) { - logrus.WithError(err).Warn("connection to the guest agent was closed unexpectedly") + client, err := a.getOrCreateClient(ctx) + if err == nil { + if err := a.processGuestAgentEvents(ctx, client); err != nil { + if !errors.Is(err, context.Canceled) { + logrus.WithError(err).Warn("connection to the guest agent was closed unexpectedly", err) + } } + } else { + logrus.WithError(err).Warn("connection to the guest agent was closed unexpectedly", err) } select { case <-ctx.Done(): @@ -587,21 +574,32 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { } } -func isGuestAgentSocketAccessible(ctx context.Context, localUnix string, proto guestagentclient.Proto, instanceName string) bool { - client, err := guestagentclient.NewGuestAgentClient(localUnix, proto, instanceName) - if err != nil { - return false - } - _, err = client.Info(ctx) +func isGuestAgentSocketAccessible(ctx context.Context, client guestagentclient.GuestAgentClient) bool { + _, err := client.Info(ctx) return err == nil } -func (a *HostAgent) processGuestAgentEvents(ctx context.Context, localUnix string, proto guestagentclient.Proto, instanceName string) error { - client, err := guestagentclient.NewGuestAgentClient(localUnix, proto, instanceName) +func (a *HostAgent) getOrCreateClient(ctx context.Context) (guestagentclient.GuestAgentClient, error) { + a.clientMu.Lock() + defer a.clientMu.Unlock() + if a.client != nil && isGuestAgentSocketAccessible(ctx, a.client) { + return a.client, nil + } + var err error + a.client, err = a.createClient(ctx) + return a.client, err +} + +func (a *HostAgent) createClient(ctx context.Context) (guestagentclient.GuestAgentClient, error) { + conn, err := a.driver.GuestAgentConn(ctx) if err != nil { - return err + return nil, err } + return guestagentclient.NewGuestAgentClient(conn) +} + +func (a *HostAgent) processGuestAgentEvents(ctx context.Context, client guestagentclient.GuestAgentClient) error { info, err := client.Info(ctx) if err != nil { return err diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go index fa0802053ca..8f786bf87a3 100644 --- a/pkg/hostagent/requirements.go +++ b/pkg/hostagent/requirements.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - guestagentclient "github.com/lima-vm/lima/pkg/guestagent/api/client" "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" ) @@ -120,38 +120,38 @@ fi debugHint: `Append "user_allow_other" to /etc/fuse.conf (/etc/fuse3.conf) in the guest`, }) } - if a.guestAgentProto == guestagentclient.VSOCK { + if a.vSockPort != 0 { req = append(req, requirement{ description: "the guest agent to be running", script: fmt.Sprintf(`#!/bin/bash set -eux -o pipefail -if ! timeout 30s bash -c "until ss -a -n --vsock --listen | grep -q '*:%d'; do sleep 3; done"; then +if ! timeout 30s bash -c "until ss -a -n --vsock --listen | grep -q ':%d'; do sleep 3; done"; then echo >&2 "lima-guestagent is not installed yet" exit 1 fi `, a.vSockPort), - debugHint: `The guest agent (/run/lima-guestagent.sock) does not seem running. + debugHint: fmt.Sprintf(`The guest agent with vsockPort %d 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. -`, +`, a.vSockPort), }) } else { req = append(req, requirement{ description: "the guest agent to be running", - script: `#!/bin/bash + script: fmt.Sprintf(`#!/bin/bash set -eux -o pipefail -sock="/run/lima-guestagent.sock" -if ! timeout 30s bash -c "until [ -S \"${sock}\" ]; do sleep 3; done"; then +sock="/dev/virtio-ports/%s" +if ! timeout 30s bash -c "until sudo fuser \"${sock}\" || sudo lsof \"${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. +`, filenames.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), }) } return req diff --git a/pkg/httpclientutil/httpclientutil.go b/pkg/httpclientutil/httpclientutil.go index 90b6c3fecbf..40ddef13a07 100644 --- a/pkg/httpclientutil/httpclientutil.go +++ b/pkg/httpclientutil/httpclientutil.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "github.com/lima-vm/lima/pkg/httputil" @@ -83,3 +84,16 @@ func Successful(resp *http.Response) error { } return nil } + +// NewHTTPClientWithConn creates a client. +// conn is a raw net.Conn instance. +func NewHTTPClientWithConn(conn net.Conn) (*http.Client, error) { + hc := &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return conn, nil + }, + }, + } + return hc, nil +} diff --git a/pkg/httpclientutil/httpclientutil_others.go b/pkg/httpclientutil/httpclientutil_others.go index 1ab6994827a..e3b4315f5b5 100644 --- a/pkg/httpclientutil/httpclientutil_others.go +++ b/pkg/httpclientutil/httpclientutil_others.go @@ -7,8 +7,6 @@ import ( "net" "net/http" "os" - - "github.com/mdlayher/vsock" ) // NewHTTPClientWithSocketPath creates a client. @@ -27,16 +25,3 @@ func NewHTTPClientWithSocketPath(socketPath string) (*http.Client, error) { } return hc, nil } - -// NewHTTPClientWithVSockPort creates a client. -// port is the port to use for the vsock. -func NewHTTPClientWithVSockPort(port int) *http.Client { - hc := &http.Client{ - Transport: &http.Transport{ - Dial: func(_, _ string) (net.Conn, error) { - return vsock.Dial(2, uint32(port), nil) - }, - }, - } - return hc -} diff --git a/pkg/httpclientutil/httpclientutil_windows.go b/pkg/httpclientutil/httpclientutil_windows.go index 889fe43d9c2..c6959744034 100644 --- a/pkg/httpclientutil/httpclientutil_windows.go +++ b/pkg/httpclientutil/httpclientutil_windows.go @@ -2,14 +2,9 @@ package httpclientutil import ( "context" - "fmt" "net" "net/http" "os" - - winio "github.com/Microsoft/go-winio" - "github.com/Microsoft/go-winio/pkg/guid" - "github.com/lima-vm/lima/pkg/windows" ) // NewHTTPClientWithSocketPath creates a client. @@ -29,34 +24,3 @@ func NewHTTPClientWithSocketPath(socketPath string) (*http.Client, error) { } return hc, nil } - -// NewHTTPClientWithVSockPort creates a client for a vsock port. -func NewHTTPClientWithVSockPort(instanceName string, port int) (*http.Client, error) { - VMIDStr, err := windows.GetInstanceVMID(fmt.Sprintf("lima-%s", instanceName)) - if err != nil { - return nil, err - } - VMIDGUID, err := guid.FromString(VMIDStr) - if err != nil { - return nil, err - } - - serviceGUID, err := guid.FromString(fmt.Sprintf("%x%s", port, windows.MagicVSOCKSuffix)) - if err != nil { - return nil, err - } - - sockAddr := &winio.HvsockAddr{ - VMID: VMIDGUID, - ServiceID: serviceGUID, - } - - hc := &http.Client{ - Transport: &http.Transport{ - DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return winio.Dial(ctx, sockAddr) - }, - }, - } - return hc, nil -} diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go index 0d0d7f50ac4..e2b6ee2d576 100644 --- a/pkg/qemu/qemu.go +++ b/pkg/qemu/qemu.go @@ -43,7 +43,9 @@ type Config struct { } // MinimumQemuVersion is the minimum supported QEMU version -const MinimumQemuVersion = "4.0.0" +const ( + MinimumQemuVersion = "4.0.0" +) // EnsureDisk also ensures the kernel and the initrd func EnsureDisk(cfg Config) error { @@ -888,6 +890,12 @@ func Cmdline(cfg Config) (string, []string, error) { args = append(args, "-chardev", fmt.Sprintf("socket,id=%s,path=%s,server=on,wait=off", qmpChardev, qmpSock)) args = append(args, "-qmp", "chardev:"+qmpChardev) + // Guest agent via serialport + guestSock := filepath.Join(cfg.InstanceDir, filenames.GuestAgentSock) + args = append(args, "-chardev", fmt.Sprintf("socket,path=%s,server=on,wait=off,id=qga0", guestSock)) + args = append(args, "-device", "virtio-serial") + args = append(args, "-device", "virtserialport,chardev=qga0,name="+filenames.VirtioPort) + // QEMU process args = append(args, "-name", "lima-"+cfg.Name) args = append(args, "-pidfile", filepath.Join(cfg.InstanceDir, filenames.PIDFile(*y.VMType))) diff --git a/pkg/qemu/qemu_driver.go b/pkg/qemu/qemu_driver.go index d41da32a625..c500721eaea 100644 --- a/pkg/qemu/qemu_driver.go +++ b/pkg/qemu/qemu_driver.go @@ -394,6 +394,12 @@ func (l *LimaQemuDriver) ListSnapshots(_ context.Context) (string, error) { return List(qCfg, l.Instance.Status == store.StatusRunning) } +func (l *LimaQemuDriver) GuestAgentConn(ctx context.Context) (net.Conn, error) { + var d net.Dialer + dialContext, err := d.DialContext(ctx, "unix", filepath.Join(l.Instance.Dir, filenames.GuestAgentSock)) + return dialContext, err +} + type qArgTemplateApplier struct { files []*os.File } diff --git a/pkg/store/filenames/filenames.go b/pkg/store/filenames/filenames.go index a273ed8662e..c630fb53947 100644 --- a/pkg/store/filenames/filenames.go +++ b/pkg/store/filenames/filenames.go @@ -47,6 +47,7 @@ const ( VNCDisplayFile = "vncdisplay" VNCPasswordFile = "vncpassword" GuestAgentSock = "ga.sock" + VirtioPort = "io.lima-vm.guest_agent.0" HostAgentPID = "ha.pid" HostAgentSock = "ha.sock" HostAgentStdoutLog = "ha.stdout.log" diff --git a/pkg/vz/vz_driver_darwin.go b/pkg/vz/vz_driver_darwin.go index cc048094181..781a59dd0fb 100644 --- a/pkg/vz/vz_driver_darwin.go +++ b/pkg/vz/vz_driver_darwin.go @@ -6,17 +6,17 @@ import ( "context" "errors" "fmt" + "net" "path/filepath" "time" - "github.com/lima-vm/lima/pkg/reflectutil" - "github.com/Code-Hex/vz/v3" "github.com/sirupsen/logrus" "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/limayaml" + "github.com/lima-vm/lima/pkg/reflectutil" ) const Enabled = true @@ -197,3 +197,13 @@ func (l *LimaVzDriver) Stop(_ context.Context) error { return errors.New("vz: CanRequestStop is not supported") } + +func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { + for _, socket := range l.machine.SocketDevices() { + connect, err := socket.Connect(uint32(l.VSockPort)) + if err == nil && connect.SourcePort() != 0 { + return connect, nil + } + } + return nil, fmt.Errorf("unable to connect to guest agent via vsock port 2222") +} diff --git a/pkg/wsl2/vm_windows.go b/pkg/wsl2/vm_windows.go index 2b4b33b7f3d..664a80c6d08 100644 --- a/pkg/wsl2/vm_windows.go +++ b/pkg/wsl2/vm_windows.go @@ -125,13 +125,11 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri } for { - select { - case <-ctx.Done(): - logrus.Info("Context closed, stopping vm") - if status, err := store.GetWslStatus(instanceName); err == nil && - status == store.StatusRunning { - stopVM(ctx, distroName) - } + <-ctx.Done() + logrus.Info("Context closed, stopping vm") + if status, err := store.GetWslStatus(instanceName); err == nil && + status == store.StatusRunning { + _ = stopVM(ctx, distroName) } } }() diff --git a/pkg/wsl2/wsl_driver_windows.go b/pkg/wsl2/wsl_driver_windows.go index 43ec3a7874c..99455f14795 100644 --- a/pkg/wsl2/wsl_driver_windows.go +++ b/pkg/wsl2/wsl_driver_windows.go @@ -3,12 +3,14 @@ package wsl2 import ( "context" "fmt" + "net" "regexp" "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/limayaml" "github.com/lima-vm/lima/pkg/reflectutil" "github.com/lima-vm/lima/pkg/store" + "github.com/mdlayher/vsock" "github.com/sirupsen/logrus" ) @@ -166,3 +168,7 @@ func (l *LimaWslDriver) Unregister(ctx context.Context) error { logrus.Info("VM not registered, skipping unregistration") return nil } + +func (l *LimaWslDriver) GuestAgentConn(_ context.Context) (net.Conn, error) { + return vsock.Dial(2, uint32(l.VSockPort), nil) +} diff --git a/website/content/en/docs/dev/Internals/_index.md b/website/content/en/docs/dev/Internals/_index.md index fac25697392..add318d24cb 100644 --- a/website/content/en/docs/dev/Internals/_index.md +++ b/website/content/en/docs/dev/Internals/_index.md @@ -71,7 +71,11 @@ VNC: - `vncpassword`: VNC display password Guest agent: -- `ga.sock`: Forwarded to `/run/lima-guestagent.sock` in the guest, via SSH + +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 Host agent: - `ha.pid`: hostagent PID