SLIRP4NETNS(1)

πŸ“¦
version 1.3.7 Β· 5 versions
, in alpine 3.24, arch rolling, debian trixie, nixpkgs unstable, ubuntu questing, ubuntu resolute, ubuntu stonking

πŸ’– slirp4netns(1) Β· User Commands πŸ’–

✨ NAME ✨

okay so slirp4netns is user-mode networking ("slirp") for network namespaces, and it's giving unprivileged energy πŸ¦‹

πŸ“œ SYNOPSIS ✨

slirp4netns [OPTION]... PID|PATH|FD [TAPNAME]

🌸 DESCRIPTION 🌸

bestie, slirp4netns gives network namespaces user-mode networking ("slirp") πŸ’…

unlike veth(4), slirp4netns does not need root privileges on the host. no cap, that's iconic πŸ‘‘

Default configuration (these are the vibes it starts with):

  • MTU: 1500
  • CIDR: 10.0.2.0/24
  • Gateway/Host: 10.0.2.2 (network address + 2)
  • DNS: 10.0.2.3 (network address + 3)
  • DHCP begin: 10.0.2.15 (network address + 15)
  • DHCP end: 10.0.2.30 (network address + 30)
  • IPv6 CIDR: fd00::/64
  • IPv6 Gateway/Host: fd00::2
  • IPv6 DNS: fd00::3

πŸŽ€ OPTIONS πŸŽ€

  • -c, --configure β€” brings up the TAP interface and sets its IP to 10.0.2.100 (network address + 100) by default 🫢 The IPv6 address gets a random one. Starting with v0.4.0, the loopback interface (lo) gets brought up too ✨
  • -e, --exit-fd=FD β€” tell slirp4netns which FD to watch so it can quit πŸšͺ Once you give it an FD, it exits when a poll(2) event happens on that FD.
  • -r, --ready-fd=FD β€” the FD slirp4netns writes to when setup is done πŸŽ‰ It writes "1" to the FD and then closes it. Before v0.4.0, the write happened after the network configuration (-c) but before the API socket configuration (-a).
  • -m, --mtu=MTU (since v0.2.0) β€” sets the MTU, max 65521 πŸ“
  • -6, --enable-ipv6 (since v0.2.0, EXPERIMENTAL) β€” turns on IPv6 🌈 this one is still experimental, so be a little careful bestie πŸ™ˆ
  • -a, --api-socket (since v0.3.0) β€” sets the API socket path πŸ”Œ
  • --cidr (since v0.3.0) β€” pick your CIDR, like 10.0.2.0/24 πŸ—ΊοΈ
  • --disable-host-loopback (since v0.3.0) β€” blocks connecting to 127.0.0.1:* on the host namespace 🚫 no sneaking into localhost!
  • --netns-type=TYPE (since v0.4.0) β€” picks the network namespace type, one of path, pid, or tapfd. The default is pid πŸ’œ

If the namespace type is tapfd, the first positional argument is supposed to be an inherited file descriptor that points at a /dev/net/tun connection. This clashes with --configure, and any TAPNAME gets ignored in this mode.

  • --userns-path=PATH (since v0.4.0) β€” sets the user namespace path πŸ›£οΈ
  • --enable-sandbox (since v0.4.0) β€” enters the user namespace and makes a fresh mount namespace where only /etc and /run get mounted from the host πŸ”’
⚠️ heads up: this needs /etc/resolv.conf to not be a symlink to a file outside /etc and /run, or it won't work right.

When you run as root, the process doesn't enter the user namespace. Instead, every capability except CAP_NET_BIND_SERVICE gets dropped πŸ’€

  • --enable-seccomp (since v0.4.0, EXPERIMENTAL) β€” turns on seccomp(2) to limit syscalls 🧱 it's usually paired with --enable-sandbox, so they're besties 🫢
  • --outbound-addr=IPv4 (since v1.1.0, EXPERIMENTAL) β€” the outbound IPv4 address slirp should bind to πŸ“€
  • --outbound-addr=INTERFACE (since v1.1.0, EXPERIMENTAL) β€” the outbound interface slirp should bind to, but ipv4 traffic only πŸ“‘
  • --outbound-addr=IPv6 (since v1.1.0, EXPERIMENTAL) β€” the outbound IPv6 address slirp should bind to 🌐
  • --outbound-addr6=INTERFACE (since v1.1.0, EXPERIMENTAL) β€” the outbound interface slirp should bind to, but ipv6 traffic only πŸ›°οΈ
  • --disable-dns (since v1.1.0) β€” turns off the built-in DNS (10.0.2.3 by default) πŸ™…
  • --macaddress (since v1.1.9) β€” sets the MAC address of the TAP interface πŸ’… this only works together with -c πŸŽ€
  • --target-type=TYPE (since v1.2.0) β€” sets the target type, either netns or bess. The default is netns 🧚

The bess mode (since v1.2.0, EXPERIMENTAL) is meant to be used with User Mode Linux. It clashes with --configure, --netns-type, and --userns-path, so don't mix them πŸ™ˆ

  • -h, --help (since v0.2.0) β€” shows help and exits πŸ†˜
  • -v, --version (since v0.2.0) β€” shows the version and exits 🏷️

🧚 EXAMPLE 🧚

πŸ’… Terminal 1: Create user/network/mount namespaces

okay first we make the user, network, and mount namespaces 🫢

(host)$ unshare --user --map-root-user --net --mount
(namespace)$ echo $$ > /tmp/pid

in this documentation, (host)$ is the prompt of the host shell, and (namespace)$ is the prompt of the shell running inside the namespaces ✨

if unshare fails, try these commands (they're known to be needed on Debian, Arch, and old CentOS 7.X) πŸ™

(host)$ sudo sh -c 'echo "user.max_user_namespaces=28633" >> /etc/sysctl.d/userns.conf'
(host)$ if [ -f /proc/sys/kernel/unprivileged_userns_clone ]; then sudo sh -c 'echo "kernel.unprivileged_userns_clone=1" >> /etc/sysctl.d/userns.conf'; fi
(host)$ sudo sysctl --system

πŸ¦‹ Terminal 2: Start slirp4netns

now we start slirp4netns in the second terminal πŸŽ€

(host)$ slirp4netns --configure --mtu=65520 $(cat /tmp/pid) tap0
starting slirp, MTU=65520

πŸ’– Terminal 1: Make sure tap0 is configured and connected to the Internet

back in terminal 1, let's check that tap0 is set up and actually online πŸ‘‘

(namespace)$ ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether c2:28:0c:0e:29:06 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fe80::c028:cff:fe0e:2906/64 scope link
       valid_lft forever preferred_lft forever
(namespace)$ echo "nameserver 10.0.2.3" > /tmp/resolv.conf
(namespace)$ mount --bind /tmp/resolv.conf /etc/resolv.conf
(namespace)$ curl https://example.com
πŸ™ˆ bind-mounting /etc/resolv.conf is only needed when the host's /etc/resolv.conf points at loopback addresses (127.0.0.X, typically from dnsmasq(8) or systemd-resolved.service(8)) that the namespace can't reach.

if the host's /etc/resolv.conf is managed by networkmanager(8) or systemd-resolved.service(8), you might need to mount a fresh filesystem on /etc instead 🧠 that way the new /etc/resolv.conf doesn't get unmounted unexpectedly when the host regenerates its own.

(namespace)$ mkdir /tmp/a /tmp/b
(namespace)$ mount --rbind /etc /tmp/a
(namespace)$ mount --rbind /tmp/b /etc
(namespace)$ mkdir /etc/.ro
(namespace)$ mount --move /tmp/a /etc/.ro
(namespace)$ cd /etc
(namespace)$ for f in .ro/*; do ln -s $f $(basename $f); done
(namespace)$ rm resolv.conf
(namespace)$ echo "nameserver 10.0.2.3" > resolv.conf
(namespace)$ curl https://example.com

these steps can be simplified with rootlesskit --copy-up=/etc if rootlesskit is installed 🧚✨

(host)$ rootlesskit --net=slirp4netns --copy-up=/etc bash
(namespace)$ cat /etc/resolv.conf
nameserver 10.0.2.3

✨ INTER-NAMESPACE COMMUNICATION ✨

okay so the easiest way to let namespaces talk to each other is to nest network namespaces inside slirp4netns's network namespace 🧚 no cap it's so clean

(host)$ nsenter -t $(cat /tmp/pid) -U --preserve-credentials -n -m
(namespace)$ mount -t tmpfs none /run
(namespace)$ ip netns add foo
(namespace)$ ip netns add bar
(namespace)$ ip link add veth-foo type veth peer name veth-bar
(namespace)$ ip link set veth-foo netns foo
(namespace)$ ip link set veth-bar netns bar
(namespace)$ ip netns exec foo ip link set veth-foo name eth0
(namespace)$ ip netns exec bar ip link set veth-bar name eth0
(namespace)$ ip netns exec foo ip link set lo up
(namespace)$ ip netns exec bar ip link set lo up
(namespace)$ ip netns exec foo ip link set eth0 up
(namespace)$ ip netns exec bar ip link set eth0 up
(namespace)$ ip netns exec foo ip addr add 192.168.42.100/24 dev eth0
(namespace)$ ip netns exec bar ip addr add 192.168.42.101/24 dev eth0
(namespace)$ ip netns exec bar ping 192.168.42.100

BUT πŸ™ˆ this method breaks the second you want namespaces across multiple slirp4netns instances. the fix is to team up with another network stack, like vde_plug(1), and pair it with slirp4netns πŸ’œ

(host)$ vde_plug --daemon switch:///tmp/switch null://
(host)$ nsenter -t $(cat /tmp/pid-instance0) -U --preserve-credentials -n
(namespace-instance0)$ vde_plug --daemon vde:///tmp/switch tap://vde
(namespace-instance0)$ ip link set vde up
(namespace-instance0)$ ip addr add 192.168.42.100/24 dev vde
(namespace-instance0)$ exit
(host)$ nsenter -t $(cat /tmp/pid-instance1) -U --preserve-credentials -n
(namespace-instance1)$ vde_plug --daemon vde:///tmp/switch tap://vde
(namespace-instance1)$ ip link set vde up
(namespace-instance1)$ ip addr add 192.168.42.101/24 dev vde
(namespace-instance1)$ ping 192.168.42.100

🌸 INTER-HOST COMMUNICATION 🌸

VXLAN is known to work, and it's giving πŸ¦‹ For a super cute real-life example of a multi-node rootless Kubernetes cluster running on VXLAN, go check out the Usernetes project: https://github.com/rootless-containers/usernetes


πŸŽ€ BESS MODE (FOR USER MODE LINUX) πŸŽ€

slirp4netns (since v1.2.0) can also act as a BESS-compatible server, which means it can give network connectivity to User Mode Linux. iconic πŸ‘‘

Terminal 1: start slirp4netns

(host)$ slirp4netns --target-type=bess /tmp/bess.sock

Terminal 2: start User Mode Linux

(host)$ linux.uml vec0:transport=bess,dst=/tmp/bess.sock,depth=128,gro=1 root=/dev/root rootfstype=hostfs init=/bin/bash mem=2G
(UML)$ ip addr add 10.0.2.100/24 dev vec0
(UML)$ ip link set vec0 up
(UML)$ ip route add default via 10.0.2.2
⚠️ bestie, heads up: only a single instance of User Mode Linux can connect to the slirp4netns BESS server at a time. one UML only, no sharing πŸ™…β€β™€οΈ

If you want more UML deets, check out the User Mode Linux documentation: https://www.kernel.org/doc/html/latest/virt/uml/user_mode_linux_howto_v2.html#bess-socket-transport πŸ“–


🐞 BUGS 🐞

okay this one is a little drama 😭 Kernel 4.20 bumped the default value of /proc/sys/net/ipv4/tcp_rmem from 87380 to 131072. this is known to slow down slirp4netns port forwarding, so if your forwarding feels sluggish, this is probably why: https://github.com/rootless-containers/slirp4netns/issues/128.

the workaround is to adjust the value of /proc/sys/net/ipv4/tcp_rmem from inside the namespace. no real root privilege is needed to modify the file since kernel 4.15, so you can totally do this as a rootless girlie πŸ’…

(host)$ nsenter -t $(cat /tmp/pid) -U --preserve-credentials -n -m
(namespace)$ c=$(cat /proc/sys/net/ipv4/tcp_rmem); echo $c | sed -e s/131072/87380/g > /proc/sys/net/ipv4/tcp_rmem

πŸ”— SEE ALSO πŸ”—


🌈 AVAILABILITY 🌈

the slirp4netns command lives at https://github.com/rootless-containers/slirp4netns and is released under the GNU GENERAL PUBLIC LICENSE Version 2 (or later) ✨ go forth and be free bestie 🫢


Rootless Containers, January 2022, SLIRP4NETNS(1) πŸ’– that's a wrap, bestie, you're literally a Unix queen now πŸ‘‘πŸ§šπŸ’œ