I'm using the macOS 26 vmnet network API with Virtualization framework (vmnet_network_create + VZVmnetNetworkDeviceAttachment) and giving each VM a stable address with vmnet_network_configuration_add_dhcp_reservation, plus creation-time rules from vmnet_network_configuration_add_port_forwarding_rule.
Both work on the network's first run. But if the network's last interface leaves while I still hold the vmnet_network_ref, then the next interface start brings the network back without its reservations or forwarding rules. The subnet and gateway are kept.
How it shows up in practice: a VM that is the only guest on its network gets restarted from inside the guest. Virtualization framework removes and re-adds the VM's vmnet interface about 1 s apart, with no delegate callback. That's enough to stop and restart the network, and the guest comes back on a dynamic lease with its forwarded ports refused.
Minimal repro, no VM needed:
- Create a configuration in VMNET_SHARED_MODE with set_ipv4_subnet, one add_dhcp_reservation and one add_port_forwarding_rule, then call vmnet_network_create.
- vmnet_interface_start_with_network. InternetSharing logs port forwarding enabled …, and /etc/bootptab contains the reservation.
- vmnet_stop_interface. InternetSharing logs no internal interface left, stopping network → reset to idle, and /etc/bootptab is emptied.
- vmnet_interface_start_with_network again on the same, still-retained ref. InternetSharing logs has been started with no port forwarding enabled line, and /etc/bootptab is never rewritten. The forwarded port is refused.
- Control: release the ref, call vmnet_network_create again from the same configuration, and start an interface. The reservation and the rule are both back.
Reproduced on macOS 27.0 (26A428) and (with a minimal vmnet only test, no actual VM) on macOS 26.6.2 (25G83).
This seems to contradict the documentation for vmnet_network_create: "The lifetime of such reservation is the same as that of vmnet_network_ref."
A related issue: vmnet_interface_add_ip_port_forwarding_rule and its remove and get counterparts return VMNET_FAILURE synchronously on an interface started with vmnet_interface_start_with_network. The same calls work on a vmnet_start_interface interface. The add_port_forwarding_rule documentation points to those calls for managing rules after start, so there's no way to put a rule back after the restart.
The same "torn down on last detach while the ref lives on" lifecycle is also reported in apple/container#2051 (https://github.com/apple/container/issues/2051), with a different symptom (two networks sharing a bridge, so tearing one down breaks the other's egress). It proposes the same mitigation: an anchor interface held with vmnet_interface_start_with_network.
Filed as:
- FB24895271: reservations and rules discarded when the last interface is removed
- FB24895264: the runtime forwarding calls fail on network-attached interfaces
- FB24895282: suggestion for a network state-change callback and a DHCP lease query by MAC
Questions:
- Is losing reservations and rules when the network goes idle intended?
- Is holding an app-owned interface on the network (via vmnet_interface_start_with_network) so it never reaches zero interfaces a supported workaround, or does it risk other side effects?
First up, thanks for all those bug reports FB24895271, FB24895264, FB24895282). Much appreciated!
Second, regarding your workaround, I asked around internally and it seems reasonable enough. We’re not aware of any specific negative consequences for having an interface instantiated but doing nothing.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"