Skip to content
Draft
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
1 change: 1 addition & 0 deletions cmd/limactl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func newApp() *cobra.Command {
newNetworkCommand(),
newCloneCommand(),
newRenameCommand(),
newVzVmnetSharedCommand(),
)
addPluginCommands(rootCmd)

Expand Down
27 changes: 27 additions & 0 deletions cmd/limactl/vz-vmnet-shared.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"github.com/spf13/cobra"
)

func newVzVmnetSharedCommand() *cobra.Command {
newCommand := &cobra.Command{
Use: "vz-vmnet-shared",
Short: "Run vz-vmnet-shared",
Args: cobra.ExactArgs(0),
RunE: newVzVmnetSharedAction,
ValidArgsFunction: newVzVmnetSharedComplete,
Hidden: true,
}
newCommand.Flags().Bool("enable-mach-service", false, "Enable Mach service")
newCommand.Flags().String("mach-service", "", "Run as Mach service")
_ = newCommand.Flags().MarkHidden("mach-service")
return newCommand
}

func newVzVmnetSharedComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return bashCompleteInstanceNames(cmd)
}
41 changes: 41 additions & 0 deletions cmd/limactl/vz-vmnet-shared_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"errors"
"os"
"os/signal"
"syscall"

"github.com/coreos/go-semver/semver"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/osutil"
"github.com/lima-vm/lima/v2/pkg/vzvmnetshared"
)

func newVzVmnetSharedAction(cmd *cobra.Command, _ []string) error {
macOSProductVersion, err := osutil.ProductVersion()
if err != nil {
return err
}
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
return errors.New("vz-vmnet-shared requires macOS 26 or higher to run")
}

if !cmd.HasLocalFlags() {
return cmd.Help()
}

ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
defer cancel()

if machServiceName, _ := cmd.Flags().GetString("mach-service"); machServiceName != "" {
return vzvmnetshared.RunMachService(ctx, machServiceName)
} else if enableMachService, _ := cmd.Flags().GetBool("enable-mach-service"); enableMachService {
return vzvmnetshared.RegisterMachService(ctx)
}
return vzvmnetshared.UnregisterMachService(ctx)
}
16 changes: 16 additions & 0 deletions cmd/limactl/vz-vmnet-shared_nodarwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !darwin

// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"errors"

"github.com/spf13/cobra"
)

func newVzVmnetSharedAction(_ *cobra.Command, _ []string) error {
return errors.New("vz-vmnet-shared command is only supported on macOS")
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,5 @@ require (
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
)

replace github.com/Code-Hex/vz/v3 => github.com/norio-nomura/vz/v3 v3.7.2-0.20251203072611-007c2a5b352c
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1pFAqvnP87Zh0Nw=
github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY=
github.com/Code-Hex/vz/v3 v3.7.1 h1:EN1yNiyrbPq+dl388nne2NySo8I94EnPppvqypA65XM=
github.com/Code-Hex/vz/v3 v3.7.1/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
Expand Down Expand Up @@ -209,6 +207,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/norio-nomura/vz/v3 v3.7.2-0.20251203072611-007c2a5b352c h1:0QGVXjk6/KA2G5yLQZGKZf9GB8caGCMS5H3sy0zPoH0=
github.com/norio-nomura/vz/v3 v3.7.2-0.20251203072611-007c2a5b352c/go.mod h1:+0IVfZY7N/7Vv5KpZWbEgTRK6jMg4s7DVM+op2hdyrs=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
Expand Down
35 changes: 35 additions & 0 deletions pkg/driver/vz/vm_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"fmt"
"io/fs"
"net"
"net/netip"
"os"
"path/filepath"
"runtime"
Expand All @@ -37,6 +38,7 @@
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
"github.com/lima-vm/lima/v2/pkg/osutil"
"github.com/lima-vm/lima/v2/pkg/store"
"github.com/lima-vm/lima/v2/pkg/vzvmnetshared"
)

// diskImageCachingMode is set to DiskImageCachingModeCached so as to avoid disk corruption on ARM:
Expand Down Expand Up @@ -363,7 +365,7 @@
}

for i, nw := range inst.Networks {
if nw.VZNAT != nil && *nw.VZNAT {

Check failure on line 368 in pkg/driver/vz/vm_darwin.go

View workflow job for this annotation

GitHub Actions / Lint Go (macos-26)

ifElseChain: rewrite if-else to switch statement (gocritic)
attachment, err := vz.NewNATNetworkDeviceAttachment()
if err != nil {
return err
Expand All @@ -373,6 +375,39 @@
return err
}
configurations = append(configurations, networkConfig)
} else if nw.VZShared != nil && *nw.VZShared {
subnet := netip.MustParsePrefix("192.168.107.0/24")
network, err := vzvmnetshared.RequestSharedVmnetNetwork(ctx, subnet)
if err != nil {
return err
}
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
if err != nil {
return err
}
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
if err != nil {
return err
}
configurations = append(configurations, networkConfig)
} else if nw.VZHost != nil && *nw.VZHost {
config, err := vz.NewVmnetNetworkConfiguration(vz.HostMode)
if err != nil {
return err
}
network, err := vz.NewVmnetNetwork(config)
if err != nil {
return err
}
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
if err != nil {
return err
}
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
if err != nil {
return err
}
configurations = append(configurations, networkConfig)
} else if nw.Lima != "" {
nwCfg, err := networks.LoadConfig()
if err != nil {
Expand Down
7 changes: 7 additions & 0 deletions pkg/driver/vz/vz_driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {

for i, nw := range cfg.Networks {
if unknown := reflectutil.UnknownNonEmptyFields(nw, "VZNAT",
"VZShared",
"VZHost",
"Lima",
"Socket",
"MACAddress",
Expand All @@ -288,6 +290,11 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
); len(unknown) > 0 {
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
}
if (nw.VZShared != nil && *nw.VZShared) || (nw.VZHost != nil && *nw.VZHost) {
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
return fmt.Errorf("networks[%d]: VZShared and VZHost require macOS 26.0 or later", i)
}
}
}

switch audioDevice := *cfg.Audio.Device; audioDevice {
Expand Down
8 changes: 8 additions & 0 deletions pkg/limatmpl/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,14 @@ func (tmpl *Template) combineNetworks() {
tmpl.copyListEntryField(networks, dst, src, "vzNAT")
dest.VZNAT = nw.VZNAT
}
if dest.VZShared == nil && nw.VZShared != nil {
tmpl.copyListEntryField(networks, dst, src, "vzShared")
dest.VZShared = nw.VZShared
}
if dest.VZHost == nil && nw.VZHost != nil {
tmpl.copyListEntryField(networks, dst, src, "vzHost")
dest.VZHost = nw.VZHost
}
if dest.Metric == nil && nw.Metric != nil {
tmpl.copyListEntryField(networks, dst, src, "metric")
dest.Metric = nw.Metric
Expand Down
4 changes: 4 additions & 0 deletions pkg/limatype/lima_yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ type Network struct {
Socket string `yaml:"socket,omitempty" json:"socket,omitempty"`
// VZNAT uses VZNATNetworkDeviceAttachment. Needs VZ. No root privilege is required.
VZNAT *bool `yaml:"vzNAT,omitempty" json:"vzNAT,omitempty"`
// VZShared, and VZHost use VZVmnetNetworkDeviceAttachment. Needs VZ. No root privilege is required.
// Requires macOS 26.0 or later.
VZShared *bool `yaml:"vzShared,omitempty" json:"vzShared,omitempty"`
VZHost *bool `yaml:"vzHost,omitempty" json:"vzHost,omitempty"`

MACAddress string `yaml:"macAddress,omitempty" json:"macAddress,omitempty"`
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`
Expand Down
44 changes: 44 additions & 0 deletions pkg/limayaml/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,22 +466,66 @@ func validateNetwork(y *limatype.LimaYAML) error {
if nw.VZNAT != nil && *nw.VZNAT {
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzNAT` are mutually exclusive", field, field))
}
if nw.VZShared != nil && *nw.VZShared {
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzShared` are mutually exclusive", field, field))
}
if nw.VZHost != nil && *nw.VZHost {
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzHost` are mutually exclusive", field, field))
}
case nw.Socket != "":
if nw.VZNAT != nil && *nw.VZNAT {
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzNAT` are mutually exclusive", field, field))
}
if nw.VZShared != nil && *nw.VZShared {
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzShared` are mutually exclusive", field, field))
}
if nw.VZHost != nil && *nw.VZHost {
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzHost` are mutually exclusive", field, field))
}
if fi, err := os.Stat(nw.Socket); err != nil && !errors.Is(err, os.ErrNotExist) {
errs = errors.Join(errs, err)
} else if err == nil && fi.Mode()&os.ModeSocket == 0 {
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` %q points to a non-socket file", field, nw.Socket))
}
case nw.VZNAT != nil && *nw.VZNAT:
if nw.VZShared != nil && *nw.VZShared {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.vzShared` are mutually exclusive", field, field))
}
if nw.VZHost != nil && *nw.VZHost {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.vzHost` are mutually exclusive", field, field))
}
if nw.Lima != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.lima` are mutually exclusive", field, field))
}
if nw.Socket != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.socket` are mutually exclusive", field, field))
}
case nw.VZShared != nil && *nw.VZShared:
if nw.VZNAT != nil && *nw.VZNAT {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.vzNAT` are mutually exclusive", field, field))
}
if nw.VZHost != nil && *nw.VZHost {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.vzHost` are mutually exclusive", field, field))
}
if nw.Lima != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.lima` are mutually exclusive", field, field))
}
if nw.Socket != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.socket` are mutually exclusive", field, field))
}
case nw.VZHost != nil && *nw.VZHost:
if nw.VZNAT != nil && *nw.VZNAT {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.vzNAT` are mutually exclusive", field, field))
}
if nw.VZShared != nil && *nw.VZShared {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.vzShared` are mutually exclusive", field, field))
}
if nw.Lima != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.lima` are mutually exclusive", field, field))
}
if nw.Socket != "" {
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.socket` are mutually exclusive", field, field))
}
default:
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` or field `%s.socket must be set", field, field))
}
Expand Down
29 changes: 29 additions & 0 deletions pkg/vzvmnetshared/io.lima-vm.vz.vmnet.shared.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{{.Label}}</string>
<key>ProgramArguments</key>
<array>
{{- range $arg := .ProgramArguments}}
<string>{{$arg}}</string>
{{- end}}
</array>
<key>RunAtLoad</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ .WorkingDirectory }}</string>
<key>StandardErrorPath</key>
<string>{{ .WorkingDirectory }}/stderr.log</string>
<key>StandardOutPath</key>
<string>{{ .WorkingDirectory }}/stdout.log</string>
<key>MachServices</key>
<dict>
{{- range $service := .MachServices}}
<key>{{$service}}</key>
<true/>
{{- end}}
</dict>
</dict>
</plist>
Loading
Loading