Routing stays abstract until you configure it yourself. Network namespaces let you build a real multi-host topology on one machine, with real interfaces and a real routing table — and delete the whole thing with two commands.
1. Create two isolated hosts
sudo ip netns add host1
sudo ip netns add host2
ip netns list
sudo ip netns exec host1 ip -brief addressA namespace has its own interfaces, routing table, and firewall rules. host1 has only loopback, and it is down — a namespace starts with nothing, exactly like a machine with no cables in it.
sudo ip netns exec host1 ping -c1 -W1 8.8.8.8; echo "exit=$?"That fails: no interface, no route.
Verify
2. Connect them with a virtual cable
sudo ip link add veth1 type veth peer name veth2
sudo ip link set veth1 netns host1
sudo ip link set veth2 netns host2
sudo ip netns exec host1 ip -brief link
sudo ip netns exec host2 ip -brief linkA veth pair is two interfaces joined back to back: anything sent into one comes out of the other. Moving each end into a different namespace makes it a cable between two hosts.
Verify
3. Address both ends and bring them up
sudo ip netns exec host1 ip address add 10.10.0.1/24 dev veth1
sudo ip netns exec host1 ip link set veth1 up
sudo ip netns exec host1 ip link set lo up
sudo ip netns exec host2 ip address add 10.10.0.2/24 dev veth2
sudo ip netns exec host2 ip link set veth2 up
sudo ip netns exec host2 ip link set lo up
sudo ip netns exec host1 ping -c2 10.10.0.2They can talk. Note what you did _not_ configure: no route and no gateway. Adding an address with a /24 implicitly creates a route for that whole network — the "connected route" — which is the prefix-length lesson from lab 2 made concrete.
sudo ip netns exec host1 ip route showVerify
4. Prove the isolation is real
sudo ip netns exec host1 ss -tln
sudo ip netns exec host2 nc -l 0.0.0.0 8080 &
sleep 1
sudo ip netns exec host1 nc -z -w2 10.10.0.2 8080; echo "from host1: exit=$?"
nc -z -w2 10.10.0.2 8080; echo "from the real host: exit=$?"
sudo pkill -f "nc -l" 2>/dev/nullhost1 reaches the service. The real machine cannot — 10.10.0.0/24 exists only inside those namespaces, and the host has no route to it. That is exactly how a container network works; Docker's default bridge is this plus a bridge device.
Verify
5. Give host1 the internet with a gateway and NAT
Add a second veth pair, this time from host1 to the real host:
sudo ip link add veth-h type veth peer name veth-ns
sudo ip link set veth-ns netns host1
sudo ip address add 10.20.0.1/24 dev veth-h
sudo ip link set veth-h up
sudo ip netns exec host1 ip address add 10.20.0.2/24 dev veth-ns
sudo ip netns exec host1 ip link set veth-ns upNow host1 has an address on a network the real host is also on, but still no way out — nothing tells it where to send packets for the rest of the world:
sudo ip netns exec host1 ping -c1 -W1 1.1.1.1; echo "before route: exit=$?"
sudo ip netns exec host1 ip route add default via 10.20.0.1
sudo ip netns exec host1 ip route show
sudo ip netns exec host1 ping -c1 -W1 1.1.1.1; echo "after route: exit=$?"Still failing — but for a different reason now. The packet leaves, and the reply cannot come back, because 10.20.0.2 is not routable on the internet. That is what NAT is for:
sudo sysctl -w net.ipv4.ip_forward=1
OUT=$(ip route show default | awk '{print $5; exit}')
sudo iptables -t nat -A POSTROUTING -s 10.20.0.0/24 -o "$OUT" -j MASQUERADE
sudo ip netns exec host1 ping -c2 1.1.1.1Two separate things were needed. ip_forward lets the host route between interfaces at all — off by default, and the reason a NAT gateway you built "does nothing". MASQUERADE rewrites the source address to the host's own, so replies come back and get translated in return.
Verify
6. Add DNS, because an address is not a name
sudo ip netns exec host1 getent hosts example.com; echo "exit=$?"
sudo mkdir -p /etc/netns/host1
echo "nameserver 1.1.1.1" | sudo tee /etc/netns/host1/resolv.conf
sudo ip netns exec host1 getent hosts example.comA namespace gets its own /etc/resolv.conf from /etc/netns/<name>/. Until you create it, the namespace inherits the host's — which often points at 127.0.0.53, a stub resolver that does not exist inside the namespace. Reachable by IP, unresolvable by name: the exact symptom the next lab's challenge is built on.
Verify
Clean up
sudo iptables -t nat -D POSTROUTING -s 10.20.0.0/24 \
-o "$(ip route show default | awk '{print $5; exit}')" -j MASQUERADE
sudo ip netns delete host1
sudo ip netns delete host2
sudo ip link delete veth-h 2>/dev/null
sudo rm -rf /etc/netns/host1
ip netns list | wc -lDeleting a namespace removes every interface inside it, so both veth ends disappear with it.
Verify
Where this goes next
You have built and routed a network by hand. The last piece of the vocabulary is the layer on top: TLS.