Skip to content
Networking
Lab 5 of 7·40mIntermediate

Build a two-host network on one machine

Use network namespaces and a veth pair to create two isolated hosts, route between them, and give them internet access with NAT.

You need

  • A Linux VM with sudo access and iproute2

Do first

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 address

A 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

ip netns list | wc -l # 2

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 link

A 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

sudo ip netns exec host1 ip -brief link | grep -c veth1 # 1

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.2

They 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 show

Verify

sudo ip netns exec host1 ping -c1 -W2 10.10.0.2 >/dev/null && echo "reachable" # reachable

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/null

host1 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

sudo ip netns exec host1 ip route get 10.10.0.2 | head -n 1 # 10.10.0.2 dev veth1 src 10.10.0.1 — a connected route, no gateway

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 up

Now 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.1

Two 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

sudo ip netns exec host1 ping -c1 -W3 1.1.1.1 >/dev/null && echo "internet reachable" # internet reachable

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.com

A 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

sudo ip netns exec host1 getent hosts example.com | wc -l # 1 or more

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 -l

Deleting a namespace removes every interface inside it, so both veth ends disappear with it.

Verify

ip netns list | wc -l # 0

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.