Uh oh!
There was an error while loading. Please reload this page.
VMM: networking mode management - #756
Conversation
501848b to
de1bbb1Comparekvinwang
commented
Jul 8, 2026
A note on the The whole Context worth flagging: the built-in port forwarding — and Given that, I'd suggest not extending this into a per-MAC Happy to help sketch the removal path if we decide to go that way. |
Per PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), and per-NIC ip population in VmInfo::interfaces - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
Per PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), per-NIC ip population in VmInfo::interfaces, and the now-dead VmInfo.guest_ip field that only fed that population - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
c447e16 to
76b965aComparePer PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), per-NIC ip population in VmInfo::interfaces, and the now-dead VmInfo.guest_ip field that only fed that population - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
76b965a to
c728dcdComparePer PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), per-NIC ip population in VmInfo::interfaces, and the now-dead VmInfo.guest_ip field that only fed that population - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
37e88da to
5693786ComparePer PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), per-NIC ip population in VmInfo::interfaces, and the now-dead VmInfo.guest_ip field that only fed that population - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
5693786 to
0f812fbCompareSingle-NIC bridge/user networking could not express mixed topologies (e.g. one bridge NIC for external traffic + one user-mode NIC for isolated management). Extend the model to support multiple NICs per VM. - Manifest field: networking (singular, Optional) -> networks (Vec) with backward-compatible deserialization of the legacy field - Proto: add repeated NetworkingConfig in VmConfiguration/UpdateVmRequest, NetworkInterfaceStatus in VmInfo, and NetworkingCapabilities in GetMetaResponse - QEMU: generate one -netdev/-device pair per entry with indexed net IDs and per-NIC deterministic MACs (SHA256 of vm_id:index) - DHCP: match leases against all NICs, persist per-MAC guest IP map - Port forwarding: select the first bridge NIC with forward_service_enabled - Validation: reject bridge mode when the host interface does not exist, reject custom mode via RPC (manifest-only) - Measurement: networking config excluded from VM measurement to allow topology changes without re-attestation
- Replace hand-written Manifest Deserialize impl with derive + Manifest::from_json() for legacy "networking" field migration - Simplify resolve_networking to clone global config as base then selectively override non-empty per-VM fields - Extract effective_networks() on VmState and VmInfo to eliminate four duplicated runtime_networks fallback blocks - Remove redundant forward_service_enabled pre-assignment in resolve_requested_networks (now inherited from global config clone)
Drop if_name, lowerdev, and pci_bdf — all three were always set to None in the only construction site. Add them back when a backend actually populates them.
Each virtio-net-pci device changes the guest ACPI/DSDT layout and thus RTMR0. Before this change dstack-mr hardcoded exactly one NIC, so a CVM booted with the new multi-network support (repeated `networks`) would produce an RTMR0 the verifier could never reconstruct, and attestation would fail closed. Dropping ACPI from the measurement instead would let an untrusted host silently attach un-attested devices under an unchanged quote, so the NIC count must be measured. Mirror the existing `num_gpus` plumbing: add `num_nics` to `dstack_types::VmConfig` and `dstack_mr::Machine`, emit one `-netdev/-device virtio-net-pci` pair per NIC in the ACPI generator, and thread the resolved interface count from the VMM through to the verifier. Backward compatibility: - `num_nics` defaults to 1 and is omitted from the serialized VmConfig when equal to 1, so existing configs, their measurement cache keys, and RTMR0 values are byte-for-byte unchanged (verified against the previous hardcoded layout). - legacy configs without the field deserialize to 1. Verified: RTMR0(num_nics=1) == RTMR0(previous hardcoded layout); RTMR0(num_nics=2) differs.
Per PR #756 review: the guest_ip machinery exists only to serve VMM's built-in port-forward, a subsystem already slated for deprecation. It learns the guest address out-of-band from an external DHCP server via an unauthenticated ReportDhcpLease RPC, so the stored value is stale-prone and empty for slirp NICs. Extending it into a per-MAC map doubled down on that subsystem: a second on-disk file with the same staleness/trust caveats plus a wider public API surface. Roll back the extension rather than build on top of it: - proto: remove NetworkInterfaceStatus.ip, renumber trailing fields - app.rs: drop guest_ips map from state and DHCP-lease path; port forwarding uses the single guest_ip again - qemu.rs: remove guest_ips field, guest-ips.json persistence (guest_ips_path/guest_ips/set_guest_ip_for_mac), per-NIC ip population in VmInfo::interfaces, and the now-dead VmInfo.guest_ip field that only fed that population - ui: drop the per-interface IP column and its grid track Multi-NIC status reporting can be revisited once the fate of port-forward is settled; if it stays, the DHCP-report path needs authentication and a release/cleanup path before persisting and exposing guest IPs more widely.
Rebasing onto master pulled in the new make_vm_config(.., requirements: Option<&Requirements>) parameter added by the launch-token-requirement work (#763). The multi-NIC bridge/user config-equality test still called the 5-arg form; pass None for the new argument so the crate builds against the rebased signature.
Reject configurations with multiple forwarding-enabled bridges so DHCP lease updates cannot redirect the VM-wide forwarding target between interfaces.\n\nPersist runtime network snapshots atomically before launching QEMU, publish the snapshot to in-memory state before DHCP can arrive, and clear both copies when process startup fails. Add regression coverage for forwarding validation and atomic snapshot replacement.
0f812fb to
1e329bfComparekvinwang
commented
Jul 16, 2026
Rebased onto current The former
I also kept bridge/filesystem validation in Validation:
|
Uh oh!
There was an error while loading. Please reload this page.
Three things that are one thing: a port mapping had no way to say which NIC it uses, the NIC it silently got was often the wrong one, and on a bridge it got nothing at all. `port_map` is implemented as QEMU `hostfwd=` entries, and those need a user-mode netdev: let hostfwd_index = self.prepared.networks.iter() .position(|n| n.mode == NetworkingMode::User); Multi-NIC (#756) made that a choice, and it has been made silently ever since. A bridge NIC for external traffic beside a user-mode NIC for management -- the topology multi-NIC was added for -- puts every published port on the *management* NIC: the traffic reaches the guest, but over slirp, bypassing whatever the bridge NIC's nwfilter was there to enforce and hiding the client's address behind the slirp gateway. A second user-mode NIC can never publish anything, because `position` returns the first. And with no user-mode NIC at all there is no `hostfwd_index`, so every mapping is dropped with no warning while `GetInfo` keeps reporting the ports as though they worked. So say it. `PortMapping.nic_index` names the NIC a mapping enters through, `@<nic>` on the CLI, unset resolving to the first user-mode NIC and failing that the first bridge NIC. Existing VMs keep their behaviour exactly wherever a user-mode NIC exists; where none does, ports now have somewhere to go instead of nowhere. One mapping resolves to exactly one NIC, and that NIC's backend decides the mechanism: `hostfwd=` for user mode, netd for a bridge. Both sides read the same resolution, so no host port can be claimed twice. The VMM cannot forward a bridge NIC's ports itself -- it runs without CAP_NET_ADMIN by design, and the userspace forwarder that used to cover this was removed in e2e607f because proxying on the host hands back the per-packet cost that leaving user mode was meant to escape. netd is privileged and is the only component that sees every VMM instance on a host, so it is also the only one that can arbitrate a host port between them. `ingress` on `prepare_bridge` carries the requirement there. Every field of it is caller-named, which is what `bridge`, `mac` and `queues` already get and the opposite of `filtered`. The line is whether netd can check what it is handed: an nwfilter name cannot be checked for whether it filters anything, while a host port is a closed space a node policy can be stated over. Naming is not deciding. The netd here builds interfaces and does not forward ports. It says so by leaving `ingress` out of its response -- the reading `queues` already has, where absent separates "this netd does not do that" from "nothing was asked for". The VMM warns rather than failing, because a VM deployed before this has been running with its ports dropped and refusing to launch it now would turn a silent misconfiguration into an outage. Also add `remove_all`. Teardown by identity only reaches the NIC indices its caller still has a record of, and that record is written after the interface exists: a VMM killed in between leaves a TAP nothing on disk points at, a lost record reads as "nothing to remove", and a manifest that lost a NIC leaves an index the list no longer reaches. netd derives every name a VM could occupy instead, bounded by what an identity may say. The VMM sweeps before preparing a launch as well as on stop, so a launch is self-healing regardless of what the record says, and an unreachable netd no longer fails a stop. And `workdir` on both prepare operations: untrusted, never read for a decision, there so an operator reading netd's log can get from an opaque TAP name back to the VM that asked for it.
Summary
Adds multi-NIC networking support to the VMM and UI:
networkingmanifest compatibility;Rebased and relocated onto the unified monorepo layout from PR #770.
Validation
cargo test -p dstack-vmm -p dstack-mr -p dstack-types -p dstack-verifiernpm ci && npm run buildindstack/vmm/uireuse lintprek run --all-files