Skip to content
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

Support for native driver specific guest agent connection #1998

Merged
merged 1 commit into from
Nov 17, 2023
Merged
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
37 changes: 20 additions & 17 deletions cmd/lima-guestagent/daemon_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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")
}
Expand All @@ -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)
}
1 change: 1 addition & 0 deletions cmd/lima-guestagent/install_systemd_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
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 was needed to properly upgrade on first start.

Without this, the below case was failing
lima 0.15 create and start -> stop -> lima 1 start -> guest is updated but not restarted to pick up latest one -> This will be taken only after second restart

}
for _, args := range args {
cmd := exec.Command("systemctl", append([]string{"--system"}, args...)...)
Expand Down
8 changes: 2 additions & 6 deletions pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}"
Copy link
Member

@AkihiroSuda AkihiroSuda Nov 14, 2023

Choose a reason for hiding this comment

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

--vsock-port shouldn't be specified for qemu mode?

Copy link
Member Author

Choose a reason for hiding this comment

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

True but by default the port will be 0 for qemu so either way it will be ignored

fi
8 changes: 8 additions & 0 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package driver
import (
"context"
"fmt"
"net"

"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/store"
Expand Down Expand Up @@ -62,13 +63,16 @@ type Driver interface {
DeleteSnapshot(_ context.Context, tag string) error

ListSnapshots(_ context.Context) (string, error)

GuestAgentConn(_ context.Context) (net.Conn, error)
}

type BaseDriver struct {
Instance *store.Instance
Yaml *limayaml.LimaYAML

SSHLocalPort int
VSockPort int
}

var _ Driver = (*BaseDriver)(nil)
Expand Down Expand Up @@ -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")
}
35 changes: 4 additions & 31 deletions pkg/guestagent/api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}

Expand Down
15 changes: 0 additions & 15 deletions pkg/guestagent/api/client/client_others.go

This file was deleted.

16 changes: 0 additions & 16 deletions pkg/guestagent/api/client/client_windows.go

This file was deleted.

56 changes: 56 additions & 0 deletions pkg/guestagent/serialport/serialconn_linux.go
Original file line number Diff line number Diff line change
@@ -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
}
63 changes: 63 additions & 0 deletions pkg/guestagent/serialport/seriallistener_linux.go
Original file line number Diff line number Diff line change
@@ -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
}
43 changes: 43 additions & 0 deletions pkg/guestagent/serialport/serialport_linux.go
Original file line number Diff line number Diff line change
@@ -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()
}
Loading
Loading