Skip to content

feat(monitor): network hyperlight-unikraft guests through the container's namespace - #1135

Merged
cmainas merged 6 commits into
urunc-dev:main-pr1135from
danbugs:hyperlight-networking
Oct 26, 2026
Merged

cmainas merged 6 commits into
urunc-dev:main-pr1135from
danbugs:hyperlight-networking

Conversation

@danbugs

@danbugs danbugs commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Description

hluk has no network device to offer a guest. It forwards each socket call of the guest to a real socket that it opens itself, so a guest reaches exactly the network of the namespace hluk runs in. urunc's networking assumes a monitor with a virtual NIC: it creates a tap in the container's namespace and redirects the traffic of the container's interface to it, which would take that traffic away from hluk's sockets. This lets a monitor keep the namespace as it is, turns hluk's networking on, and installs the container's resolver configuration in the guest.

  • Opting out of the tap: UsesTUNTAP joins UsesKVM on the VMM interface. The six tap-based monitors report true and are unchanged. For a monitor that reports false, SetupNet creates no tap and leaves the interface of the container alone, only describing it, and the monitor gets neither /dev/net/tun nor CAP_NET_ADMIN, which it does not need; the decision is recorded once in the monitor resources, where the capability code reads it.
  • hluk networking: hyperlight-unikraft starts hluk with --net --port=all, for a booted and a resumed guest alike, since hluk gives a guest no network unless told to and, since v0.18.0, --port on its own only lets the guest serve. The address of the pod, its routes and the NetworkPolicy that applies to it stay the boundary, as they are for every other container. A container started with no network has no interface in its namespace, and the guest is then left to hluk's default of no network, as a second boundary.
  • Ports: the com.urunc.unikernel.hyperlightPorts annotation limits the ports the guest may serve on, as ports and LOW-HIGH ranges passed to hluk as one --port each. Without it the guest serves on any port, as a container does. A deployment can set it on an image configured through urunc.json too, where it replaces what the image says. urunc validates the value and rejects the annotation on any other monitor, since no other can enforce it.
  • resolv.conf: the guest resolves names itself, with the resolv.conf of its rootfs or snapshot, which knows nothing of the cluster's DNS. urunc applies the container's own /etc/resolv.conf mount in the monitor rootfs as well, read-only, and passes --resolv-conf, so hluk installs it at boot and on every restore, and the guest follows the engine's file as a container does. A container without one leaves the guest with its own. The nameserver extraction for tap guests is skipped when the guest gets the whole file.
  • Ports below 1024: the guest binds ports as the user of the container, and the monitor keeps no capability across the switch to a non-root user, so such a container needs net.ipv4.ip_unprivileged_port_start=0 in its namespace to serve on a privileged port. This is how every monitor behaves today and is only observable now that a guest can bind; the docs say so.
  • Docs and tests: the hypervisor-support page describes the model, and a docker and a nerdctl e2e case serve HTTP from the guest and fetch it from the host at the container's address.

Related issues

Part of #140, following #1058.

How was this tested?

Unit tests cover the opt-out (a monitor without a tap gets no tun device and no extra capability), the hluk arguments for booted and resumed guests with and without a network, a resolv.conf and a ports annotation, the validation of that annotation, the resolv.conf mount and the choice of the last /etc/resolv.conf mount in the spec. make test_docker passes, including the new Hyperlight-unikraft-http case, except for the one case that needs an io.containerd.urunc-fc.v2 runtime my docker does not define; the ctr Hyperlight-unikraft-hello case passes too. By hand on KVM with docker and hluk v0.18.0, using the v0.18.0 hello image with a script added to its initrd: the guest sees the container's resolv.conf, resolves github.com and fetches http://example.com (HTTP 200), and a Python http.server in the guest answers requests from the host on a published port and at the container's address. The ports annotation restricts that server to the listed ports (hluk gets one --port per entry, and a request on a port outside them fails), --network none boots the guest with no network at all, and a qemu image rejects the annotation.

LLM usage

Fable 5.1

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 Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for urunc ready!

Name Link
🔨 Latest commit 52f3b81
🔍 Latest deploy log https://app.netlify.com/projects/urunc/deploys/6adf928973d5e2b21326e3cf
😎 Deploy Preview https://deploy-preview-1135--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.

@cmainas cmainas 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.

Thank you @danbugs for adding support for network. A have left some comments. In the meantime I will create the images for the end-to-end tests.

Comment thread pkg/unikontainers/hypervisors/hyperlight.go
Comment thread pkg/unikontainers/types/types.go Outdated
Comment thread pkg/unikontainers/types/types.go Outdated
Comment thread pkg/unikontainers/unikontainers.go Outdated
Comment thread pkg/unikontainers/unikontainers.go Outdated
Comment thread pkg/unikontainers/types/types.go Outdated
Comment thread pkg/unikontainers/libcontainer.go

@cmainas cmainas 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.

Hello @danbugs ,

thank you for the update. It seems I did not have to build any new images, since the tests are using the existing one.

I just have added a couple cleanup related comments. Also, please rebase over main so the end-to-end tests can run without issues.

Comment thread pkg/unikontainers/unikontainers.go Outdated
Comment thread pkg/unikontainers/unikontainers.go
urunc gives every guest its network through a tap device: it creates one
in the network namespace of the container and redirects the traffic of
the container's interface to it. That fits a monitor that emulates a
network device, but not one whose guest reaches the network through
sockets that the monitor itself opens in the namespace. For such a
monitor the redirect would take the traffic away from those sockets.

Add UsesHostNet to the VMM interface, false on every monitor so far.
When a monitor reports it, both the libcontainer and the plain paths
skip SetupNet, so no tap is created and the interface of the container
is left as it is, and mark the network parameters with HostNet so the
monitor knows to open the network to its guest. Such a monitor needs
neither /dev/net/tun in its rootfs nor the CAP_NET_ADMIN that urunc
adds to the monitor's capabilities for the tap, so leave both out.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
hluk has no network device to offer a guest. It forwards each socket
call of the guest to a real socket that it opens itself, so a guest
reaches exactly the network of the namespace hluk runs in: the address
of the pod, its routes and any NetworkPolicy that applies to it.
Report that through UsesHostNet, so urunc leaves the interface of the
container alone instead of redirecting its traffic to a tap that
nothing reads.

hluk gives a guest no network unless told to, and since v0.18.0 --port
on its own only lets the guest serve: it can then connect nowhere, not
even to a DNS server. A container expects the whole network of its
namespace, outbound and inbound, so start hluk with both, --net and
--port=all, for a booted and a resumed guest alike. No hluk policy is
layered on top: the namespace, and the NetworkPolicy that applies to
it, stay the boundary, as they are for every other container.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
A hyperlight-unikraft guest resolves names itself, over the sockets
hluk opens for it, with the /etc/resolv.conf of its own filesystem. A
booted guest gets that file from the rootfs CPIO, and a resumed one from
the snapshot, which was saved wherever the image was built. Neither
knows the nameserver or the search domains of the container, so cluster
names do not resolve in the guest.

hluk installs a resolver configuration in the guest, at boot and on
every restore, with --resolv-conf. For a monitor on host networking
only, bind-mount the resolv.conf that the engine mounts in the container
read-only into the monitor rootfs, at /etc/resolv.conf, along with the
other mounts the monitor gets, and point hluk at it. The guest follows
the engine's file as a container does. A container without a
resolv.conf leaves the guest with its own, and the nameserver that the
tap-based guests take from the file is not extracted for one that gets
the whole file.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
Explain that hluk forwards the sockets of the guest to the network
namespace of the container, so urunc creates no tap for this monitor and
starts hluk with both outbound and inbound networking, and that the
resolv.conf of the container is installed in the guest, whether it
boots or resumes from a snapshot.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
The Hyperlight case only prints a greeting, so the networking of the
guest is verified by nothing but the arguments urunc builds for hluk.
Add a docker and a nerdctl case that run the hello image with Python's
http.server, which the Python driver runs like any script from the
initrd, and fetch its root from the host at the address of the
container: the guest binds in the network namespace of the container
and answers through the sockets hluk opens for it. A hluk release that
changed the meaning of --net or --port would fail it. ctr gets no case,
since it gives a container no address to reach.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
A container serves on whatever port its workload binds, and so does a
hyperlight-unikraft guest, since urunc starts hluk with --port=all. hluk
can also restrict the ports a guest binds, so let a deployment ask for
that with the com.urunc.unikernel.hyperlightPorts annotation: a
comma-separated list of ports and LOW-HIGH ranges, passed to hluk as one
--port each. An image configured through urunc.json takes it too, as a
run-time annotation that replaces what the image says. urunc rejects the
annotation on any other monitor, since no other can enforce it, and
checks its format before it reaches the hluk command line. This is a
second boundary inside the network namespace, opt-in, so that the
default stays what a container gets.

Signed-off-by: danbugs <danilochiarlone@gmail.com>
@danbugs
danbugs force-pushed the hyperlight-networking branch from a12b46b to 52f3b81 Compare October 26, 2026 17:48
@urunc-bot
urunc-bot Bot changed the base branch from main to main-pr1135 October 26, 2026 20:46

@cmainas cmainas 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.

Thank you @danbugs for the network support!

@cmainas
cmainas merged commit 844714f into urunc-dev:main-pr1135 Oct 26, 2026
68 of 77 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
urunc gives every guest its network through a tap device: it creates one
in the network namespace of the container and redirects the traffic of
the container's interface to it. That fits a monitor that emulates a
network device, but not one whose guest reaches the network through
sockets that the monitor itself opens in the namespace. For such a
monitor the redirect would take the traffic away from those sockets.

Add UsesHostNet to the VMM interface, false on every monitor so far.
When a monitor reports it, both the libcontainer and the plain paths
skip SetupNet, so no tap is created and the interface of the container
is left as it is, and mark the network parameters with HostNet so the
monitor knows to open the network to its guest. Such a monitor needs
neither /dev/net/tun in its rootfs nor the CAP_NET_ADMIN that urunc
adds to the monitor's capabilities for the tap, so leave both out.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
hluk has no network device to offer a guest. It forwards each socket
call of the guest to a real socket that it opens itself, so a guest
reaches exactly the network of the namespace hluk runs in: the address
of the pod, its routes and any NetworkPolicy that applies to it.
Report that through UsesHostNet, so urunc leaves the interface of the
container alone instead of redirecting its traffic to a tap that
nothing reads.

hluk gives a guest no network unless told to, and since v0.18.0 --port
on its own only lets the guest serve: it can then connect nowhere, not
even to a DNS server. A container expects the whole network of its
namespace, outbound and inbound, so start hluk with both, --net and
--port=all, for a booted and a resumed guest alike. No hluk policy is
layered on top: the namespace, and the NetworkPolicy that applies to
it, stay the boundary, as they are for every other container.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
A hyperlight-unikraft guest resolves names itself, over the sockets
hluk opens for it, with the /etc/resolv.conf of its own filesystem. A
booted guest gets that file from the rootfs CPIO, and a resumed one from
the snapshot, which was saved wherever the image was built. Neither
knows the nameserver or the search domains of the container, so cluster
names do not resolve in the guest.

hluk installs a resolver configuration in the guest, at boot and on
every restore, with --resolv-conf. For a monitor on host networking
only, bind-mount the resolv.conf that the engine mounts in the container
read-only into the monitor rootfs, at /etc/resolv.conf, along with the
other mounts the monitor gets, and point hluk at it. The guest follows
the engine's file as a container does. A container without a
resolv.conf leaves the guest with its own, and the nameserver that the
tap-based guests take from the file is not extracted for one that gets
the whole file.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
Explain that hluk forwards the sockets of the guest to the network
namespace of the container, so urunc creates no tap for this monitor and
starts hluk with both outbound and inbound networking, and that the
resolv.conf of the container is installed in the guest, whether it
boots or resumes from a snapshot.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
The Hyperlight case only prints a greeting, so the networking of the
guest is verified by nothing but the arguments urunc builds for hluk.
Add a docker and a nerdctl case that run the hello image with Python's
http.server, which the Python driver runs like any script from the
initrd, and fetch its root from the host at the address of the
container: the guest binds in the network namespace of the container
and answers through the sockets hluk opens for it. A hluk release that
changed the meaning of --net or --port would fail it. ctr gets no case,
since it gives a container no address to reach.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
A container serves on whatever port its workload binds, and so does a
hyperlight-unikraft guest, since urunc starts hluk with --port=all. hluk
can also restrict the ports a guest binds, so let a deployment ask for
that with the com.urunc.unikernel.hyperlightPorts annotation: a
comma-separated list of ports and LOW-HIGH ranges, passed to hluk as one
--port each. An image configured through urunc.json takes it too, as a
run-time annotation that replaces what the image says. urunc rejects the
annotation on any other monitor, since no other can enforce it, and
checks its format before it reaches the hluk command line. This is a
second boundary inside the network namespace, opt-in, so that the
default stays what a container gets.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
urunc gives every guest its network through a tap device: it creates one
in the network namespace of the container and redirects the traffic of
the container's interface to it. That fits a monitor that emulates a
network device, but not one whose guest reaches the network through
sockets that the monitor itself opens in the namespace. For such a
monitor the redirect would take the traffic away from those sockets.

Add UsesHostNet to the VMM interface, false on every monitor so far.
When a monitor reports it, both the libcontainer and the plain paths
skip SetupNet, so no tap is created and the interface of the container
is left as it is, and mark the network parameters with HostNet so the
monitor knows to open the network to its guest. Such a monitor needs
neither /dev/net/tun in its rootfs nor the CAP_NET_ADMIN that urunc
adds to the monitor's capabilities for the tap, so leave both out.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
hluk has no network device to offer a guest. It forwards each socket
call of the guest to a real socket that it opens itself, so a guest
reaches exactly the network of the namespace hluk runs in: the address
of the pod, its routes and any NetworkPolicy that applies to it.
Report that through UsesHostNet, so urunc leaves the interface of the
container alone instead of redirecting its traffic to a tap that
nothing reads.

hluk gives a guest no network unless told to, and since v0.18.0 --port
on its own only lets the guest serve: it can then connect nowhere, not
even to a DNS server. A container expects the whole network of its
namespace, outbound and inbound, so start hluk with both, --net and
--port=all, for a booted and a resumed guest alike. No hluk policy is
layered on top: the namespace, and the NetworkPolicy that applies to
it, stay the boundary, as they are for every other container.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
A hyperlight-unikraft guest resolves names itself, over the sockets
hluk opens for it, with the /etc/resolv.conf of its own filesystem. A
booted guest gets that file from the rootfs CPIO, and a resumed one from
the snapshot, which was saved wherever the image was built. Neither
knows the nameserver or the search domains of the container, so cluster
names do not resolve in the guest.

hluk installs a resolver configuration in the guest, at boot and on
every restore, with --resolv-conf. For a monitor on host networking
only, bind-mount the resolv.conf that the engine mounts in the container
read-only into the monitor rootfs, at /etc/resolv.conf, along with the
other mounts the monitor gets, and point hluk at it. The guest follows
the engine's file as a container does. A container without a
resolv.conf leaves the guest with its own, and the nameserver that the
tap-based guests take from the file is not extracted for one that gets
the whole file.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
Explain that hluk forwards the sockets of the guest to the network
namespace of the container, so urunc creates no tap for this monitor and
starts hluk with both outbound and inbound networking, and that the
resolv.conf of the container is installed in the guest, whether it
boots or resumes from a snapshot.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
The Hyperlight case only prints a greeting, so the networking of the
guest is verified by nothing but the arguments urunc builds for hluk.
Add a docker and a nerdctl case that run the hello image with Python's
http.server, which the Python driver runs like any script from the
initrd, and fetch its root from the host at the address of the
container: the guest binds in the network namespace of the container
and answers through the sockets hluk opens for it. A hluk release that
changed the meaning of --net or --port would fail it. ctr gets no case,
since it gives a container no address to reach.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
urunc-bot Bot pushed a commit that referenced this pull request Oct 26, 2026
A container serves on whatever port its workload binds, and so does a
hyperlight-unikraft guest, since urunc starts hluk with --port=all. hluk
can also restrict the ports a guest binds, so let a deployment ask for
that with the com.urunc.unikernel.hyperlightPorts annotation: a
comma-separated list of ports and LOW-HIGH ranges, passed to hluk as one
--port each. An image configured through urunc.json takes it too, as a
run-time annotation that replaces what the image says. urunc rejects the
annotation on any other monitor, since no other can enforce it, and
checks its format before it reaches the hluk command line. This is a
second boundary inside the network namespace, opt-in, so that the
default stays what a container gets.

PR: #1135
Signed-off-by: danbugs <danilochiarlone@gmail.com>
Reviewed-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Approved-by: Charalampos Mainas <cmainas@nubificus.co.uk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants