Step 1 of 5 · Reading · ~4 min
Read
Linux Namespaces
Network Namespace
The third lie: the network. A container gets its own network stack — interfaces, IP addresses, routing table, firewall rules, and (the part every developer has felt) its own port space. Two containers can both bind port 8080 without conflict because, as far as the kernel's concerned, they're binding ports on different machines.
A fresh stack is an empty stack
Create a network namespace (CLONE_NEWNET) and the process finds itself on a machine with one interface: lo (loopback, 127.0.0.1) — and even that starts down. No eth0, no routes, no connectivity. Everything else the container will have must be constructed and plumbed in explicitly.
That emptiness is the security model. The container can't reach the network because there is no network in its universe — not because a firewall says no. Same theme, third verse: PID (processes unaddressable), mount (paths unlisted), network (wires absent).
What lives inside the namespace
Each network namespace owns, independently:
- Interfaces — a physical or virtual NIC lives in exactly one namespace at a time (it can be moved between them).
- IP addresses — assigned to interfaces, so they're namespaced too.
- Routing table — "to reach X, send via Y" decisions, private per namespace.
- The port table — bindings like
0.0.0.0:8080. This is why the classicEADDRINUSEdisappears between containers: the bind tables are simply different tables. - Firewall rules (iptables/nftables) — per namespace, which is why in-container rules don't affect the host.
The complete state of "being a machine on a network," duplicated per container, by one kernel.
Getting packets in and out
An isolated stack is uselessly pure — real containers talk. The kernel's building block is the veth pair (virtual ethernet): two interfaces created as a unit, permanently joined like a two-ended cable — packet in one end, out the other. Standard container plumbing:
- Create a veth pair on the host.
- Move one end into the container's namespace (it becomes the container's
eth0). - Attach the other end to a host-side bridge (a virtual switch, typically
docker0) — connecting many containers' cables into one LAN. - NAT at the host boundary translates container addresses to the host's for the outside world, and port publishing (
-p 8080:80) adds a rule steering host-port traffic to a container address.
That's the topology behind every docker run -p — and it's precisely what the veth + Bridge + NAT lesson (and its exercise) has you build two lessons from now. Today's model is the prerequisite: namespaces as separate stacks, connectivity as explicit wiring between them.
Why the exercise is a simulator
Creating a network namespace, or moving an interface into one, needs CAP_NET_ADMIN — which the sandbox running your submission does not have. So the exercise models what a struct net actually contains: a device list, an address per device, and a routing table, one independent set per namespace. That is not a simplification of the kernel's design, it is the kernel's design; the parts we drop are the driver plumbing, not the ownership rules.
Your exercise: Network Namespace Simulator
| The kernel does | Your simulator does |
|---|---|
clone(CLONE_NEWNET) builds a struct net holding only lo, and even that is down | NEWNS — new namespace pre-loaded with lo 127.0.0.1; emit the id |
ip link set eth0 netns <ns> — a device belongs to exactly one namespace | IFACE-ADD <ns> <name> <ip> — the device appears in that namespace's list only |
ip addr — devices and addresses of the caller's namespace | IFACE-LIST <ns> — that list, sorted by name |
ip route add 172.17.0.0/16 dev eth0 — a FIB entry, per namespace | ROUTE-ADD <ns> <cidr> <iface> |
ip route — the caller's routing table | ROUTE-LIST <ns> — sorted by destination |
The invariant the tests probe: nothing is global. Every lookup goes through "which namespace?" before it goes anywhere else. Two namespaces can each hold an eth0, with different addresses, and neither can see the other's — which is the same fact as two containers both binding :8080 without EADDRINUSE. A single flat dictionary of interfaces will pass the first test and fail the moment a second namespace exists.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…