Skip to content

Commit 67f3b3a

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> # Conflicts: # go.sum # Conflicts: # go.sum
1 parent c7fed1f commit 67f3b3a

8 files changed

Lines changed: 106 additions & 2 deletions

File tree

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,5 @@ require (
118118
go.yaml.in/yaml/v3 v3.0.4 // indirect
119119
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
120120
)
121+
122+
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=
@@ -178,6 +176,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
178176
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
179177
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
180178
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
179+
github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123 h1:3Xzg1W5gel17So2d2NSA+flx6yoyknx5nG9Pb6eZU6s=
180+
github.com/norio-nomura/vz/v3 v3.7.2-0.20251122122159-6617c8faa123/go.mod h1:+0IVfZY7N/7Vv5KpZWbEgTRK6jMg4s7DVM+op2hdyrs=
181181
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
182182
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
183183
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
@@ -399,6 +399,42 @@ func attachNetwork(ctx context.Context, inst *limatype.Instance, vmConfig *vz.Vi
399399
return err
400400
}
401401
configurations = append(configurations, networkConfig)
402+
} else if nw.VZShared != nil && *nw.VZShared {
403+
config, err := vz.NewVmnetNetworkConfiguration(vz.SharedMode)
404+
if err != nil {
405+
return err
406+
}
407+
network, err := vz.NewVmnetNetwork(config)
408+
if err != nil {
409+
return err
410+
}
411+
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
412+
if err != nil {
413+
return err
414+
}
415+
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
416+
if err != nil {
417+
return err
418+
}
419+
configurations = append(configurations, networkConfig)
420+
} else if nw.VZHost != nil && *nw.VZHost {
421+
config, err := vz.NewVmnetNetworkConfiguration(vz.HostMode)
422+
if err != nil {
423+
return err
424+
}
425+
network, err := vz.NewVmnetNetwork(config)
426+
if err != nil {
427+
return err
428+
}
429+
attachment, err := vz.NewVmnetNetworkDeviceAttachment(network)
430+
if err != nil {
431+
return err
432+
}
433+
networkConfig, err := newVirtioNetworkDeviceConfiguration(attachment, nw.MACAddress)
434+
if err != nil {
435+
return err
436+
}
437+
configurations = append(configurations, networkConfig)
402438
} else if nw.Lima != "" {
403439
nwCfg, err := networks.LoadConfig()
404440
if err != nil {

pkg/driver/vz/vz_driver_darwin.go

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

292292
for i, nw := range cfg.Networks {
293293
if unknown := reflectutil.UnknownNonEmptyFields(nw, "VZNAT",
294+
"VZShared",
295+
"VZHost",
294296
"Lima",
295297
"Socket",
296298
"MACAddress",
@@ -299,6 +301,11 @@ func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
299301
); len(unknown) > 0 {
300302
logrus.Warnf("vmType %s: ignoring networks[%d]: %+v", *cfg.VMType, i, unknown)
301303
}
304+
if (nw.VZShared != nil && *nw.VZShared) || (nw.VZHost != nil && *nw.VZHost) {
305+
if macOSProductVersion.LessThan(*semver.New("26.0.0")) {
306+
return fmt.Errorf("networks[%d]: VZShared and VZHost require macOS 26.0 or later", i)
307+
}
308+
}
302309
}
303310

304311
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)