Skip to content

[DONT REVIEW] microVM: let a platform restore install a new network policy on a NIC template - #4767

Merged
Pedro Henrique Penna (ppenna) merged 2 commits into
feature/microvm/devfrom
copilot/microvm-platform-network-policy
Oct 27, 2026
Merged

Pedro Henrique Penna (ppenna) merged 2 commits into
feature/microvm/devfrom
copilot/microvm-platform-network-policy

Conversation

@ppenna

@ppenna Pedro Henrique Penna (ppenna) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Lets a microVM platform restore install a network policy of its own in place of the complete deny-all policy that its NIC template was captured with, before any vCPU runs, including private-network ingress, explicit host-loopback access, and host-loopback forwards, for microsoft/nvx#536. This is a new feature of the platform tier rather than a fixup.

Based on feature/microvm/dev. This branch is two commits directly on e68871cc6, the head of feature/microvm/dev and the revision that NVX's dev pins.

A NIC template is a platform NIC captured with a complete deny-all (3e3a454c5)

Restore required the policy that a snapshot recorded, because its endpoint may have held flows, listeners, and forwards under it, and its guest may have learned a DNS server and announced its listeners under it. A NIC captured with a complete deny-all policy is different: the policy denies every guest frame, host loopback, and inbound access, and keeps no proxy endpoint, so nothing in the snapshot depends on it beyond the NIC's identity.

microvm_network_template_policy binds that policy to a NIC's identity, as a capture with --network-egress deny and --host-loopback deny, without rules, a proxy, or ingress, records it. validate_microvm_network_replacement checks that a snapshot's network record is exactly that policy's for its own identity, in the encoding that the policy has today, version 3 without IPv6 or version 4 with it, and returns it for the restore contract. Versions 0 to 2 encode neither the host-loopback action nor the proxy endpoint, so no snapshot that records one is a template. A replacement keeps the captured identity, so it may name IPv6 destinations only when that identity has IPv6.

The manifest format does not change: a template is recognized from what every networked snapshot already records, including those captured before this change.

--restore-network-policy (57d0a18b3)

With the option, a platform restore installs the network policy of its own options in place of the template's deny-all:

  • Identity. The NIC keeps its captured identity: the restore binds the policy to it, as it binds a matching policy today, and still rejects --net.
  • Validation. The same code as at a cold boot validates the policy, so the checks of its proxy, endpoints, rules, ingress, and forwards are a cold boot's. Only the IPv6 rule above is new.
  • Before any vCPU runs. The device installs the policy when it is built, and the endpoint binds its forwards when it is resolved, so the restored guest never runs under the template's policy or a partial one, and a forward that cannot bind its port fails the restore. A restore that wants no connectivity passes the template's deny-all.
  • Contract. The restore contract still compares the whole network record exactly. It expects the template's deny-all, which the record must hold, rather than the installed policy, so the option admits only a platform snapshot, which prepare_restore checks, with a NIC, which effective_microvm_network checks, whose record is the complete deny-all of its identity.
  • Live attachments. The snapshot refusals of --network-ingress allow, --host-loopback allow, and --host-loopback-forward admit such a restore, and keep refusing captures and every other restore. Every other restore, of any tier, keeps the same-policy rule, and so does a template's restore without the option.
  • Refusals. The option is refused on a cold boot, explicitly, because clap's requirement of --restore-snapshot does not hold once --kernel is present, and on a standard machine.

The guest keeps its captured command line, which has no virtnet_dns= or virtnet_ingress= token under a deny-all policy, so it learns its DNS server and whether to announce its listeners from its host after the restore (microsoft/nvx#838 and microsoft/nvx#850).

Tests

  • openvmm_helpers::snapshot::microvm: only_a_complete_deny_all_network_is_a_template (templates with and without IPv6; replacements that open every direction; deny-all egress with host loopback, a proxy, ingress, host-loopback ingress, rules that deny every destination, and allow-all egress; the legacy encodings, which the same-policy rule still accepts; a noncanonical identity) and a_template_replacement_names_ipv6_only_with_an_ipv6_identity.
  • microvm::network: a_complete_deny_all_capture_records_a_template; a_template_restore_installs_a_new_policy_and_keeps_the_identity, which restores one template with eight policies (the template's deny-all, allow-all egress, IPv4 and IPv6 rules, exact endpoints, a proxy, explicit host loopback with TCP and UDP forwards, and private-network ingress with and without host loopback), each installing the requested policy, keeping the identity, and expecting the template's contract, and checks that a template's restore without the option keeps its policy; only_a_template_admits_a_new_policy; and a_template_restore_binds_its_policy_to_the_captured_identity (a proxy or endpoint that does not suit the identity, a new address, IPv6 rules on a template that predates IPv6).
  • microvm::restore: only_a_platform_restore_replaces_the_network_policy, for every tier.
  • cli_args::microvm: test_microvm_restore_network_policy_requires_a_microvm_restore, and the snapshot cases of test_microvm_host_loopback_allow_publishes_guest_listeners and test_microvm_private_network_ingress_publishes_guest_listeners, which now cover the restore with the option, including the early check that runs before the snapshot is opened.

Validation

  • On this branch, on Windows: cargo clippy --all-targets and cargo doc --no-deps for openvmm_helpers and openvmm_entry; cargo nextest run --profile agent for both (335 passed); and cargo xtask fmt --fix (clean).
  • Bare metal, with openvmm: let a platform restore install a new network policy on a NIC template nvx#931, which pins this branch and adds the platform-network-policy microVM scenario, on Linux/MSHV, Linux/KVM, and Windows/WHP hosts: the scenario, which restores one NIC template with the template's deny-all, egress rules, explicit host loopback with a forward and private-network ingress, and a proxy, passes in 16.2, 13.6, and 37.6 s, and NVX's default microVM suite passes 40/40 on each. Every clone keeps the template's MAC, IPv4, and IPv6 addresses and reaches exactly what its policy admits, and the refused restores fail before the guest runs, the busy forward port with failed to create socket for port forward. With the unpatched e68871cc6 on KVM, the template's capture succeeds and the first clone's restore fails at unexpected argument '--restore-network-policy'.
  • Not run locally: the VMM tests. The management RPC refuses snapshots with a NIC, so no VMM test reaches these paths; NVX's scenarios cover them through the CLI.

Notes

A platform snapshot is a template that each clone restores, and a clone
of a template with a NIC needs its own network policy. Restore requires
the policy that the snapshot recorded, because the endpoint may have held
flows, listeners, and forwards under it, and the guest may have learned a
DNS server and announced its listeners under it.

A NIC captured with a complete deny-all policy is different. The policy
denies every guest frame, host loopback, and inbound access, and keeps no
proxy endpoint, so the endpoint held no flows, listeners, or forwards,
and the guest learned no DNS server and announces no listeners. Nothing
in such a snapshot depends on the policy beyond the NIC's identity.

Add microvm_network_template_policy, which binds that policy to a NIC's
identity, as a capture with --network-egress deny and --host-loopback
deny, without rules, a proxy, or ingress, records it. Add
validate_microvm_network_replacement, which checks that a snapshot's
network records exactly that policy for its own identity, and returns
the policy for the restore contract, which keeps it whatever policy
replaces it. The saved record must equal the template's in the encoding
that the policy has today: version 3 without IPv6, or version 4 with it.
Versions 0 to 2 encode neither the host-loopback action nor the proxy
endpoint, so no snapshot that records one is a template, whatever its
digest. A replacement keeps the captured identity, so it may name IPv6
destinations only when that identity has IPv6, rather than install rules
that could never match.

The manifest format does not change: a template is recognized from what
every networked snapshot already records, including those captured
before this change.

Unit tests cover templates with and without IPv6, replacements that open
every direction, the near misses (deny-all egress with host loopback, a
proxy, ingress, host-loopback ingress, rules that deny every destination,
and allow-all egress), the legacy encodings, a noncanonical identity, and
IPv6 rules on templates with and without IPv6.

See microsoft/nvx#536.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A platform restore takes its NIC's identity from the snapshot and
requires the captured policy, and every snapshot rejects private-network
ingress, explicit host-loopback access, and host-loopback forwards,
whose listeners and bound ports are live host attachments. One template
therefore served one network policy, and none with inbound access.

Add --restore-network-policy, with which a platform restore installs the
network policy of its own options in place of the complete deny-all that
its NIC template was captured with. The NIC keeps its captured identity:
the restore binds the policy to it, as it binds a matching policy today,
and still rejects --net. The same code as at a cold boot validates the
policy, so the checks of its proxy, endpoints, rules, ingress, and
forwards are a cold boot's, and only the IPv6 rule of
validate_microvm_network_replacement is new. The device installs the
policy when it is built, and the endpoint binds its forwards when it is
resolved, both before any vCPU runs, so the restored guest never runs
under the template's policy or a partial one, and a forward that cannot
bind its port fails the restore. A restore that wants no connectivity
passes the template's deny-all.

The restore contract still compares the whole network record exactly:
it expects the template's deny-all, which the record must hold, rather
than the installed policy. The option therefore admits only a platform
snapshot, which prepare_restore checks, with a NIC, which
effective_microvm_network checks, whose record is the complete deny-all
of its identity. Every other restore, of any tier, keeps the same-policy
rule, and so does a template's restore without the option. With the
option, a restore may also publish the guest's listeners and forward
host ports: the snapshot refusals of --network-ingress allow,
--host-loopback allow, and --host-loopback-forward admit such a restore,
and keep refusing captures and every other restore. The option is
refused on a cold boot, where clap's requirement of --restore-snapshot
does not hold once --kernel is present, and on a standard machine.

The guest keeps its captured command line, which has no virtnet_dns= or
virtnet_ingress= token under a deny-all policy, so it learns its DNS
server and whether to announce its listeners from its host after the
restore.

Unit tests cover the CLI rules, the restore's tier check, and restores of
one template with eight policies: the template's deny-all, allow-all
egress, IPv4 and IPv6 rules, exact endpoints, a proxy, explicit host
loopback with TCP and UDP forwards, and private-network ingress with and
without host loopback. Each installs the requested policy, keeps the
identity, and expects the template's contract. They also cover the
refusals of a snapshot that is not a template, of one without a NIC, of
a proxy or endpoint that does not suit the captured identity, of a new
address, and of IPv6 rules on a template that predates IPv6, and that a
template's restore without the option keeps its policy. The CLI
reference, the snapshot guide, and the snapshot format describe the
option.

See microsoft/nvx#536.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

@ppenna
Pedro Henrique Penna (ppenna) merged commit 57d0a18 into feature/microvm/dev Oct 27, 2026
169 of 176 checks passed
Pedro Henrique Penna (ppenna) added a commit to microsoft/nvx that referenced this pull request Oct 27, 2026
Record the two commits that the previous commit pins as positions 348
and 349 of the microVM chain, and move the NVX pin to d2338ea22.

Position 348 pins amd.genoa.v2, a second revision of the Genoa CPU
profile that position 282 pins, and makes CPUID 0x80000008 EDX a limit
of the derivation policy of positions 159-269. Position 349 records the
nested-WHP Genoa host that amd.genoa.v2 serves as a test fixture. They
add to the CPU profile work that no PR maps yet rather than fix mapped
work, so they join positions 158-333, 341-343, 346, and 347 as
unassigned. Add them to the chain and to the table of the commits after
the mapped tip, and extend the scope. The pin is now 192 commits past
the mapped tip.

Positions 346 and 347 are no longer only a proposal:
microsoft/openvmm#4767 merged them, so position 347, 57d0a18b3, is the
head of feature/microvm/dev. Say so in the scope, and name
microsoft/openvmm#4769 as the proposal of positions 348 and 349.

Part of #928

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fork-updater Bot pushed a commit to sheeeng/microsoft-nvx that referenced this pull request Oct 28, 2026
Pin microsoft/openvmm#4767 at 57d0a18b3b542dce3f0787e60f3c48296b40d30e,
two commits on e68871cc6, the previous pin, which is the head of
feature/microvm/dev.

The first recognizes a NIC template: a platform snapshot whose NIC was
captured with the complete deny-all policy, --network-egress deny and
--host-loopback deny without egress rules, a proxy, or ingress. Its
guest learned no DNS server and announces no listeners, and its
endpoint held no flows, listeners, or forwards, so nothing in the
snapshot depends on its policy beyond the NIC's identity. A snapshot's
network record identifies a template only in digest encodings from
version 3 on, since earlier ones record neither the host-loopback
action nor the proxy endpoint. The manifest format does not change.

The second adds --restore-network-policy, with which a platform restore
installs the network policy of its own options in place of the
template's deny-all, including private-network ingress, explicit host
loopback, and host-loopback forwards, which snapshots refused. The same
code as at a cold boot validates the policy, which is bound to the
NIC's captured identity, and the device installs it, with its forwards
bound, before any vCPU runs; a forward that cannot bind its port fails
the restore. The restore contract still compares the whole network
record, against the template's deny-all. Captures, other tiers, and
restores without the option keep the same-policy rule.

Part of microsoft#536

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fork-updater Bot pushed a commit to sheeeng/microsoft-nvx that referenced this pull request Oct 28, 2026
Record the two commits that the previous commit pins as positions 346
and 347 of the microVM chain, and move the NVX pin to 57d0a18b3.

Position 346 recognizes a NIC template, a platform snapshot whose NIC
was captured with the complete deny-all policy, from the policy digest
that every networked snapshot records. Position 347 adds
--restore-network-policy, which installs a restore's own policy on such
a template. They add behavior rather than fix a mapped commit, and they
change the snapshot network policy and the forward refusal that PR21's
position 129 added and the snapshot refusals of the unassigned
positions 306 and 307, so they stay unassigned, like positions 341-343.
Note in the table's remarks that assigning them means revisiting that
work. The pin is now 190 commits past the mapped tip.

Positions 344 and 345 are no longer only a proposal:
microsoft/openvmm#4762 merged them, so position 345, e68871cc6, is the
head of feature/microvm/dev. Say so in the scope, and name
microsoft/openvmm#4767 as the proposal of positions 346 and 347.

Part of microsoft#536

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant