Skip to content

Commit 4cd78ff

Browse files
committed
feat: Add limactl vz-vmnet
It provides `VmnetNetwork` serialization to VMs. `limactl vz-vmnet` takes flags: - `--enable-mach-service[=true,false]`: register/unregister Mach service to `launchd` - It creates a launchd plist under ~/Library/LaunchAgents and bootstraps it. - The Mach service "io.lima-vm.vz.vmnet" is registered. - The working directory is $LIMA_HOME/_networks/vz-vmnet. - It also creates a shell script named "io.lima-vm.vz.vmnet.sh" that runs "limactl vz-vmnet" to avoid launching "limactl" directly from launchd. macOS System Settings (General > Login Items & Extensions) shows the first element of `ProgramArguments` as the login item name; using a shell script with a fixed filename makes the item easier to identify. - There is no need to register manually because the VZ driver is registered as appropriate. - `--mach-service=<service name>`: launched as Mach server by `launchd` via `io.lima-vm.vz.vmnet.plist` - Launched on demand to connection from VZ driver by `launchd`. - Receives a request payload from VZ driver with fields: - `Network`: name of the network ("shared", "host", etc) - `Configuration`: `[]bytes@ representing `VzNetworkConfig` in JSON. - Validates clients are the same executable (cdhash) by using xpc_peer_requirement API. - Create `VmnetNetwork` from provided `Configuration` if cached one does not exist. - Replies to VZ driver with fields: - `Configuration`: If `VmnetNetwork` is cached, it may be created by different configuration. - `Serialization`: newly created or cached. - Monitors changes of networks - When the interface created by `VmnetNetwork` disappears from host, remove them from cache. - If all `VmnetNetwork` are removed, `limactl vz-vmnet` exits. VZ driver (hostagent) does: - Read `.vz` VzVmnetConfig from `networks.yaml` - Use them on `- vz: <network>` fields; "shared" and "host" network are predefined. - Register `limactl vz-vmnet` to `launchd` if not registered. - Request serialization to the Mach service "io.lima-vm.vz.vmnet". - Create `VmnetNetwork` by provided serialization, then use them. Additional changes: - Because shutdown takes longer on using `VmnetNetwork`: - Extend VZ driver's shutdown timeout from 5 seconds to 15 seconds - Add `ExitTimeOut` key with 20 seconds to autostart `io.lima-vm.autostart.INSTANCE.plist` - `lima.yaml`: `- vzShared` and `- vzHost` are renamed to `- vz: shared` and `- vz: host` Signed-off-by: Norio Nomura <[email protected]>
1 parent 8ef8be5 commit 4cd78ff

28 files changed

+1176
-82
lines changed

cmd/limactl/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ func newApp() *cobra.Command {
208208
newNetworkCommand(),
209209
newCloneCommand(),
210210
newRenameCommand(),
211+
newvzvmnetCommand(),
211212
)
212213
addPluginCommands(rootCmd)
213214

cmd/limactl/vz-vmnet.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"github.com/spf13/cobra"
8+
)
9+
10+
func newvzvmnetCommand() *cobra.Command {
11+
newCommand := &cobra.Command{
12+
Use: "vz-vmnet",
13+
Short: "Run vz-vmnet",
14+
Args: cobra.ExactArgs(0),
15+
RunE: newvzvmnetAction,
16+
ValidArgsFunction: newvzvmnetComplete,
17+
Hidden: true,
18+
}
19+
newCommand.Flags().Bool("enable-mach-service", false, "Enable Mach service")
20+
newCommand.Flags().String("mach-service", "", "Run as Mach service")
21+
_ = newCommand.Flags().MarkHidden("mach-service")
22+
return newCommand
23+
}
24+
25+
func newvzvmnetComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
26+
return bashCompleteInstanceNames(cmd)
27+
}

cmd/limactl/vz-vmnet_darwin.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"errors"
8+
"os"
9+
"os/signal"
10+
"syscall"
11+
12+
"github.com/coreos/go-semver/semver"
13+
"github.com/spf13/cobra"
14+
15+
"github.com/lima-vm/lima/v2/pkg/osutil"
16+
"github.com/lima-vm/lima/v2/pkg/vzvmnet"
17+
)
18+
19+
func newvzvmnetAction(cmd *cobra.Command, _ []string) error {
20+
macOSProductVersion, err := osutil.ProductVersion()
21+
if err != nil {
22+
return err
23+
}
24+
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
25+
return errors.New("vz-vmnet requires macOS 26 or higher to run")
26+
}
27+
28+
if !cmd.HasLocalFlags() {
29+
return cmd.Help()
30+
}
31+
32+
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
33+
defer cancel()
34+
35+
if machServiceName, _ := cmd.Flags().GetString("mach-service"); machServiceName != "" {
36+
return vzvmnet.RunMachService(ctx, machServiceName)
37+
} else if enableMachService, _ := cmd.Flags().GetBool("enable-mach-service"); enableMachService {
38+
return vzvmnet.RegisterMachService(ctx)
39+
}
40+
return vzvmnet.UnregisterMachService(ctx)
41+
}

cmd/limactl/vz-vmnet_nodarwin.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build !darwin
2+
3+
// SPDX-FileCopyrightText: Copyright The Lima Authors
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
package main
7+
8+
import (
9+
"errors"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func newvzvmnetAction(_ *cobra.Command, _ []string) error {
15+
return errors.New("vz-vmnet command is only supported on macOS")
16+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,4 @@ require (
148148
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
149149
)
150150

151-
replace github.com/Code-Hex/vz/v3 => github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123
151+
replace github.com/Code-Hex/vz/v3 => github.com/norio-nomura/vz/v3 v3.7.2-0.20251217001012-3b512d7782b0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
207207
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
208208
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
209209
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
210-
github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123 h1:3Xzg1W5gel17So2d2NSA+flx6yoyknx5nG9Pb6eZU6s=
211-
github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123/go.mod h1:+0IVfZY7N/7Vv5KpZWbEgTRK6jMg4s7DVM+op2hdyrs=
210+
github.com/norio-nomura/vz/v3 v3.7.2-0.20251217001012-3b512d7782b0 h1:RRTDK0pOm8Gl9g3eZjAoEFTwm7Sr5nsnxpQZoAK+2hQ=
211+
github.com/norio-nomura/vz/v3 v3.7.2-0.20251217001012-3b512d7782b0/go.mod h1:+0IVfZY7N/7Vv5KpZWbEgTRK6jMg4s7DVM+op2hdyrs=
212212
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
213213
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
214214
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=

pkg/autostart/autostart_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ func TestRenderTemplate(t *testing.T) {
6363
</array>
6464
<key>RunAtLoad</key>
6565
<true/>
66+
<key>ExitTimeOut</key>
67+
<integer>20</integer>
6668
<key>StandardErrorPath</key>
6769
<string>launchd.stderr.log</string>
6870
<key>StandardOutPath</key>

pkg/autostart/launchd/io.lima-vm.autostart.INSTANCE.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
</array>
1414
<key>RunAtLoad</key>
1515
<true/>
16+
<key>ExitTimeOut</key>
17+
<integer>20</integer>
1618
<key>StandardErrorPath</key>
1719
<string>launchd.stderr.log</string>
1820
<key>StandardOutPath</key>

pkg/driver/vz/vm_darwin.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
3838
"github.com/lima-vm/lima/v2/pkg/osutil"
3939
"github.com/lima-vm/lima/v2/pkg/store"
40+
"github.com/lima-vm/lima/v2/pkg/vzvmnet"
4041
)
4142

4243
// diskImageCachingMode is set to DiskImageCachingModeCached so as to avoid disk corruption on ARM:
@@ -363,7 +364,8 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
363364
}
364365

365366
for i, nw := range inst.Networks {
366-
if nw.VZNAT != nil && *nw.VZNAT {
367+
switch {
368+
case nw.VZNAT != nil && *nw.VZNAT:
367369
attachment, err := vz.NewNATNetworkDeviceAttachment()
368370
if err != nil {
369371
return err
@@ -373,30 +375,16 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
373375
return err
374376
}
375377
configurations = append(configurations, networkConfig)
376-
} else if nw.VZShared != nil && *nw.VZShared {
377-
config, err := vz.NewVmnetNetworkConfiguration(vz.SharedMode)
378-
if err != nil {
379-
return err
380-
}
381-
network, err := vz.NewVmnetNetwork(config)
382-
if err != nil {
383-
return err
384-
}
385-
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
386-
if err != nil {
387-
return err
388-
}
389-
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
378+
case nw.Vz != "":
379+
nwCfg, err := networks.LoadConfig()
390380
if err != nil {
391381
return err
392382
}
393-
configurations = append(configurations, networkConfig)
394-
} else if nw.VZHost != nil && *nw.VZHost {
395-
config, err := vz.NewVmnetNetworkConfiguration(vz.HostMode)
396-
if err != nil {
397-
return err
383+
vzCfg, ok := nwCfg.Vz[nw.Vz]
384+
if !ok {
385+
return fmt.Errorf("networks.yaml: 'vz: %s' is not defined", nw.Vz)
398386
}
399-
network, err := vz.NewVmnetNetwork(config)
387+
network, err := vzvmnet.RequestVmnetNetwork(ctx, nw.Vz, vzCfg)
400388
if err != nil {
401389
return err
402390
}
@@ -409,7 +397,7 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
409397
return err
410398
}
411399
configurations = append(configurations, networkConfig)
412-
} else if nw.Lima != "" {
400+
case nw.Lima != "":
413401
nwCfg, err := networks.LoadConfig()
414402
if err != nil {
415403
return err
@@ -461,7 +449,7 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
461449
configurations = append(configurations, networkConfig)
462450
}
463451
}
464-
} else if nw.Socket != "" {
452+
case nw.Socket != "":
465453
clientFile, err := DialQemu(ctx, nw.Socket)
466454
if err != nil {
467455
return err

pkg/driver/vz/vz_driver_darwin.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,7 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
280280

281281
for i, nw := range cfg.Networks {
282282
if unknown := reflectutil.UnknownNonEmptyFields(nw, "VZNAT",
283-
"VZShared",
284-
"VZHost",
283+
"Vz",
285284
"Lima",
286285
"Socket",
287286
"MACAddress",
@@ -290,9 +289,9 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
290289
); len(unknown) > 0 {
291290
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
292291
}
293-
if (nw.VZShared != nil && *nw.VZShared) || (nw.VZHost != nil && *nw.VZHost) {
292+
if nw.Vz != "" {
294293
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
295-
return fmt.Errorf("networks[%d]: VZShared and VZHost require macOS 26.0 or later", i)
294+
return fmt.Errorf("networks[%d]: 'vz: %s' require macOS 26.0 or later", i, nw.Vz)
296295
}
297296
}
298297
}
@@ -375,9 +374,10 @@ func (l *LimaVzDriver) Stop(_ context.Context) error {
375374
return err
376375
}
377376

378-
timeout := time.After(5 * time.Second)
377+
timeout := time.After(15 * time.Second)
379378
ticker := time.NewTicker(500 * time.Millisecond)
380379
for {
380+
logrus.Debug("Waiting for VZ to stop...")
381381
select {
382382
case <-timeout:
383383
return errors.New("vz timeout while waiting for stop status")

0 commit comments

Comments
 (0)