Skip to content

Add FileHandleNetworkInterface; make Interface.ipv4Address optional - #935

Open
petersen wants to merge 6 commits into
apple:mainfrom
petersen:network-fd-without-bridge
Open

petersen wants to merge 6 commits into
apple:mainfrom
petersen:network-fd-without-bridge

Conversation

@petersen

Copy link
Copy Markdown

This is a source-breaking change to Interface: ipv4Address becomes CIDRv4?. In-tree conformers are updated here; out-of-tree ones need a one-line change. The reason is an interface whose address is not known when the container
is configured, because something other than our allocator assigns it.

FileHandleNetworkInterface is the first such interface. It attaches the guest NIC to a FileHandle via VZFileHandleNetworkDeviceAttachment, so a host process supplies the network — a simulated network, a VPN, or a bridge onto a host interface. The handle must wrap a connected AF_UNIX datagram socket carrying one Ethernet frame per datagram, and must also be bound to a local path of its own: macOS does not auto-assign an address to a unix datagram socket, so an unbound one transmits and never receives.

mtu is stored rather than inherited so the guest link can match a provider whose MTU is below 1500; otherwise handshakes succeed and bulk transfers stall. The host-side attachment MTU stays at the framework default, which rejects anything lower.

Two follow-on fixes: only an interface that has an address claims the default route (previously an address-less eth0 consumed the slot), and an IPv6 address with no IPv4 address is now refused, since InterfaceAddress requires v4 and the
link would otherwise come up silently unconfigured.

The first two commits are carried from #759 with authorship intact; this drops BridgedNetworkInterface and the vminitd DNS fallback and adds MTU support, docs, and tests. Happy to fold it back into #759 instead if that is easier.

@petersen
petersen force-pushed the network-fd-without-bridge branch from 2536a4b to 7e863c7 Compare September 21, 2026 20:39
torarnv and others added 6 commits September 21, 2026 17:26
Makes Interface.ipv4Address optional (CIDRv4?) to accommodate interfaces
whose address is not known at configuration time, such as an attachment
where the address is assigned by an upstream DHCP server or by a service
outside of our allocation pool.

Updates all existing conformers (NATInterface, NATNetworkInterface,
VmnetNetwork.Interface, TAPInterface) and guards the static address and
route setup in VirtualMachineAgent+Interface behind an ipv4Address
nil-check. The cctl call sites that render an interface address into
/etc/hosts now skip the entry when no address is set.

Co-authored-by: Curd Becker <me@curd-becker.de>
…e that has one

setupInterface installs no default route for an interface without a static
IPv4 address, but the loop marked the route as set after the first interface
regardless. An address-less interface at eth0 therefore consumed the slot and
a later interface with a gateway never got a default route.

Only mark the default route as set when the interface actually has an address
to install it from.
FileHandleNetworkInterface uses VZFileHandleNetworkDeviceAttachment to
attach the container to a file handle where another service can provide an
arbitrary network. This might be an entirely simulated network, a virtual
network like a VPN, e.g. using Wireguard, but it could also be a bridged
physical network from the host where the service will then take over the
responsibility of bridging the traffic between the container and host.

The guest address is optional: pass ipv4Address and ipv4Gateway to configure
it statically, or leave them nil when the address is assigned out of band,
for instance by a DHCP server reachable through the file handle.

Co-authored-by: Tor Arne Vestbø <torarnv@gmail.com>
Covers the FileHandleNetworkInterface surface: that it defaults to no static
address and picks up the Interface protocol defaults, that a caller-supplied
address, gateway, and mac round-trip through Interface, and that device()
attaches the file handle and applies the mac.

Also covers an Interface conformer with no IPv4 address, which the protocol
now permits.
Make the interface usable for a network provider whose link MTU is below the
Ethernet default. `mtu` becomes a stored property rather than being inherited
from the `Interface` extension, so the guest link can be brought up at the
provider's MTU; without it the guest comes up at 1500 and full-size frames are
dropped, which presents as working handshakes and stalled bulk transfers.

The host-side attachment MTU deliberately stays at the framework default,
because `VZFileHandleNetworkDeviceAttachment` rejects values below 1500. That
property is a maximum, so smaller frames pass through it fine.

Drop `@available(macOS 26, *)`: `VZFileHandleNetworkDeviceAttachment` is
macOS 11+ and `maximumTransmissionUnit` is macOS 13+, so nothing here needs 26.
`NATNetworkInterface` keeps its annotation because it depends on
`vmnet_network_ref`.

Document the socket contract on the initializer. The handle must wrap a
connected `AF_UNIX` datagram socket that is also bound to a local path of its
own: platforms without automatic binding for unix datagram sockets, macOS
included, leave an unbound socket with no address for the peer to reply to, so
the guest transmits and never receives.

Correct the addressing comment. It suggested an address-less interface would be
configured out of band by a DHCP server reachable through the handle, but
`setupInterface` only performs static configuration and `vminitd` ships no DHCP
client, so such an interface comes up with no address unless the guest image
supplies a client of its own.
Making `ipv4Address` optional opened a state the guest setup path cannot
express. `InterfaceAddress` requires an IPv4 address, so when a conformer
supplies only an IPv6 address, `addressAdd` is skipped and the v6 address is
never assigned; the default-route guard then returns early and no v6 default
route is installed either. The link comes up unaddressed and the caller is left
to debug a silently dead interface.

Supporting IPv6-only interfaces means making `InterfaceAddress.ipv4Address`
optional and changing the `ipAddrAdd` RPC, which is out of scope here. Refuse
the combination instead, so the limitation surfaces as an error naming it.

Add a recording `VirtualMachineAgent` to cover the setup paths the optional
address introduced: an address-less interface brings the link up without an
address and claims no default route even when asked, a static interface still
gets both, and the interface MTU reaches `up`.
@petersen
petersen force-pushed the network-fd-without-bridge branch from 7e863c7 to 93e236d Compare September 21, 2026 22:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants