Skip to content

Commit 5edd4c0

Browse files
committed
krunkit: support SSHOverVsock and replace SSH-based guestagent connection with vsock
Signed-off-by: Ansuman Sahoo <anshumansahoo500@gmail.com>
1 parent 4c791f1 commit 5edd4c0

3 files changed

Lines changed: 142 additions & 8 deletions

File tree

pkg/driver/krunkit/krunkit_darwin_arm64.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strconv"
1515

1616
"github.com/docker/go-units"
17+
"github.com/inetaf/tcpproxy"
1718
"github.com/lima-vm/go-qcow2reader/image/raw"
1819
"github.com/sirupsen/logrus"
1920

@@ -47,6 +48,12 @@ func Cmdline(inst *limatype.Instance) (*exec.Cmd, error) {
4748
// First virtio-blk device is the boot disk
4849
"--device", fmt.Sprintf("virtio-blk,path=%s,format=raw", filepath.Join(inst.Dir, filenames.Disk)),
4950
"--device", fmt.Sprintf("virtio-blk,path=%s", filepath.Join(inst.Dir, filenames.CIDataISO)),
51+
"--device", fmt.Sprintf("virtio-vsock,port=%d,socketURL=%s,connect", vSockPort, filepath.Join(inst.Dir, filenames.GuestAgentSock)),
52+
}
53+
54+
if inst.Config.SSH.OverVsock != nil && *inst.Config.SSH.OverVsock {
55+
sshVsockPath := filepath.Join(inst.Dir, sshVsockSock)
56+
args = append(args, "--device", fmt.Sprintf("virtio-vsock,port=22,socketURL=%s,connect", sshVsockPath))
5057
}
5158

5259
// Add additional disks
@@ -205,3 +212,50 @@ func startUsernet(ctx context.Context, inst *limatype.Instance) (*usernet.Client
205212
subnetIP, _, err := net.ParseCIDR(networks.SlirpNetwork)
206213
return usernet.NewClient(endpointSock, subnetIP), cancel, err
207214
}
215+
216+
func startVsockForwarder(ctx context.Context, unixSockPath, hostAddress string) error {
217+
// Test if the vsock port is open
218+
var d net.Dialer
219+
conn, err := d.DialContext(ctx, "unix", unixSockPath)
220+
if err != nil {
221+
return err
222+
}
223+
conn.Close()
224+
225+
var lc net.ListenConfig
226+
l, err := lc.Listen(ctx, "tcp", hostAddress)
227+
if err != nil {
228+
return err
229+
}
230+
go func() {
231+
<-ctx.Done()
232+
l.Close()
233+
}()
234+
logrus.Infof("Started krunkit vsock forwarder: %s -> %s", hostAddress, unixSockPath)
235+
go func() {
236+
defer l.Close()
237+
for {
238+
conn, err := l.Accept()
239+
if err != nil {
240+
if errors.Is(err, net.ErrClosed) {
241+
return
242+
}
243+
logrus.WithError(err).Errorf("krunkit vsock forwarder accept error: %v", err)
244+
} else {
245+
p := tcpproxy.DialProxy{
246+
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
247+
return d.DialContext(ctx, "unix", unixSockPath)
248+
},
249+
}
250+
go p.HandleConn(conn)
251+
}
252+
select {
253+
case <-ctx.Done():
254+
return
255+
default:
256+
continue
257+
}
258+
}
259+
}()
260+
return nil
261+
}

pkg/driver/krunkit/krunkit_driver_darwin_arm64.go

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os"
1313
"os/exec"
1414
"path/filepath"
15+
"strconv"
1516
"strings"
1617
"syscall"
1718
"time"
@@ -30,6 +31,11 @@ import (
3031
"github.com/lima-vm/lima/v2/pkg/ptr"
3132
)
3233

34+
const (
35+
vSockPort = 2222
36+
sshVsockSock = "ssh-vsock.sock"
37+
)
38+
3339
type LimaKrunkitDriver struct {
3440
Instance *limatype.Instance
3541
SSHLocalPort int
@@ -64,8 +70,8 @@ func (l *LimaKrunkitDriver) CreateDisk(ctx context.Context) error {
6470
}
6571

6672
func (l *LimaKrunkitDriver) Start(ctx context.Context) (chan error, error) {
67-
if l.Instance.Config.SSH.OverVsock != nil && *l.Instance.Config.SSH.OverVsock {
68-
logrus.Warn(".ssh.overVsock is not implemented yet for krunkit driver")
73+
if err := l.cleanupStaleSockets(); err != nil {
74+
logrus.WithError(err).Warn("Failed to clean up stale krunkit sockets before start")
6975
}
7076

7177
var err error
@@ -112,7 +118,27 @@ func (l *LimaKrunkitDriver) Start(ctx context.Context) (chan error, error) {
112118
l.krunkitWaitCh <- krunkitCmd.Wait()
113119
}()
114120

115-
err = l.usernetClient.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort)
121+
usernetSSHLocalPort := l.SSHLocalPort
122+
useSSHOverVsock := l.Instance.Config.SSH.OverVsock != nil && *l.Instance.Config.SSH.OverVsock
123+
124+
if !useSSHOverVsock {
125+
logrus.Info("ssh.overVsock is false, using usernet forwarder for SSH")
126+
} else if err := l.usernetClient.WaitOpeningSSHPort(ctx, l.Instance); err == nil {
127+
hostAddress := net.JoinHostPort(l.Instance.SSHAddress, strconv.Itoa(usernetSSHLocalPort))
128+
sshVsockPath := filepath.Join(l.Instance.Dir, sshVsockSock)
129+
if err := startVsockForwarder(ctx, sshVsockPath, hostAddress); err == nil {
130+
logrus.Infof("Detected SSH server is listening on the vsock port; changed %s to proxy for the vsock port", hostAddress)
131+
usernetSSHLocalPort = 0 // disable gvisor ssh port forwarding
132+
} else {
133+
logrus.WithError(err).WithField("hostAddress", hostAddress).
134+
Debugf("Failed to start vsock forwarder (systemd is older than v256?)")
135+
logrus.Info("SSH server does not seem to be running on vsock port, using usernet forwarder")
136+
}
137+
} else {
138+
logrus.WithError(err).Warn("Failed to wait for the guest SSH server to become available, falling back to usernet forwarder")
139+
}
140+
141+
err = l.usernetClient.ConfigureDriver(ctx, l.Instance, usernetSSHLocalPort)
116142
if err != nil {
117143
l.krunkitWaitCh <- fmt.Errorf("failed to configure usernet: %w", err)
118144
}
@@ -156,6 +182,29 @@ func (l *LimaKrunkitDriver) Validate(_ context.Context) error {
156182
return validateConfig(l.Instance.Config)
157183
}
158184

185+
func checkKrunkitVersion() error {
186+
cmd := exec.Command(vmType, "--version")
187+
output, err := cmd.Output()
188+
if err != nil {
189+
return fmt.Errorf("failed to check krunkit version: %w", err)
190+
}
191+
versionStr := strings.TrimSpace(string(output))
192+
versionStr = strings.TrimPrefix(versionStr, "krunkit ")
193+
194+
minVersion := semver.New("1.2.1")
195+
currentVersion, err := semver.NewVersion(versionStr)
196+
if err != nil {
197+
logrus.WithError(err).Warnf("Failed to parse krunkit version %q, skipping version check", versionStr)
198+
return nil
199+
}
200+
201+
if currentVersion.LessThan(*minVersion) {
202+
return fmt.Errorf("krunkit version %q is older than required minimum 1.2.1 (needed for vsock forwarder feature)", currentVersion)
203+
}
204+
205+
return nil
206+
}
207+
159208
func validateConfig(cfg *limatype.LimaYAML) error {
160209
if cfg == nil {
161210
return errors.New("configuration is nil")
@@ -171,7 +220,11 @@ func validateConfig(cfg *limatype.LimaYAML) error {
171220
return fmt.Errorf("unsupported arch: %q (krunkit requires native arch)", *cfg.Arch)
172221
}
173222
if _, err := exec.LookPath(vmType); err != nil {
174-
return errors.New("krunkit CLI not found in PATH. Install it via:\nbrew tap slp/krunkit\nbrew install krunkit")
223+
return errors.New("krunkit CLI not found in PATH. Install it via:\nbrew tap slp/krun\nbrew install krunkit")
224+
}
225+
// Also check if krunkit version >= 1.2.1 because of the vsock forwarder feature
226+
if err := checkKrunkitVersion(); err != nil {
227+
return err
175228
}
176229

177230
if cfg.MountType != nil && (*cfg.MountType != limatype.VIRTIOFS && *cfg.MountType != limatype.REVSSHFS) {
@@ -216,6 +269,10 @@ func (l *LimaKrunkitDriver) FillConfig(_ context.Context, cfg *limatype.LimaYAML
216269

217270
cfg.VMType = ptr.Of(vmType)
218271

272+
if cfg.SSH.OverVsock == nil {
273+
cfg.SSH.OverVsock = ptr.Of(cfg.OS != nil && *cfg.OS == limatype.LINUX)
274+
}
275+
219276
return validateConfig(cfg)
220277
}
221278

@@ -260,6 +317,7 @@ func (l *LimaKrunkitDriver) Create(_ context.Context) error {
260317
func (l *LimaKrunkitDriver) Info() driver.Info {
261318
var info driver.Info
262319
info.Name = vmType
320+
info.VsockPort = vSockPort
263321
if l.Instance != nil && l.Instance.Dir != "" {
264322
info.InstanceDir = l.Instance.Dir
265323
}
@@ -277,7 +335,7 @@ func (l *LimaKrunkitDriver) SSHAddress(_ context.Context) (string, error) {
277335
}
278336

279337
func (l *LimaKrunkitDriver) ForwardGuestAgent() bool {
280-
return true
338+
return false
281339
}
282340

283341
func (l *LimaKrunkitDriver) Delete(_ context.Context) error {
@@ -324,10 +382,32 @@ func (l *LimaKrunkitDriver) Unregister(_ context.Context) error {
324382
return nil
325383
}
326384

327-
func (l *LimaKrunkitDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
328-
return nil, "unix", nil
385+
func (l *LimaKrunkitDriver) GuestAgentConn(ctx context.Context) (net.Conn, string, error) {
386+
var d net.Dialer
387+
conn, err := d.DialContext(ctx, "unix", filepath.Join(l.Instance.Dir, filenames.GuestAgentSock))
388+
return conn, "unix", err
329389
}
330390

331391
func (l *LimaKrunkitDriver) AdditionalSetupForSSH(_ context.Context) error {
332392
return nil
333393
}
394+
395+
// Currently, Krunkit does not clean-up the vsock unix socket when the VM stops
396+
// Issue: https://github.com/containers/krunkit/issues/101
397+
func (l *LimaKrunkitDriver) cleanupStaleSockets() error {
398+
if l.Instance == nil || l.Instance.Dir == "" {
399+
return nil
400+
}
401+
402+
var errs []error
403+
for _, sock := range []string{
404+
filepath.Join(l.Instance.Dir, filenames.GuestAgentSock),
405+
filepath.Join(l.Instance.Dir, sshVsockSock),
406+
} {
407+
if err := os.RemoveAll(sock); err != nil {
408+
errs = append(errs, fmt.Errorf("failed to remove socket %q: %w", sock, err))
409+
}
410+
}
411+
412+
return errors.Join(errs...)
413+
}

website/content/en/docs/config/vmtype/krunkit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Krunkit runs super‑light VMs on macOS/ARM64 with a focus on GPU access. It bui
1313

1414
## Install krunkit (host)
1515
```bash
16-
brew tap slp/krunkit
16+
brew tap slp/krun
1717
brew install krunkit
1818
```
1919
For reference: https://github.com/slp/homebrew-krun

0 commit comments

Comments
 (0)