Namespaces & Virtual Interfaces
The kernel primitives that make container networking possible
Network Namespaces: Fully Isolated Network Stacks
A network namespace is a kernel construct that provides a complete, independent copy of the entire network stack. Each namespace has its own:
Per-Namespace Resources
- Network interfaces (lo, eth0, etc.)
- Routing table (separate
ip route) - iptables/nftables rules (separate firewall)
- Socket bindings (port 80 in ns1 != port 80 in ns2)
- ARP/NDP tables
- /proc/net view
Where Namespaces Are Used
- Docker containers — each container gets one
- Kubernetes pods — all containers in a pod share one
- Network testing — simulate multi-host topologies on one machine
- VRF isolation — separate routing domains
- systemd-networkd — isolates network services
Note
Default namespace: The host itself runs in the initial (default) network namespace. When you run ip addr on a host, you see the default namespace's interfaces. Every process inherits its parent's namespace unless explicitly moved.
Creating and Using Namespaces
# Create a namespace
$ ip netns add ns1
# List all namespaces
$ ip netns list
ns1
# Execute a command inside the namespace
$ ip netns exec ns1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# Note: loopback is DOWN by default in new namespaces
$ ip netns exec ns1 ip link set lo up
# Enter namespace interactively (spawns a shell inside ns1)
$ ip netns exec ns1 bash
# Delete a namespace
$ ip netns del ns1
Tip
Tip: ip netns exec uses setns(2) + unshare(2) under the hood. Docker uses the same syscalls but manages namespace lifecycle via containerd/runc. You can inspect a container's namespace with nsenter -t <PID> -n.
veth Pairs: Virtual Ethernet Cables
A veth pair is two virtual network interfaces connected like a pipe — whatever goes in one end comes out the other. They are the fundamental mechanism for connecting network namespaces together.
Creating a veth Pair
# Create veth pair (both ends start in the default namespace)
$ ip link add veth0 type veth peer name veth1
# Move one end into a namespace
$ ip link set veth1 netns ns1
# Assign IPs and bring up interfaces
$ ip addr add 10.0.0.1/24 dev veth0
$ ip link set veth0 up
$ ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth1
$ ip netns exec ns1 ip link set veth1 up
# Test connectivity
$ ping -c 1 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.043 ms
Key Properties of veth Pairs
- Always created in pairs — you cannot have one end without the other
- Deleting one end automatically destroys the other
- They operate at L2 — Ethernet frames pass between the two ends
- Each end can be in a different namespace
- Performance is near line-rate — no kernel-to-userspace copy
- Docker creates a veth pair for every container: one end in the container ns, the other attached to
docker0bridge
Linux Bridges: L2 Switch in Software
A Linux bridge is a software Layer 2 switch. It learns MAC addresses from incoming frames and forwards traffic between attached interfaces — exactly like a physical Ethernet switch, but implemented in the kernel.
Creating and Using a Bridge
# Create a bridge
$ ip link add br0 type bridge
$ ip link set br0 up
# Assign an IP to the bridge (acts as default gateway for containers)
$ ip addr add 172.17.0.1/24 dev br0
# Attach veth ends to the bridge
$ ip link set vethA master br0
$ ip link set vethB master br0
# View bridge members
$ bridge link show
3: vethA: <BROADCAST,MULTICAST,UP> mtu 1500 master br0
4: vethB: <BROADCAST,MULTICAST,UP> mtu 1500 master br0
# View learned MAC addresses
$ bridge fdb show br br0
How Bridge Forwarding Works
- Learning: When a frame arrives on a port, the bridge records the source MAC → port mapping
- Flooding: If the destination MAC is unknown, the frame is sent to all ports (except the source)
- Forwarding: If the destination MAC is known, the frame is sent only to the correct port
- This is identical behavior to a physical L2 switch
Note
Docker's docker0: When Docker starts, it creates a bridge called docker0 (default: 172.17.0.1/16). Each container's veth pair has one end attached to this bridge. That's how containers on the same Docker network communicate at L2.
tun/tap Devices
tun and tap are virtual network interfaces where the "other end" is a userspace program instead of a physical wire or another namespace. The program reads/writes packets via a file descriptor (/dev/net/tun).
- Carries Ethernet frames
- Appears as a full Ethernet interface (has a MAC address)
- Used by: VM hypervisors (QEMU/KVM), OpenVPN in tap mode, bridged networking
- Userspace program receives raw L2 frames
- Can be attached to a bridge
- Carries IP packets
- No Ethernet framing — point-to-point
- Used by: WireGuard, OpenVPN in tun mode, VPN tunnels
- Userspace program receives raw IP packets
- Cannot be bridged (no L2 headers)
Application
sends data to dest IP
Kernel Routing
routes to tun0/tap0
tun/tap Device
kernel → userspace fd
VPN Process
encrypts, encapsulates
Physical NIC
sends encrypted packet
# Create a tun device
$ ip tuntap add dev tun0 mode tun
$ ip addr add 10.8.0.1/24 dev tun0
$ ip link set tun0 up
# Create a tap device
$ ip tuntap add dev tap0 mode tap
$ ip link set tap0 up
$ ip link set tap0 master br0 # attach to bridge
macvlan / ipvlan
Both macvlan and ipvlan create virtual sub-interfaces from a single physical NIC, allowing multiple "virtual NICs" to share one uplink — each appearing as a distinct network identity to the rest of the network.
- Each sub-interface gets its own unique MAC address
- To external network, each looks like a separate physical NIC
- Multiple modes: bridge, vepa, private, passthru
- Bridge mode: sub-interfaces can talk to each other directly
- Use case: give containers direct L2 network access without NAT
# Create macvlan sub-interface
ip link add macvlan0 link eth0 type macvlan mode bridge
ip addr add 192.168.1.100/24 dev macvlan0
ip link set macvlan0 up- All sub-interfaces share the same MAC address as the parent
- Distinguished by IP only — L3 multiplexing
- Two modes: L2 (acts like bridge) and L3 (acts like router)
- Works in environments that restrict multiple MACs (some cloud NICs, 802.1X)
- Use case: cloud VMs where provider limits MACs per interface
# Create ipvlan sub-interface
ip link add ipvlan0 link eth0 type ipvlan mode l2
ip addr add 192.168.1.101/24 dev ipvlan0
ip link set ipvlan0 up| Feature | macvlan | ipvlan |
|---|---|---|
| MAC address | Unique per sub-interface | Shared with parent |
| Isolation level | L2 (different MACs) | L3 (same MAC, different IPs) |
| Cloud compatibility | May not work (MAC restrictions) | Works everywhere |
| Parent ↔ child traffic | Cannot communicate directly | Cannot communicate directly |
| Performance | Excellent (no bridge overhead) | Excellent (no bridge overhead) |
| Docker driver | --driver macvlan |
--driver ipvlan |
Warning
Limitation: With both macvlan and ipvlan, the parent interface cannot communicate with its sub-interfaces. This is by kernel design. If you need host-to-container communication, add a separate macvlan/ipvlan sub-interface for the host or use a bridge instead.
Hands-on: Build Container Networking from Scratch
This walkthrough recreates what Docker and Kubernetes CNIs do under the hood: create isolated network namespaces and connect them through a bridge with NAT for external access.
Target Topology
| host routing + NAT
|
Step 1: Create Namespaces
# Create two namespaces (representing two containers)
$ ip netns add ns1
$ ip netns add ns2
# Bring up loopback in each
$ ip netns exec ns1 ip link set lo up
$ ip netns exec ns2 ip link set lo up
Step 2: Create the Bridge
# Create bridge and assign it an IP (acts as default gateway)
$ ip link add br0 type bridge
$ ip link set br0 up
$ ip addr add 10.200.0.1/24 dev br0
Step 3: Create veth Pairs and Connect
# Create veth pairs for ns1
$ ip link add veth-ns1 type veth peer name eth0-ns1
$ ip link set veth-ns1 master br0 # host end → bridge
$ ip link set veth-ns1 up
$ ip link set eth0-ns1 netns ns1 # container end → namespace
# Create veth pairs for ns2
$ ip link add veth-ns2 type veth peer name eth0-ns2
$ ip link set veth-ns2 master br0
$ ip link set veth-ns2 up
$ ip link set eth0-ns2 netns ns2
Step 4: Assign IPs Inside Namespaces
# Configure ns1
$ ip netns exec ns1 ip addr add 10.200.0.2/24 dev eth0-ns1
$ ip netns exec ns1 ip link set eth0-ns1 up
$ ip netns exec ns1 ip route add default via 10.200.0.1
# Configure ns2
$ ip netns exec ns2 ip addr add 10.200.0.3/24 dev eth0-ns2
$ ip netns exec ns2 ip link set eth0-ns2 up
$ ip netns exec ns2 ip route add default via 10.200.0.1
Step 5: Enable Routing + NAT
# Enable IP forwarding on the host
$ sysctl -w net.ipv4.ip_forward=1
# NAT outbound traffic from containers
$ iptables -t nat -A POSTROUTING -s 10.200.0.0/24 ! -o br0 -j MASQUERADE
# Allow forwarded traffic
$ iptables -A FORWARD -i br0 -j ACCEPT
$ iptables -A FORWARD -o br0 -j ACCEPT
Step 6: Verify Connectivity
# Container-to-container (via bridge, L2)
$ ip netns exec ns1 ping -c 2 10.200.0.3
PING 10.200.0.3 (10.200.0.3) 56(84) bytes of data.
64 bytes from 10.200.0.3: icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from 10.200.0.3: icmp_seq=2 ttl=64 time=0.038 ms
# Container-to-host (via bridge, L3)
$ ip netns exec ns1 ping -c 1 10.200.0.1
64 bytes from 10.200.0.1: icmp_seq=1 ttl=64 time=0.029 ms
# Container-to-internet (via host NAT)
$ ip netns exec ns1 ping -c 1 8.8.8.8
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=12.3 ms
Tip
That's it. You just built the same networking model that Docker uses. Docker's docker0 bridge, per-container veth pairs, and MASQUERADE rules are exactly this pattern. Kubernetes CNIs (Flannel, Calico, Cilium) build on this foundation with additional overlay/routing logic.
Summary: Virtual Interface Types
| Interface Type | Layer | Purpose | Primary Use Case |
|---|---|---|---|
veth pair |
L2 | Connect two namespaces | Container networking (Docker, K8s) |
bridge |
L2 | Software switch (connect multiple interfaces) | docker0, VM networking |
tun |
L3 | Userspace ↔ kernel IP packets | VPNs (WireGuard, OpenVPN tun) |
tap |
L2 | Userspace ↔ kernel Ethernet frames | VM networking, OpenVPN tap |
macvlan |
L2 | Multiple MACs on one physical NIC | Direct L2 container access |
ipvlan |
L3 | Multiple IPs, same MAC on one NIC | Cloud environments (MAC-restricted) |
How These Compose in Real Systems
Container
network namespace
veth pair
pipe between ns
bridge
L2 switching
iptables NAT
MASQUERADE
Physical NIC
internet access