@@ -2,6 +2,9 @@ package vz_test
22
33import (
44 "log"
5+ "net"
6+ "net/netip"
7+ "slices"
58 "testing"
69
710 "github.com/Code-Hex/vz/v3"
@@ -11,17 +14,31 @@ func TestVmnetSharedModeAllowsCommunicationBetweenMultipleVMs(t *testing.T) {
1114 if vz .Available (26.0 ) {
1215 t .Skip ("VmnetSharedMode is supported from macOS 26" )
1316 }
17+
18+ // Create VmnetNetwork instance from configuration
1419 config , err := vz .NewVmnetNetworkConfiguration (vz .SharedMode )
1520 if err != nil {
1621 t .Fatal (err )
1722 }
18- network , err := vz .NewVmnetNetwork (config )
23+ network1 , err := vz .NewVmnetNetwork (config )
24+ if err != nil {
25+ t .Fatal (err )
26+ }
27+ macaddress1 := randomMACAddress (t )
28+
29+ // Create another VmnetNetwork instance from serialization of the first one
30+ serialization , err := network1 .CopySerialization ()
31+ if err != nil {
32+ t .Fatal (err )
33+ }
34+ network2 , err := vz .NewVmnetNetworkWithSerialization (serialization )
1935 if err != nil {
2036 t .Fatal (err )
2137 }
38+ macaddress2 := randomMACAddress (t )
2239
23- container1 := newVirtualizationMachine (t , ConfigureNetworkDeviceWithNetwork ( network ))
24- container2 := newVirtualizationMachine (t , ConfigureNetworkDeviceWithNetwork ( network ))
40+ container1 := newVirtualizationMachine (t , configureNetworkDevice ( network1 , macaddress1 ))
41+ container2 := newVirtualizationMachine (t , configureNetworkDevice ( network2 , macaddress2 ))
2542 t .Cleanup (func () {
2643 if err := container1 .Shutdown (); err != nil {
2744 log .Println (err )
@@ -32,12 +49,12 @@ func TestVmnetSharedModeAllowsCommunicationBetweenMultipleVMs(t *testing.T) {
3249 })
3350
3451 // Log network information
35- ipv4Subnet , err := network .IPv4Subnet ()
52+ ipv4Subnet , err := network1 .IPv4Subnet ()
3653 if err != nil {
3754 t .Fatal (err )
3855 }
3956 t .Logf ("Vmnet network IPv4 subnet: %s" , ipv4Subnet .String ())
40- prefix , err := network .IPv6Prefix ()
57+ prefix , err := network1 .IPv6Prefix ()
4158 if err != nil {
4259 t .Fatal (err )
4360 }
@@ -52,7 +69,68 @@ func TestVmnetSharedModeAllowsCommunicationBetweenMultipleVMs(t *testing.T) {
5269 container2 .exec (t , "ping " + container1IPv4 )
5370}
5471
55- func ConfigureNetworkDeviceWithNetwork (network * vz.VmnetNetwork ) func (cfg * vz.VirtualMachineConfiguration ) error {
72+ func TestVmnetSharedModeWithConfiguringIPv4 (t * testing.T ) {
73+ if vz .Available (26.0 ) {
74+ t .Skip ("VmnetSharedMode is supported from macOS 26" )
75+ }
76+ // Create VmnetNetwork instance from configuration
77+ config , err := vz .NewVmnetNetworkConfiguration (vz .SharedMode )
78+ if err != nil {
79+ t .Fatal (err )
80+ }
81+ // Configure IPv4 subnet
82+ ipv4Subnet := detectFreeIPv4Subnet (t , netip .MustParsePrefix ("192.168.5.0/24" ))
83+ if err := config .SetIPv4Subnet (ipv4Subnet ); err != nil {
84+ t .Fatal (err )
85+ }
86+ // Configure DHCP reservation
87+ macaddress := randomMACAddress (t )
88+ ipv4 := "192.168.5.15"
89+ config .AddDhcpReservation (macaddress , netip .MustParseAddr (ipv4 ))
90+
91+ // Create VmnetNetwork instance
92+ network , err := vz .NewVmnetNetwork (config )
93+ if err != nil {
94+ t .Fatal (err )
95+ }
96+
97+ // Create VirtualizationMachine instance
98+ container := newVirtualizationMachine (t , configureNetworkDevice (network , macaddress ))
99+ t .Cleanup (func () {
100+ if err := container .Shutdown (); err != nil {
101+ log .Println (err )
102+ }
103+ })
104+
105+ // Log network information
106+ ipv4SubnetConfigured , err := network .IPv4Subnet ()
107+ if err != nil {
108+ t .Fatal (err )
109+ }
110+ t .Logf ("Vmnet network IPv4 subnet: %s" , ipv4SubnetConfigured .String ())
111+
112+ // Verify the configured subnet
113+ // Compare with masked value to ignore host bits since Vmnet prefers to use first address as network address.
114+ if ipv4Subnet != ipv4SubnetConfigured .Masked () {
115+ t .Fatalf ("expected IPv4 subnet %s, but got %s" , ipv4Subnet .String (), ipv4SubnetConfigured .Masked ().String ())
116+ }
117+
118+ // Log IPv6 prefix
119+ prefix , err := network .IPv6Prefix ()
120+ if err != nil {
121+ t .Fatal (err )
122+ }
123+ t .Logf ("Vmnet network IPv6 prefix: %s" , prefix .String ())
124+
125+ // Detect IP address and verify DHCP reservation
126+ containerIPv4 := container .DetectIPv4 (t , "eth0" )
127+ t .Logf ("Container IPv4: %s" , containerIPv4 )
128+ if ipv4 != containerIPv4 {
129+ t .Fatalf ("expected IPv4 %s, but got %s" , ipv4 , containerIPv4 )
130+ }
131+ }
132+
133+ func configureNetworkDevice (network * vz.VmnetNetwork , macAddress * vz.MACAddress ) func (cfg * vz.VirtualMachineConfiguration ) error {
56134 return func (cfg * vz.VirtualMachineConfiguration ) error {
57135 var configurations []* vz.VirtioNetworkDeviceConfiguration
58136 attachment , err := vz .NewVmnetNetworkDeviceAttachment (network )
@@ -63,8 +141,55 @@ func ConfigureNetworkDeviceWithNetwork(network *vz.VmnetNetwork) func(cfg *vz.Vi
63141 if err != nil {
64142 return err
65143 }
144+ config .SetMACAddress (macAddress )
66145 configurations = append (configurations , config )
67146 cfg .SetNetworkDevicesVirtualMachineConfiguration (configurations )
68147 return nil
69148 }
70149}
150+
151+ func detectFreeIPv4Subnet (t * testing.T , prefer netip.Prefix ) netip.Prefix {
152+ targetPrefix := netip .MustParsePrefix ("192.168.0.0/16" )
153+ hostNetIfs , err := net .Interfaces ()
154+ if err != nil {
155+ t .Fatal (err )
156+ }
157+ candidates := make ([]netip.Prefix , len (hostNetIfs ))
158+ for _ , hostNetIf := range hostNetIfs {
159+ hostNetAddrs , err := hostNetIf .Addrs ()
160+ if err != nil {
161+ continue
162+ }
163+ for _ , hostNetAddr := range hostNetAddrs {
164+ netIPNet , ok := hostNetAddr .(* net.IPNet )
165+ if ! ok {
166+ continue
167+ }
168+ hostPrefix := netip .MustParsePrefix (netIPNet .String ())
169+ if targetPrefix .Overlaps (hostPrefix ) {
170+ candidates = append (candidates , hostPrefix )
171+ }
172+ }
173+ }
174+ slices .SortFunc (candidates , func (l , r netip.Prefix ) int {
175+ if l .Addr ().Less (r .Addr ()) {
176+ return - 1
177+ }
178+ return 1
179+ })
180+ for _ , candidate := range candidates {
181+ if prefer .Addr () != candidate .Addr () {
182+ return prefer
183+ }
184+ }
185+ t .Fatal ("no free IPv4 subnet found" )
186+ return netip.Prefix {}
187+ }
188+
189+ func randomMACAddress (t * testing.T ) * vz.MACAddress {
190+ mac , err := vz .NewRandomLocallyAdministeredMACAddress ()
191+ if err != nil {
192+ t .Fatal (err )
193+ }
194+ return mac
195+ }
0 commit comments