Skip to content

Commit 07eebec

Browse files
committed
feat: add VZVmnetNetworkDeviceAttachment support on macOS 26+
Based on `VMNET_SHARED_MODE`, and `VMNET_HOST_MODE` ```yaml networks: - vzShared: true - vzHost: true ``` But, to sharing network between multiple VMs, `VZVmnetNetworkDeviceAttachment` requires VMs are launched by same process. It depends on Code-Hex/vz#205 Signed-off-by: Norio Nomura <[email protected]>
1 parent 2df5112 commit 07eebec

File tree

8 files changed

+106
-2
lines changed

8 files changed

+106
-2
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,5 @@ require (
147147
sigs.k8s.io/randfill v1.0.0 // indirect
148148
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
149149
)
150+
151+
replace github.com/Code-Hex/vz/v3 => github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
44
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
55
github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1pFAqvnP87Zh0Nw=
66
github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY=
7-
github.com/Code-Hex/vz/v3 v3.7.1 h1:EN1yNiyrbPq+dl388nne2NySo8I94EnPppvqypA65XM=
8-
github.com/Code-Hex/vz/v3 v3.7.1/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8=
97
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
108
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
119
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
209207
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
210208
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
211209
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=
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/driver/vz/vm_darwin.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,42 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
368368
return err
369369
}
370370
configurations = append(configurations, networkConfig)
371+
} else if nw.VZShared != nil && *nw.VZShared {
372+
config, err := vz.NewVmnetNetworkConfiguration(vz.SharedMode)
373+
if err != nil {
374+
return err
375+
}
376+
network, err := vz.NewVmnetNetwork(config)
377+
if err != nil {
378+
return err
379+
}
380+
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
381+
if err != nil {
382+
return err
383+
}
384+
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
385+
if err != nil {
386+
return err
387+
}
388+
configurations = append(configurations, networkConfig)
389+
} else if nw.VZHost != nil && *nw.VZHost {
390+
config, err := vz.NewVmnetNetworkConfiguration(vz.HostMode)
391+
if err != nil {
392+
return err
393+
}
394+
network, err := vz.NewVmnetNetwork(config)
395+
if err != nil {
396+
return err
397+
}
398+
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
399+
if err != nil {
400+
return err
401+
}
402+
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
403+
if err != nil {
404+
return err
405+
}
406+
configurations = append(configurations, networkConfig)
371407
} else if nw.Lima != "" {
372408
nwCfg, err := networks.LoadConfig()
373409
if err != nil {

pkg/driver/vz/vz_driver_darwin.go

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

269269
for i, nw := range cfg.Networks {
270270
if unknown := reflectutil.UnknownNonEmptyFields(nw, "VZNAT",
271+
"VZShared",
272+
"VZHost",
271273
"Lima",
272274
"Socket",
273275
"MACAddress",
@@ -276,6 +278,11 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
276278
); len(unknown) > 0 {
277279
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
278280
}
281+
if (nw.VZShared != nil && *nw.VZShared) || (nw.VZHost != nil && *nw.VZHost) {
282+
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
283+
return fmt.Errorf("networks[%d]: VZShared and VZHost require macOS 26.0 or later", i)
284+
}
285+
}
279286
}
280287

281288
switch audioDevice := *cfg.Audio.Device; audioDevice {

pkg/limatmpl/embed.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,14 @@ func (tmpl *Template) combineNetworks() {
543543
tmpl.copyListEntryField(networks, dst, src, "vzNAT")
544544
dest.VZNAT = nw.VZNAT
545545
}
546+
if dest.VZShared == nil && nw.VZShared != nil {
547+
tmpl.copyListEntryField(networks, dst, src, "vzShared")
548+
dest.VZShared = nw.VZShared
549+
}
550+
if dest.VZHost == nil && nw.VZHost != nil {
551+
tmpl.copyListEntryField(networks, dst, src, "vzHost")
552+
dest.VZHost = nw.VZHost
553+
}
546554
if dest.Metric == nil && nw.Metric != nil {
547555
tmpl.copyListEntryField(networks, dst, src, "metric")
548556
dest.Metric = nw.Metric

pkg/limatype/lima_yaml.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,10 @@ type Network struct {
315315
Socket string `yaml:"socket,omitempty" json:"socket,omitempty"`
316316
// VZNAT uses VZNATNetworkDeviceAttachment. Needs VZ. No root privilege is required.
317317
VZNAT *bool `yaml:"vzNAT,omitempty" json:"vzNAT,omitempty"`
318+
// VZShared, and VZHost use VZVmnetNetworkDeviceAttachment. Needs VZ. No root privilege is required.
319+
// Requires macOS 26.0 or later.
320+
VZShared *bool `yaml:"vzShared,omitempty" json:"vzShared,omitempty"`
321+
VZHost *bool `yaml:"vzHost,omitempty" json:"vzHost,omitempty"`
318322

319323
MACAddress string `yaml:"macAddress,omitempty" json:"macAddress,omitempty"`
320324
Interface string `yaml:"interface,omitempty" json:"interface,omitempty"`

pkg/limayaml/validate.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,22 +466,66 @@ func validateNetwork(y *limatype.LimaYAML) error {
466466
if nw.VZNAT != nil && *nw.VZNAT {
467467
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzNAT` are mutually exclusive", field, field))
468468
}
469+
if nw.VZShared != nil && *nw.VZShared {
470+
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzShared` are mutually exclusive", field, field))
471+
}
472+
if nw.VZHost != nil && *nw.VZHost {
473+
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` and field `%s.vzHost` are mutually exclusive", field, field))
474+
}
469475
case nw.Socket != "":
470476
if nw.VZNAT != nil && *nw.VZNAT {
471477
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzNAT` are mutually exclusive", field, field))
472478
}
479+
if nw.VZShared != nil && *nw.VZShared {
480+
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzShared` are mutually exclusive", field, field))
481+
}
482+
if nw.VZHost != nil && *nw.VZHost {
483+
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` and field `%s.vzHost` are mutually exclusive", field, field))
484+
}
473485
if fi, err := os.Stat(nw.Socket); err != nil && !errors.Is(err, os.ErrNotExist) {
474486
errs = errors.Join(errs, err)
475487
} else if err == nil && fi.Mode()&os.ModeSocket == 0 {
476488
errs = errors.Join(errs, fmt.Errorf("field `%s.socket` %q points to a non-socket file", field, nw.Socket))
477489
}
478490
case nw.VZNAT != nil && *nw.VZNAT:
491+
if nw.VZShared != nil && *nw.VZShared {
492+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.vzShared` are mutually exclusive", field, field))
493+
}
494+
if nw.VZHost != nil && *nw.VZHost {
495+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.vzHost` are mutually exclusive", field, field))
496+
}
479497
if nw.Lima != "" {
480498
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.lima` are mutually exclusive", field, field))
481499
}
482500
if nw.Socket != "" {
483501
errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.socket` are mutually exclusive", field, field))
484502
}
503+
case nw.VZShared != nil && *nw.VZShared:
504+
if nw.VZNAT != nil && *nw.VZNAT {
505+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.vzNAT` are mutually exclusive", field, field))
506+
}
507+
if nw.VZHost != nil && *nw.VZHost {
508+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.vzHost` are mutually exclusive", field, field))
509+
}
510+
if nw.Lima != "" {
511+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.lima` are mutually exclusive", field, field))
512+
}
513+
if nw.Socket != "" {
514+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzShared` and field `%s.socket` are mutually exclusive", field, field))
515+
}
516+
case nw.VZHost != nil && *nw.VZHost:
517+
if nw.VZNAT != nil && *nw.VZNAT {
518+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.vzNAT` are mutually exclusive", field, field))
519+
}
520+
if nw.VZShared != nil && *nw.VZShared {
521+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.vzShared` are mutually exclusive", field, field))
522+
}
523+
if nw.Lima != "" {
524+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.lima` are mutually exclusive", field, field))
525+
}
526+
if nw.Socket != "" {
527+
errs = errors.Join(errs, fmt.Errorf("field `%s.vzHost` and field `%s.socket` are mutually exclusive", field, field))
528+
}
485529
default:
486530
errs = errors.Join(errs, fmt.Errorf("field `%s.lima` or field `%s.socket must be set", field, field))
487531
}

templates/default.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@ networks:
467467
# The "vzNAT" IP address is accessible from the host, but not from other guests.
468468
# Needs `vmType: vz`
469469
# - vzNAT: true
470+
# requires `vmType: vz` and macOS 26.0 or later.
471+
# - vzShared: true
472+
# - vzHost: true
470473

471474
# Port forwarding rules. Forwarding between ports 22 and ssh.localPort cannot be overridden.
472475
# Rules are checked sequentially until the first one matches.

0 commit comments

Comments
 (0)