Linux Network Namespaces with ip netns and veth Pairs - 夜莺博客

Linux Network Namespaces with ip netns and veth Pairs

Network namespaces are the isolation primitive behind containers and network virtualization: each namespace gets its own interfaces, routing table, ARP table and firewall state. With ip netns and veth pairs you can build small virtual networks entirely in software - two namespaces chatting over a virtual cable, or several namespaces bridged like real hosts - which makes netns the perfect sandbox for testing routing, filtering and forwarding ideas without touching production. This guide builds both topologies step by step.

Why Network Namespaces Exist

A network namespace is a complete, independent copy of the kernel's network stack. Inside it you get your own set of interfaces (including a private lo), a private routing table, a private ARP/neighbour table, private socket tables, private iptables/nftables rules, and private /proc/net counters. Two namespaces can both own eth0 with the same address range without conflicting, because the kernel resolves interface names relative to the namespace of the calling process.

This is the mechanism that makes containers work. When Docker starts a container it does roughly this: create a new netns, create a veth pair, put one end in the container and one end on a host bridge, assign an address inside, and add a default route via the bridge. Everything else - overlay networks, service meshes, VPN namespaces, WireGuard sidecars - is built on top of the same primitive.

You need CAP_NET_ADMIN in the current namespace to do almost any of it, so run the commands below as root or with sudo.

Creating Namespaces and a veth Pair

sudo ip netns add client
sudo ip netns add server
ip netns list

Creating a namespace also mounts a persistent reference at /var/run/netns/<name>. That mount is what keeps the namespace alive: if you only unshare into a new namespace from a shell, it disappears when the last process exits, but ip netns add keeps it around until you delete it. You can verify with ls /var/run/netns/ and run commands in a shell inside a namespace with sudo ip netns pids client to see which processes belong to it.

A veth pair is a virtual cable: packets entering one end come out the other. Create the pair, then move each end into its namespace:

sudo ip link add veth-client type veth peer name veth-server
sudo ip link set veth-client netns client
sudo ip link set veth-server netns server

Two details matter here. First, a veth pair is created in the current namespace and both ends must be moved; the moment you move one end the pairing is preserved, so the two ends no longer appear together. Second, a veth end has no address and is administratively down by default - nothing works until you bring it up and assign an address.

Confirm the state of each half from its owning namespace:

sudo ip netns exec client ip link show
sudo ip netns exec server ip link show

Configuring the Point-to-Point Link

Bring up loopback and the veth end inside each namespace, assign addresses, and test:

sudo ip netns exec client ip link set lo up
sudo ip netns exec client ip link set veth-client up
sudo ip netns exec client ip addr add 10.10.10.1/24 dev veth-client

sudo ip netns exec server ip link set lo up
sudo ip netns exec server ip link set veth-server up
sudo ip netns exec server ip addr add 10.10.10.2/24 dev veth-server

sudo ip netns exec client ping 10.10.10.2

ip netns exec <ns> <command> runs any command inside the namespace - the same trick works for ip route, tcpdump or an application you want to isolate.

Because both ends are in the same /24, the kernel installs a connected route in each namespace automatically and the two hosts discover each other by ARP over the veth link. Inspect that state with:

sudo ip netns exec client ip route show
sudo ip netns exec client ip neigh show
sudo ip netns exec client ip -s link show veth-client

A frequent gotcha: a veth end in the host namespace that is left down will silently black-hole traffic, and because ARP resolution fails rather than erroring, you see "no route to host" or a hung connection instead of a clear message. Always verify state UP on both halves.

Connecting Namespaces Through a Bridge

To give many namespaces shared connectivity (the container-network model), create a Linux bridge in the default namespace and attach one end of each veth pair to it:

sudo ip netns add ns1
sudo ip netns add ns2
sudo ip link add br0 type bridge
sudo ip link set br0 up

sudo ip link add veth1 type veth peer name br-veth1
sudo ip link set veth1 netns ns1
sudo ip link set br-veth1 master br0
sudo ip link set br-veth1 up

sudo ip link add veth2 type veth peer name br-veth2
sudo ip link set veth2 netns ns2
sudo ip link set br-veth2 master br0
sudo ip link set br-veth2 up

sudo ip netns exec ns1 ip addr add 10.10.10.1/24 dev veth1
sudo ip netns exec ns1 ip link set veth1 up
sudo ip netns exec ns2 ip addr add 10.10.10.2/24 dev veth2
sudo ip netns exec ns2 ip link set veth2 up

Give the bridge itself an address in the same subnet if the namespaces must also reach the host, and note that the bridge approach is exactly what Docker and Open vSwitch automate - our Docker networking drivers guide shows the production version of this pattern.

sudo ip addr add 10.10.10.254/24 dev br0
sudo ip netns exec ns1 ping 10.10.10.2
bridge link show
bridge fdb show br0

The bridge is a learning switch: it floods unknown unicast, learns MAC addresses from source frames, and forwards between member ports. All of that happens in the host namespace, which is why a namespace attached to a bridge can talk to every other namespace on that bridge but nothing else unless you add routing or NAT. To let namespaces reach the outside world, enable forwarding and add a masquerade rule:

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o eth0 -j MASQUERADE
sudo ip netns exec ns1 ip route add default via 10.10.10.254

Namespaces and VLANs or VXLAN

Once you have a bridge, the same plumbing extends to tagging and tunnelling. A VLAN sub-interface created inside the host namespace and enslaved to br0 gives the namespaces an 802.1Q-tagged uplink, which is useful when you want to emulate several isolated tenants on one physical NIC - see Linux VLAN tagging with ip link for the tag syntax. If you would rather emulate the overlay involved, create a VXLAN interface as the bridge uplink instead; Linux VXLAN configuration with ip link covers the static VTEP setup and the port/FDB expectations.

sudo ip link add link eth0 name eth0.100 type vlan id 100
sudo ip link set eth0.100 master br0
sudo ip link set eth0.100 up

Use Cases and Cleanup

  • Test routing policies safely - combine namespaces with ip rule policy routing to verify table selection before production.
  • Simulate multi-host topologies on one machine for OSPF/BGP labs.
  • Sandbox firewalls (nftables rules per namespace) and traffic-shaping experiments.
  • Understand containers: a container is mostly a set of namespaces - see Docker's bridge/macvlan drivers and 802.1Q VLAN tagging for the tagging angle when namespaces need VLAN separation.

Two more patterns worth knowing. First, running a service inside a namespace is just ip netns exec plus the daemon: sudo ip netns exec server python3 -m http.server 8080 gives you a web server that no other namespace or the host can reach unless you route to it. Second, ip netns exec is also how you give a single process a private network stack without a container runtime, which is handy for untrusted code or for reproducing a customer topology locally.

For larger labs, containerlab wraps exactly these operations and lets you describe the whole topology in YAML - our Containerlab guide walks through the multi-vendor version, and Linux network namespaces and veth pairs in practice collects the day-to-day recipes.

sudo ip netns exec ns1 ip link del veth1     # deleting one end removes the pair
sudo ip netns del ns1
sudo ip link del br0

Cleanup order matters: delete the namespace last, because deleting a namespace destroys every interface inside it and returns the peer interfaces in other namespaces to a dangling state. If a namespace refuses to disappear, a process is still attached - find it with ip netns pids <ns> and kill it, or unmount the reference with umount /var/run/netns/<ns>.

Troubleshooting Checklist

  • No route to host: the peer veth end is down or unaddressed. Check ip -br link inside both namespaces.
  • Ping works but TCP does not: usually an MTU problem on the veth uplink, or a firewall rule in the wrong namespace. Remember iptables rules apply per namespace, so the host rules do not automatically filter namespace traffic.
  • Namespaces cannot reach the internet: missing default route, missing ip_forward, or a missing masquerade rule - in that order.
  • Traffic works one way only: asymmetric routing, typically a stray default route inside one namespace.
  • Performance looks terrible: veth pairs are fast but still add a copy; bonded or TSO-heavy workloads may want macvlan instead, which puts the namespace directly on the wire with minimal host involvement.

Per-Namespace DNS and Configuration Files

Some tools read global configuration files that know nothing about namespaces. The kernel gives you a clean escape hatch: any file placed under /etc/netns/<name>/ shadows the global file of the same name for processes running in that namespace. This is the standard way to give a namespace its own resolver configuration:

sudo mkdir -p /etc/netns/server
echo 'nameserver 10.10.10.53' | sudo tee /etc/netns/server/resolv.conf
sudo ip netns exec server cat /etc/resolv.conf

The same trick works for /etc/netns/<name>/hosts and for any other file libc would otherwise resolve globally. Without it, a namespace inherits the host's resolver and you will chase phantom DNS failures during a lab.

macvlan and ipvlan: When a Bridge Is Not Enough

veth plus bridge is the default answer, but it has one structural cost: every packet is copied between the namespace and the host bridge, and the host becomes part of the forwarding path. Where you want a namespace to look like a real host directly on the wire, use a macvlan interface instead. It is created in the host namespace and moved just like a veth end, but it gets its own MAC address and speaks directly to the upstream switch:

sudo ip link add link eth0 name macv0 type macvlan mode bridge
sudo ip link set macv0 netns ns1
sudo ip netns exec ns1 ip addr add 192.168.2.50/24 dev macv0
sudo ip netns exec ns1 ip link set macv0 up
sudo ip netns exec ns1 ip route add default via 192.168.2.1

Two caveats dominate macvlan deployments. First, many wireless drivers and some virtual switches refuse macvlan, or silently drop traffic, because they expect one MAC per port. Second, the host itself cannot talk to a macvlan child by default - a quirk that is fixed with a macvlan shim interface in the host namespace. Both behaviours are worth reproducing in a namespace before you commit a production design to them.

Namespaces as a Security Boundary

Isolating a service in a namespace narrows its blast radius: it can no longer sniff host interfaces, no longer bind to a host port by accident, and no longer reach management networks unless you route them. What it does not do is build a sandbox. Network namespaces do not isolate the filesystem, the process table or the user database, and a process with CAP_SYS_ADMIN in the host namespace can move itself out of any namespace. Treat netns as one axis of isolation and pair it with mount, PID and user namespaces - which is precisely what a container runtime does on your behalf.

原文链接:https://www.redhat.com/en/blog/net-namespaces