Skip to content

Commit 8125323

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 <norio.nomura@gmail.com>
1 parent 9dbf572 commit 8125323

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
@@ -373,6 +373,42 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
373373
return err
374374
}
375375
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)
390+
if err != nil {
391+
return err
392+
}
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
398+
}
399+
network, err := vz.NewVmnetNetwork(config)
400+
if err != nil {
401+
return err
402+
}
403+
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
404+
if err != nil {
405+
return err
406+
}
407+
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
408+
if err != nil {
409+
return err
410+
}
411+
configurations = append(configurations, networkConfig)
376412
} else if nw.Lima != "" {
377413
nwCfg, err := networks.LoadConfig()
378414
if err != nil {

pkg/driver/vz/vz_driver_darwin.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ 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",
283285
"Lima",
284286
"Socket",
285287
"MACAddress",
@@ -288,6 +290,11 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
288290
); len(unknown) > 0 {
289291
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
290292
}
293+
if (nw.VZShared != nil && *nw.VZShared) || (nw.VZHost != nil && *nw.VZHost) {
294+
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
295+
return fmt.Errorf("networks[%d]: VZShared and VZHost require macOS 26.0 or later", i)
296+
}
297+
}
291298
}
292299

293300
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
@@ -317,6 +317,10 @@ type Network struct {
317317
Socket string `yaml:"socket,omitempty" json:"socket,omitempty"`
318318
// VZNAT uses VZNATNetworkDeviceAttachment. Needs VZ. No root privilege is required.
319319
VZNAT *bool `yaml:"vzNAT,omitempty" json:"vzNAT,omitempty"`
320+
// VZShared, and VZHost use VZVmnetNetworkDeviceAttachment. Needs VZ. No root privilege is required.
321+
// Requires macOS 26.0 or later.
322+
VZShared *bool `yaml:"vzShared,omitempty" json:"vzShared,omitempty"`
323+
VZHost *bool `yaml:"vzHost,omitempty" json:"vzHost,omitempty"`
320324

321325
MACAddress string `yaml:"macAddress,omitempty" json:"macAddress,omitempty"`
322326
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
@@ -472,6 +472,9 @@ networks:
472472
# The "vzNAT" IP address is accessible from the host, but not from other guests.
473473
# Needs `vmType: vz`
474474
# - vzNAT: true
475+
# requires `vmType: vz` and macOS 26.0 or later.
476+
# - vzShared: true
477+
# - vzHost: true
475478

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

0 commit comments

Comments
 (0)