diff --git a/go.mod b/go.mod index a7d8b6dadc9..7c222e7ffd4 100644 --- a/go.mod +++ b/go.mod @@ -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.20251122122159-6617c8faa123 diff --git a/go.sum b/go.sum index 25f787e757f..7bc13aea712 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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.20251122122159-6617c8faa123 h1:3Xzg1W5gel17So2d2NSA+flx6yoyknx5nG9Pb6eZU6s= +github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123/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= diff --git a/pkg/driver/vz/vm_darwin.go b/pkg/driver/vz/vm_darwin.go index c0014ad5c5a..97074209a8f 100644 --- a/pkg/driver/vz/vm_darwin.go +++ b/pkg/driver/vz/vm_darwin.go @@ -368,6 +368,42 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi return err } configurations = append(configurations, networkConfig) + } else if nw.VZShared != nil && *nw.VZShared { + config, err := vz.NewVmnetNetworkConfiguration(vz.SharedMode) + 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.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 { diff --git a/pkg/driver/vz/vz_driver_darwin.go b/pkg/driver/vz/vz_driver_darwin.go index f80bb878147..d34522de022 100644 --- a/pkg/driver/vz/vz_driver_darwin.go +++ b/pkg/driver/vz/vz_driver_darwin.go @@ -268,6 +268,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", @@ -276,6 +278,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 { diff --git a/pkg/limatmpl/embed.go b/pkg/limatmpl/embed.go index 4797369580a..3f494b44427 100644 --- a/pkg/limatmpl/embed.go +++ b/pkg/limatmpl/embed.go @@ -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 diff --git a/pkg/limatype/lima_yaml.go b/pkg/limatype/lima_yaml.go index 838c9e85ba5..957925ca591 100644 --- a/pkg/limatype/lima_yaml.go +++ b/pkg/limatype/lima_yaml.go @@ -315,6 +315,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"` diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index c03ca5e7d6b..849435f7560 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -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)) } diff --git a/templates/default.yaml b/templates/default.yaml index bbc37a9b95e..041813e8c7b 100644 --- a/templates/default.yaml +++ b/templates/default.yaml @@ -467,6 +467,9 @@ networks: # The "vzNAT" IP address is accessible from the host, but not from other guests. # Needs `vmType: vz` # - vzNAT: true +# requires `vmType: vz` and macOS 26.0 or later. +# - vzShared: true +# - vzHost: true # Port forwarding rules. Forwarding between ports 22 and ssh.localPort cannot be overridden. # Rules are checked sequentially until the first one matches.