Skip to content

feat(qemu): expose a configurable QMP control socket - #841

Open
Anamika1608 wants to merge 8 commits into
urunc-dev:mainfrom
Anamika1608:firecracker-socket-path
Open

feat(qemu): expose a configurable QMP control socket#841
Anamika1608 wants to merge 8 commits into
urunc-dev:mainfrom
Anamika1608:firecracker-socket-path

Conversation

@Anamika1608

@Anamika1608 Anamika1608 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Description

Expose QEMU's QMP control socket so the runtime can talk to the monitor after the guest starts (for graceful shutdown, and later snapshots). QEMU still boots from its CLI arguments as before; this only adds the socket for post-boot control.

  • qemu.go launches with -qmp unix:<socket_path>,server,nowait alongside the existing -monitor null; server,nowait lets QEMU boot without waiting for a client to connect.
  • The socket path is configurable via a socket_path monitor field; when a custom path is set, urunc creates its parent directory inside the monitor rootfs. Default is a per-container path under /tmp.

This PR is self-contained against main: it carries a small copy of the shared socket infrastructure (socket_path field, ResolveSocketPath, DefaultSocketDir, UsesControlSocket, the socket-directory creation). The same infra also appears in the Firecracker PR (#809); whichever merges first lands it, and this PR then rebases to drop the duplicate. This PR does not touch firecracker.go.

Related issues

How was this tested?

  • go build ./..., go test ./pkg/... ./internal/..., gofmt, and cspell pass; TestQemuBuildExecCmd covers the -qmp string for both a configured socket_path and the default path (Ubuntu 24.04 aarch64 VM, KVM, QEMU 10.2.1).
  • Live, through real containerd + nerdctl: chttp-qemu-linux-aarch64 boots, the guest serves HTTP 200, qemu-system-aarch64 launches with -qmp unix:/tmp/qemu.sock,server,nowait, and the QMP socket negotiates capabilities and answers query-status with "status": "running".

LLM usage

Checklist

  • I have read the contribution guide.
  • The linter passes locally (make lint).
  • The e2e tests of at least one tool pass locally (make test_ctr, make test_nerdctl, make test_docker, make test_crictl).
  • If LLMs were used: I have read the llm policy.

@netlify

netlify Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploy Preview for urunc ready!

Name Link
🔨 Latest commit 2beebe4
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6a6841c166b2930009e635ca
😎 Deploy Preview https://deploy-preview-841--urunc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Anamika1608
Anamika1608 force-pushed the firecracker-socket-path branch from 74479f7 to 7bc678b Compare July 21, 2026 13:44
@Anamika1608

Copy link
Copy Markdown
Contributor Author

hi @cmainas,

a couple of things on this PR i wanted to check with you.

1. socket_path and the /tmp constraint

the socket_path field works, but there is a constraint: the monitor creates the socket inside its own pivoted rootfs (after changeRoot), and that rootfs only has a standard set of directories. so a custom socket_path has to be placed under /tmp, which is always present there. i verified this end to end: with socket_path = "/tmp/firecracker.sock" firecracker boots from the config file, the guest serves HTTP 200, and the socket is reachable and reports state Running. an arbitrary directory like /run/urunc fails, since it does not exist inside the monitor rootfs (bind returns "no such file or directory").

is keeping socket_path scoped to /tmp fine for now, or do you want it fully configurable?

2. testing the other monitors

i wired the socket for qemu (a QMP socket) and cloud hypervisor (its REST api socket) too, both honoring socket_path.

for qemu i was able to test it fully end to end: i found harbor.nbfc.io/nubificus/urunc/chttp-qemu-linux-aarch64 and it worked perfectly on my aarch64 setup. qemu boots with -qmp unix:<socket_path>,server,nowait, the guest returns HTTP 200, and the QMP socket negotiates and query-status returns running.

for cloud hypervisor i could only cover it with unit tests (asserting the launch args), because i could not find an aarch64 cloud hypervisor image in the registry. i probed a few names (chttp/busybox/nginx variants of *-cloud-hypervisor-linux-aarch64) and they all 404.

is there an aarch64 cloud hypervisor image i can use to test it end to end, or is x86 only expected there for now?

@cmainas

cmainas commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Hello @Anamika1608 ,

is keeping socket_path scoped to /tmp fine for now, or do you want it fully configurable?

The default location can be /tmp and we can have a constant to specify that. However, if a user specifies a specific path, we need to ensure that this path exists inside the monitor rootfs. Except of course if it is an invalid location (e.g. another file already exists)

is there an aarch64 cloud hypervisor image i can use to test it end to end, or is x86 only expected there for now?

I have created the harbor.nbfc.io/nubificus/urunc/ubuntu-cloud-hypervisor-linux-raw:latest, but I am afraid you might have issues with cloud-hypervisor in aarch64. You can give it a try, otherwise, we will test it manually.

@Anamika1608

Copy link
Copy Markdown
Contributor Author

hi @cmainas,

did both things you mentioned.

  • socket_path now creates the directory

default stays /tmp (behind a constant), and for a custom socket_path urunc creates the directory inside the monitor rootfs, so any path works.

  • cloud hypervisor on aarch64

the socket part works: it launches with --api-socket path=/tmp/<id>.sock, the socket is created, and it answers api calls (queried /api/v1/vm.info, got the vm config back).

the guest does not boot though, looks like the aarch64 issue you warned about.

so the socket is verified on all three monitors now.

@Anamika1608
Anamika1608 force-pushed the firecracker-socket-path branch from 0647fcb to 2e20d7d Compare July 25, 2026 15:31
@Anamika1608 Anamika1608 changed the title feat(monitors): expose a configurable control socket feat(qemu): expose a configurable QMP control socket Jul 25, 2026
Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
@Anamika1608
Anamika1608 force-pushed the firecracker-socket-path branch from 2e20d7d to 02c0ac8 Compare July 27, 2026 08:04
Add an opt-in boot_mode field to the monitor configuration. It is
inert in this commit; the following commit makes qemu honor
boot_mode=api.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
With boot_mode=api, QEMU is spawned after changeRoot as a supervised
child with its CPUs frozen (-S), so the monitor and its QMP socket are
confined inside the monitor rootfs. urunc performs the QMP handshake
and sends cont only after the start-success handshake, preserving OCI
start ordering, then supervises the child for its lifetime.

The machine configuration itself stays on the command line: QMP cannot
configure a machine, so for QEMU the api mode drives only the guest's
start. Any other boot_mode value keeps the exec-based boot unchanged.

Add a QMP client implementing the handshake and single commands, and
unit tests driving it against a fake QMP server on a Unix socket.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
createTapDevice relies on a later syscall.Exec into the monitor to
close the tap device fd that netlink.LinkAdd opened (O_CLOEXEC). In any
flow where urunc keeps running instead of exec-ing away, the fd stays
open and, since the tap device is single-queue, blocks the monitor from
attaching to it ("Resource busy").

Close the fd explicitly after the owner and group are set (handling the
close error), and add the author to contributors.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Reject boot_mode=api with virtiofs sharedfs up front, since QMP cannot
be sequenced ahead of virtiofsd's own startup. Pass real stdin through
to the paused QEMU child, matching the exec path's -serial stdio.
Bound every QMP read/write with a deadline instead of only the initial
dial, and restore the SIGKILL-cannot-be-caught note on Supervise.

Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
Signed-off-by: Anamika Aggarwal <anamikaagg18@gmail.com>
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