Skip to main content

Module network_namespace

Module network_namespace 

Source
Expand description

The capsule’s own network namespace: the structural half of the mechanism that replaced the seccomp-notify connect/sendto supervisor.

[crate::egress_proxy] decides which destinations a native subprocess may reach. This module is what makes that decision the only way out — it puts the subprocess tree somewhere with no route off the host, so an unlisted destination is not “denied” by a filter that has to be consulted, it is simply unreachable.

§The syscall sequence

Everything happens in the forked child’s pre_exec window, before the seccomp filter (which denies unshare, setns and socket(AF_NETLINK) outright) and before crate::sealed’s composed root:

  1. unshare(CLONE_NEWUSER | CLONE_NEWNET), then the identity uid_map/gid_map writes that make the child the owner of both. The user namespace is not incidental: CLONE_NEWNET alone needs CAP_NET_ADMIN, which an unprivileged process does not have, and creating it inside a user namespace it owns is what supplies that capability there and nowhere else. This is the same primitive sealed uses and the same AppArmor restriction governs it — see EgressNamespaceBlocker.
  2. lo is brought up ([bring_loopback_up]). It is the namespace’s only interface: there is no veth pair, no bridge and no NAT, so nothing in the namespace has a path to the host’s network or to the outside world.
  3. One route is installed over netlink ([add_local_default_route]): local default dev lo. A local-type default route makes every address locally deliverable, so a connect() to any address is looped back to a socket in this namespace with the original destination intact — recoverable with getsockname(2). That is the entire interception mechanism, and it is why no netfilter rule is needed anywhere. It also means a lookup reaches the resolver whatever address /etc/resolv.conf names, and a connection to a port nothing is bound to is refused immediately rather than stalling through the kernel’s full SYN-retry schedule against a blackhole.
  4. The listening sockets are created here, inside the namespace — one TCP listener per port the allowlist implies, then one wildcard UDP :53 resolver socket — and handed back to the parent in a single SCM_RIGHTS message. A socket belongs to the namespace it was created in, not the one it is used from, which is the whole reason this inversion works: the runtime serves them while staying in the host’s namespace, with real internet access for the upstream half.

§Why this shape rather than the obvious ones

No veth pair. Wiring a namespace to the host with veth requires CAP_NET_ADMIN in the host’s network namespace, which means real root. This design needs no host privilege at all, which is what lets mur install per-user.

No proxy process inside the namespace. setns(2) into the namespace from a thread of the runtime is not possible: it requires CAP_SYS_ADMIN in the caller’s own user namespace, and an unprivileged multithreaded process cannot get it (unshare(CLONE_NEWUSER) requires a single-threaded process). Passing the listening sockets out inverts the problem and needs no privilege whatsoever. This was established empirically, not assumed — the setns route was tried first and returned EPERM.

No iptables/nft/TPROXY. Nothing here needs a netfilter rule, so there is no dependency on netfilter modules being present, no iptables binary to shell out to, and no rule set that can drift out of step with the manifest.

§What this does not isolate

AF_UNIX is untouched. CLONE_NEWNET does not mediate unix sockets in any way that matters here — a pathname socket is reached through the filesystem, not the network stack. That is exactly why the register-level socket(2) domain filter (denied_socket_domains in [crate::sandbox]) is untouched here and remains the thing that refuses /var/run/docker.sock.

Enums§

EgressNamespaceBlocker
Why this host cannot give a capsule its own network namespace.

Functions§

check_egress_namespace
Refuses the launch when a capsule that can spawn native subprocesses is on a host that cannot give them a network namespace.
detect_egress_namespace_blocker
Which blocker, if any, stands between this host and a capsule network namespace.
skip_without_egress_namespace
Whether a test that needs a capsule network namespace must stand down on this host, printing the blocker when it must.