Linux Network Namespaces and veth Pairs in Practice - 夜莺博客

Linux Network Namespaces and veth Pairs in Practice

Network namespaces are the kernel feature underneath containers, VPN clients and lab topologies. Each namespace gets its own interfaces, routing table, ARP table and firewall rules; a veth pair is a virtual cable with one end in each namespace. Once you can wire that by hand, container networking stops being magic and becomes debuggable. This guide builds a working two-namespace lab step by step.

What a Network Namespace Isolates

A network namespace is a complete, independent network stack: its own set of interfaces (including a private loopback), its own IPv4 and IPv6 routing tables, its own neighbor table, its own netfilter rules, its own socket accounting and its own /proc/net view. Processes inside a namespace see only that stack. Two namespaces on the same host can each have an interface called eth0 with the same address and never conflict, because they are addressing different tables.

The isolation does not stop at IP. Cheaper features that look similar - adding a second IP address, or a VLAN subinterface - share the routing table and firewall with the host. A namespace does not. That is exactly why container runtimes reach for namespaces when they need per-workload addressing, and why a namespace is the cleanest way to build a router or a two-host topology on a single machine.

Create Namespaces and a Cable

ip netns add blue
ip netns add red
ip link add veth-blue type veth peer name veth-red
ip link set veth-blue netns blue
ip link set veth-red netns red
ip netns list

A veth pair is bidirectional: whatever enters one side leaves the other, including broadcast traffic. Namespaces are visible under /var/run/netns/, and ip netns exec runs any command inside one.

Note the order of operations. You create the veth pair in the root namespace first, then move each end into its target namespace. You cannot create one end directly inside a namespace without a helper, which is why the move-then-configure pattern is the standard. Once moved, the interfaces disappear from the host's default ip link listing - they now belong to their namespaces.

Address and Bring Up

ip netns exec blue ip addr add 10.200.1.1/24 dev veth-blue
ip netns exec red  ip addr add 10.200.1.2/24 dev veth-red
ip netns exec blue ip link set veth-blue up
ip netns exec red  ip link set veth-red up
ip netns exec blue ip link set lo up
ip netns exec red  ip link set lo up
ip netns exec blue ping -c2 10.200.1.2

Loopback must be raised separately in every namespace - a surprisingly common cause of "the service is running but nothing can reach it" when a container's health check hits 127.0.0.1.

Also note the point-to-point nature of a raw veth pair. There is no switch in the middle, so both ends are in the same /24 and reach each other directly over the link. That is enough for the two-namespace case, but it does not scale: a third namespace cannot join this cable, because a veth is strictly a two-ended wire. For more than two peers you need a bridge, which is the next section.

Reach the Outside World

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

Give each namespace a default route and NAT the whole subnet on the host. To reach a service inside a namespace from the host, either publish a port with DNAT or give the namespace a bridge instead of a point-to-point veth - the same two models containers expose with host networking and bridged networking.

Enable forwarding first. Without net.ipv4.ip_forward=1, the host will drop the packets even though the routes and the NAT rule look correct. If you want the setting to survive a reboot, write it to /etc/sysctl.d/ rather than using the runtime sysctl -w shown above. And remember that the DNAT rule must be added in the root namespace's filter, not inside the target namespace, because the traffic is being redirected before it arrives.

Connecting Two Namespaces Through a Bridge

For more than two peers - or to mirror how a real container network is built - create a Linux bridge in the root namespace and attach veth ends to it. Each veth still has one end in the namespace, but the other end joins the bridge instead of pointing at a single peer.

ip link add br0 type bridge
ip link set br0 up
ip link add veth-blue type veth peer name veth-br-blue
ip link set veth-blue netns blue
ip link set veth-br-blue master br0
ip link set veth-br-blue up
ip addr add 10.200.1.254/24 dev br0

Now the bridge acts as the virtual switch, the host becomes the gateway on 10.200.1.254, and any number of namespaces can be attached the same way. This is exactly the model that bridge-backed container networks (Docker's default bridge, CNI's bridge plugin, Kubernetes default networking) use, which is why understanding it makes container networking diagnosis straightforward.

Debugging Inside a Namespace

ip netns exec blue ss -lntp
ip netns exec blue ip neigh show
ip netns exec blue tcpdump -ni veth-blue
ip netns pids blue
nsenter -t $(ip netns pids blue | head -1) -n ss -lntp

Because each namespace has its own tables, ss, ip route and iptables -L on the host tell you nothing about what a namespace sees. Always scope the command with ip netns exec before concluding "there is nothing listening". The host-side equivalents for ARP and neighbour state are covered in Linux ip neigh and ARP table commands.

The nsenter form is the bridge to the container world: given a PID, you can enter its network namespace without the ip netns machinery, which is how you inspect a running pod's stack when the namespace is not registered under /var/run/netns. That is the standard trick for debugging container networking on a host where the runtime keeps namespaces elsewhere.

Persistent Namespaces and Cleanup

Namespaces created with ip netns add persist until you delete them, which is convenient for labs but dangerous if you forget them. Delete a namespace and its veth ends are cleaned up automatically:

ip netns delete blue
ip netns delete red
ip link del br0

If a namespace is still in use, ip netns delete removes the bind mount but the kernel keeps the namespace alive until the last process in it exits - so check ip netns pids first if the deletion seems to have no effect. Adopt a naming convention such as veth-<ns>, and audit for orphans periodically, because a host carrying hundreds of abandoned namespaces and veth pairs is a slow and confusing failure rather than an obvious one.

A Worked Lab: Turn a Namespace into a Router

The reason namespaces are so useful for network engineering is that a namespace with forwarding enabled behaves like a router with its own routing table. The pattern is to place one namespace on the "inside" of a simulated network and one on the "outside", connect them with a veth pair, and let the middle namespace route between two of its interfaces just as a physical router would.

ip netns add inside
ip netns add outside
ip netns add core

# two cables, one end in core, one in each leaf
ip link add veth-in type veth peer name core-in
ip link add veth-out type veth peer name core-out
ip link set veth-in netns inside
ip link set veth-out netns outside
ip link set core-in netns core
ip link set core-out netns core

# addresses and forwarding inside the middle namespace
ip netns exec inside ip addr add 192.0.2.1/30 dev veth-in
ip netns exec outside ip addr add 198.51.100.1/30 dev veth-out
ip netns exec core ip addr add 192.0.2.2/30 dev core-in
ip netns exec core ip addr add 198.51.100.2/30 dev core-out
ip netns exec core sysctl -w net.ipv4.ip_forward=1

Now the core namespace routes between the two links, and any routing protocol or static route you add inside it changes only that namespace. This is precisely the model that container-lab tooling automates: each simulated node is a namespace, each link is a veth pair, and a small piece of orchestration wires them together from a topology file. Building one by hand once is what makes the tool's behaviour legible afterwards.

Namespaces and VPNs

WireGuard, OpenVPN and similar clients can run inside a namespace, which gives you a clean way to route only specific applications over a tunnel while everything else uses the host stack. The client's interface and routes live in the namespace; the host's default route is untouched. This "split tunnel without touching the host" pattern is common on workstations and on servers that need one tunneled workload and one direct workload at the same time.

ip netns add vpn
ip link add wg0 type wireguard
ip link set wg0 netns vpn
ip netns exec vpn ip addr add 10.9.0.2/32 dev wg0
ip netns exec vpn ip link set wg0 up
ip netns exec vpn ip route add default dev wg0

The same isolation works for the reverse case - a service that must never see the corporate network can be confined to a namespace whose only route is local, no matter what the host is connected to.

Automating the Lab Setup

Anything you type at the console for a two-namespace lab should be a script, because you will tear it down and rebuild it many times. Keep three idempotent functions: create (build namespaces and cables), configure (addresses, routes, forwarding) and destroy (delete namespaces). Guard each step so re-running the script does not fail on objects that already exist, and delete on exit so an interrupted run does not leave orphans behind.

ip netns list | grep -q '^blue$' || ip netns add blue
ip link show veth-blue &>/dev/null || ip link add veth-blue type veth peer name veth-red
ip netns delete blue 2>/dev/null; ip netns delete red 2>/dev/null

Namespaces are cheap, so prefer "destroy and rebuild" over "patch in place" for labs. It removes a whole class of "why is this state weird" questions, and it makes the topology reproducible for the next person who needs to debug it.

Where This Shows Up in Production

  • Container runtimes create exactly this pair for every pod, then plug the host end into a bridge or VXLAN - see Linux VXLAN VTEP configuration.
  • Multi-vendor network labs use namespace-backed containers as routers, described in Containerlab for multi-vendor labs.
  • Bonding and LACP behaviour at the host level interacts with namespace design; the modes are explained in Linux bonding modes with LACP.
  • Keep a naming convention (veth-<ns>) and delete stale namespaces - a host with hundreds of orphaned namespaces and veth pairs is a slow, confusing failure.
  • Routing between namespaces - rather than switching - is where a userspace routing suite earns its place; a Linux router built from namespaces and BGP is covered in FRRouting BGP on Linux.
  • Policy is per-namespace too, which is why Kubernetes network policy enforcement lives at the namespace boundary; see Kubernetes NetworkPolicy default-deny patterns.

原文链接:https://man7.org/linux/man-pages/man8/ip-netns.8.html