I first learnt of network namespaces from configuring WireGuard. They offer a very neat way of ensuring all traffic uses your wg interface, in brief:
- create a new netns, call it 'physical' say
- move your physical device's interface to 'physical'
- create wg interface in 'physical'
- move wg interface to init ns
It works because it 'remembers' where it was created and remains bound to it for sending encrypted packets over the physical device, but is now the only interface available to processes by default (i.e. without a superuser making them use 'physical' ns directly).
These are really good posts - I've been mucking about with namespaces recently at work and I wish I had known about this series before today, I wouldn't have had to go figure out a bunch of stuff the hard way!
I hope some future part covers the nsfs and bind mount thing for persisting namespaces past a specific process. It took me way, way to long to catch this passage in the man page:
Bind mounting (see mount(2)) one of the files in this directory to somewhere else in the filesystem keeps the corresponding namespace of the process specified by pid alive even if all processes currently in the namespace terminate.
It's not that it's hard to understand, it's just poorly highlighted in most namespace discourse that I've seen. Persisting namespaces was very much a mystery to me - I had to break down and read the source to understand how the ip command persisted network namespaces before I understood the importance of that sentence.
This technique works more generically to allow you to persist any file descriptor. That's probably why it wasn't mentioned in the documentation (it's just another tool in the toolbox).
Recent pet project: I wanted multiple network clients to access the internet concurrently, each through its own separate VPN session, established on demand and without elevated privileges.
The first part seemed like a simple enough idea until I remembered that traditional IP routing is based on destination address alone. Although I could establish multiple VPN sessions, give each one its own network interface, and configure each client to bind to a different interface (and therefore send packets with a different source address), the routing table had no way to know that it should send CLIENT_A's packets to the server via IFACE_A and CLIENT_B's packets to the same server via IFACE_B.
Policy-based routing looked like a promising solution, but it would require hooking the VPN startup sequence to find each new tunnel interface as it was created, using elevated privileges to install a special routing policy for each interface, and reliably cleaning up the routing tables when each client was done. This seemed overly complicated, and a poor fit for unprivileged users.
Linux namespaces, along with tools that are already in the debian/ubuntu repos, let me do everything I wanted. I ended up writing a couple shell scripts that create N namespaces, give each one a veth connected to a shared bridge device with internet access, establish a namespaced VPN session via each veth, and let the VPN take control of its namespace's default route.
That by itself would have been enough, since any network client running within a namespace is at the mercy of that namespace's routing table, and therefore goes through its own VPN session. I went a couple steps further, though: I added firewall rules within each namespace, to prevent any leaks if the VPN failed or a routing mistake crept in. I also decided to launch a lightweight proxy server in each namespace instead of running my network clients directly within, allowing me to conveniently start and stop my network clients outside of the namespaces and independently of the namespace setup/teardown process.
With the addition of user namespaces, the steps that would normally require root can be run as a fake uid 0 by an unprivileged user. With the help of lxc-user-nic, the one privileged operation that must be run outside the namespaces (adding each veth to the bridge) can be done by an unprivileged user as well. With the help of dumb-init (or tini), stopping the parent process of any namespace automatically cleans up everything within, including the routing table, firewall rules, veth, VPN, and proxy.
This arrangement solved all the problems I set out to solve, and the exercise was a good way to get familiar with namespaces. I might some day convert my hackish shell script implementation to python, perhaps with a little GUI, and see if I can combine it with Firefox containers. Per-container or per-tab VPN sessions in a web browser would be neat.
- create a new netns, call it 'physical' say
- move your physical device's interface to 'physical'
- create wg interface in 'physical'
- move wg interface to init ns
It works because it 'remembers' where it was created and remains bound to it for sending encrypted packets over the physical device, but is now the only interface available to processes by default (i.e. without a superuser making them use 'physical' ns directly).
Not long enough; want to read more: https://www.wireguard.com/netns