Skip to content

nvx-rs: check the guest agent's replies through shared methods - #615

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/session-replies
Oct 20, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/session-replies

Conversation

@ppenna

Copy link
Copy Markdown
Contributor

Summary

ControlSession (nvx::host::session), the host side of a sandbox's control session, checked the guest agent's replies with copies of the same code. ping and stop differed only in their request and reply kinds and their message, features, map_host_paths, and next_exec_event each refused a reply to another request with the same check and message, and map_host_paths and next_exec_event truncated the error category of a refusal to 64 characters the same way. This change keeps each of those rules once, in three private helpers, so that the requests validate their replies alike by construction and a new request can reuse them. The CLI's own session, nvx::host::cli::session, which keeps the messages of nvx.py, is untouched.

Changes

All in nvx-rs/src/host/session.rs:

  • ControlSession::acknowledged(kind, reply, what, deadline) sends a request of kind with a fresh request ID and no payload, and fails with the guest agent did not acknowledge WHAT unless the next application frame is an empty reply with status 0 to that request. The sites that now share it:
    • ping: APP_PING, acknowledged by APP_READY, as readiness;
    • stop: APP_STOP, acknowledged by APP_STOPPED, as stop.
  • ControlSession::read_reply(request_id, deadline) reads the next application frame, fails with the guest agent returned a mismatched request ID unless it answers request_id, and returns its kind, status, and payload. The sites that now share it:
    • features;
    • map_host_paths, for each of its requests;
    • next_exec_event.
  • error_category(payload) returns the error category of an APP_ERROR payload, truncated to 64 characters. The sites that now share it:
    • map_host_paths, in its message for a refusal other than mapping-failed;
    • next_exec_event, in ExecEvent::Rejected.
  • Two unit tests pin what no test did:
    • pings_and_stops_need_an_empty_acknowledgement: the kinds and empty payloads of the requests that ping and stop send, and that each refuses, with its message, a reply of another kind, to another request, with another status, or with a payload;
    • responses_to_another_request_fail_closed: the message with which features, map_host_paths, and next_exec_event refuse a reply to another request; the existing tests checked only its variant, for the first two.

Behavior

Nothing observable changes:

  • Every message is the same text. acknowledged formats the guest agent did not acknowledge readiness and ... stop from what it acknowledges, and the other messages moved verbatim.
  • Each method makes the same checks, in the same order, on the same frames: ping and stop still test the kind, request ID, status, and payload in one condition with one message, and features, map_host_paths, and next_exec_event still refuse a reply to another request before they look at its kind. Every error is still a SessionError::Protocol, so ErrorKinds are unchanged.
  • read_app still reads and sequences every frame, and each request still draws a fresh request ID.
  • No public item, protocol byte, golden fixture, guest binary, or CLI output changes: the change is private to the host feature, and the CLI's session keeps its own checks.

Existing tests drive every changed path: the session's unit tests; the managed agent's loopback test (guest::managed_agent::loopback), which runs ping, features, map_host_paths, workloads and their events, and stop against the real agent in-process on Linux; Instance, whose start, exec, and stop the fake-OpenVMM tests of nvx-rs (tests/fake_openvmm.rs) and aci_edge_sandboxes (tests/openvmm_fake.rs) run; and, on each host below, test-aci-edge-sandboxes, whose openvmm_e2e tests run the same lifecycle on a real hypervisor, and the managed-lifecycle scenario of test-microvm.

Follow-ups

The survey turned these up, and this change leaves them alone. I found no suspected bug.

  • host/cli/session.rs repeats the same reply checks in ping, stop, features, and exec, with the managed guest ... messages and Exit errors of nvx.py's ControlSession. Helpers parameterized by message and error could serve both sessions, but they map failures differently, so that wants its own change.
  • In host/session.rs, the tests' Scripted and Responder transports still have identical read methods.
  • host/platform/linux.rs (start_time_from_stat) and host/cli/sandbox/identity.rs (linux_start_time) both parse /proc/PID/stat, but they differ observably: the CLI's port of Python requires 20 fields and an all-digit start time and returns Python's messages, while the platform's accepts what u64::from_str accepts. They want a decision before they share code.
  • These follow-ups of earlier runs remain:
    • in host/sandbox/mount.rs, MountAccess, MountOwner, and MountKind repeat from_name with the same message form, and sandbox/mount/tests.rs and sandbox/tests.rs define the same policy test helper;
    • process::tests::a_process_runs_until_its_last_thread_exits and cli::sandbox::identity::tests::a_vm_runs_until_its_last_thread_exits repeat the fixture of a Python process whose main thread exits first;
    • filesystem.rs, network/mod.rs, and sandbox/options/tests.rs each define the same option-values test helper;
    • Instance::runtime and Instance::launch in instance.rs read a record and refuse another STATE_FORMAT in two shapes;
    • the managed-agent test fixtures, run_file and run_network in guest/device_io.rs and the device_io helper of tests/guest_tools.rs, the time tools' watcher-violation sites and port-window lock failures, the two MAPS decoders of protocol/maps.rs, and the guest-program follow-ups that nvx-rs: make the guest failure reporters return their CommandError #614 lists.

Validation

  • BASE: 830536ed210e0d8bef0ca9166c13fb9636acb614, origin/dev when the run started: two performance-baseline updates on top of the merge of nvx-rs: parse fake OpenVMM --mount-child values with one function #613.
  • TREE: 3f97d134bbb0a76f2ea3796c37dcfa47516ae3dd, the tree of this commit. Every host applied the patch to BASE and checked that git write-tree equals TREE.
  • Release on every host: v0.1.0-dev.6c4171aa3166, from the merge of nvx-rs: parse fake OpenVMM --mount-child values with one function #613, the nearest first-parent ancestor of BASE that had packages. Its OpenVMM, d0bdbed8f6e3b86271277dc22658109e2e633b2d, is the pinned revision.
  • Each host built nvx-rs from TREE with Cargo and recorded its provenance, and pre-built the openvmm_e2e and managed_lifecycle test binaries, so that no Cargo build overlapped its hypervisor tests.
  • Guest artifacts were rebuilt from TREE on prometheus30, whose user namespaces let Alpine's apk run its scripts: build-guest-binaries --output; test-guest-binaries --binaries, where 34 tests passed; and the Alpine build-initramfs with NVX_GUEST_BINARIES. initramfs.cpio.gz (SHA-256 54364e7301c11f7048f15c67f10c679f75d5ab5fc3cc1abd9025df6cef4b297e), its packages manifest (9b3bbafc3926715bbd074c398a295691c964db6ca9e4dc7104908cc714b7dd8e), and its provenance (21581b7203f62c333bdd2b580403b179049b81c4db10a6896a2975dfa2bb09b7) went through this machine to the other two hosts, where the SHA-256 digests matched.
  • Local checks, on Windows: cargo fmt --check; Clippy with -D warnings for all targets and features and for the library without default features; cargo test with all features and with the defaults; and rustdoc with -D warnings. This machine also ran the steps for which the hosts lack a toolchain or target: the Rust 1.89 MSRV check of both crates on Windows, and the aarch64-apple-darwin check of both crates, which CI runs on Linux.
Host profile Backend Host type check-nvx-rs check-aci-edge-sandboxes Guest binaries and initramfs test-aci-edge-sandboxes test-microvm (default suite)
prometheus32 kvm baremetal Linux steps pass, including MSRV 1.89, the bundled-release test, and check-guest-binaries; the macOS check ran locally Linux steps pass, including MSRV 1.89 and agent bundling; the macOS check ran locally — pass, 18 of 18 pass, 43 scenarios
prometheus30 mshv baremetal — — build-guest-binaries, test-guest-binaries, and build-initramfs pass pass, 18 of 18 pass, 43 scenarios
prometheus28 whp baremetal Windows steps pass, including the bundled-release test; MSRV ran locally Windows steps pass, including agent bundling; MSRV ran locally — pass, 18 of 18 pass, 43 scenarios

directories_on_another_volume_are_mapped passes by skipping on hosts without a second volume, as on all three.

Left to CI: the test-microvm scenario that runs only when named (smp-lapic), the Ubuntu and Azure Linux guests, the OpenVMM suites, guest determinism, benchmarks, and check-quality and validate-nvx, which this change doesn't affect: it changes no Python, shell, or Markdown file, and adds, moves, or deletes none.

ControlSession, the host side of a sandbox's control session, checked
the guest agent's replies with copies of the same code. ping and stop
differed only in their request and reply kinds and their message: each
sent an empty request, read the next application frame, and failed
unless it was an empty reply of the expected kind, with status 0, to
that request. features, map_host_paths, and next_exec_event each read
a frame and refused one that answered another request with the same
message, and map_host_paths and next_exec_event truncated the error
category of a refusal to 64 characters the same way.

Three private helpers now hold these rules once: acknowledged, the
exchange of ping and stop; read_reply, the read that refuses a reply
to another request; and error_category, the truncation. Each check
lives in one place, so the requests cannot drift apart in how they
validate their replies, and a new request can reuse them.

Behavior is unchanged. Every message is the same, including "the
guest agent did not acknowledge readiness" and "... stop", which
acknowledged now formats from what it acknowledges; each method makes
the same checks in the same order on the same frames; and the public
API is untouched. The existing session tests, the managed agent's
loopback tests, the fake-OpenVMM tests of nvx-rs and
aci_edge_sandboxes, and the hypervisor suites drive every changed
path. Two new unit tests pin what no test did: the requests that ping
and stop send and each refusal of their acknowledgement, and the
mismatched-request-ID message of features, map_host_paths, and
next_exec_event.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 20, 2026 01:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The private refactor preserves validation order and error messages, with no blocking issues identified.

No open issues 🎉

What changed in this PR

Consolidates guest-agent reply validation in the host’s ControlSession, preserving existing behavior and leaving the CLI session unchanged.

Changes:

  • Extracts shared acknowledgement, request-ID validation, and error-category helpers.
  • Adds tests for invalid acknowledgements and mismatched reply IDs.
File Description
nvx-rs/​src/​host/​session.rs Centralizes reply checks and adds regression tests.

Review effort: Balanced

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.

2 participants