Container Networking Deep Dive Part 2: Two Namespaces on the Same Host

2024-04-14

Introduction

What Are We Building? 🧱

In this second part of the series, we connect two network namespaces (netns1, netns2) to the same virtual bridge on a single VM. This mirrors how Kubernetes pods share a flat L2 network on a single node.

Why This Matters

This setup is the basis of many container networks. It replicates the behavior of a Linux bridge or a virtual switch (like what Docker and most CNIs do).

Scenario Overview

  • Two namespaces: netns1, netns2
  • Each has a veth interface (veth11, veth21)
  • The host side of the veth pairs (veth10, veth20) is connected to a Linux bridge br0
  • The bridge has IP 172.16.0.1
  • netns1 gets 172.16.0.2, netns2 gets 172.16.0.3
  • All interfaces are on the same /24 subnet

Architecture

cns2

Setup

We'll use the same setup script as in the previous part.

NAME=scenario2
IMAGE=22.04

up:
	@if multipass info $(NAME) >/dev/null 2>&1; then \
		echo "$(NAME) already exists. Skipping launch."; \
	else \
		echo "Launching $(NAME)..."; \
		multipass launch --name $(NAME) --memory 1G --disk 5G; \
	fi
	@chmod +x env.sh setup.sh test.sh
	@echo "Transferring files..."
	@for f in env.sh setup.sh test.sh; do \
		multipass transfer $$f $(NAME):/home/ubuntu/; \
	done

Now run the VM setup.

make up

env.sh contains the variables for the scenario.

CON1="netns1"
CON2="netns2"
NODE_IP="10.0.0.10"
BRIDGE_IP="172.16.0.1"
IP1="172.16.0.2"
IP2="172.16.0.3"

Creating the Namespaces & Interfaces

setup.sh creates the namespaces and interfaces.

#!/bin/bash -e
. /home/ubuntu/env.sh

sudo ip netns add $CON1
sudo ip netns add $CON2

sudo ip link add veth10 type veth peer name veth11
sudo ip link add veth20 type veth peer name veth21

sudo ip link set veth11 netns $CON1
sudo ip link set veth21 netns $CON2

sudo ip netns exec $CON1 ip addr add $IP1/24 dev veth11
sudo ip netns exec $CON2 ip addr add $IP2/24 dev veth21

sudo ip netns exec $CON1 ip link set veth11 up
sudo ip netns exec $CON2 ip link set veth21 up

Connecting via Bridge

sudo ip link add name br0 type bridge
sudo ip link set veth10 master br0
sudo ip link set veth20 master br0
sudo ip addr add $BRIDGE_IP/24 dev br0

Bridging everything together

sudo ip link set br0 up
sudo ip link set veth10 up
sudo ip link set veth20 up
sudo ip netns exec $CON1 ip link set lo up
sudo ip netns exec $CON2 ip link set lo up

Routing configuration

sudo ip netns exec $CON1 ip route add default via $BRIDGE_IP dev veth11
sudo ip netns exec $CON2 ip route add default via $BRIDGE_IP dev veth21

Manual testing

Conditions to test:

  • Ping from netns1 to netns2
  • Ping from netns2 to netns1
  • Ping from host to netns1 & host to netns2
  • Ping from netns1 to host & netns2 to host.

AutomatedTesting

test.sh contains the tests for the scenario.

#!/bin/bash
. /home/ubuntu/env.sh

check() {
  echo "[TEST] $1"
  eval "$2"
  echo ""
}

fail_if_empty() {
  if [ -z "$($1)" ]; then
    echo "[FAIL] $2"
    exit 1
  else
    echo "[PASS] $2"
  fi
}

fail_if_empty "sudo ip netns list | grep $CON1" "Namespace $CON1 exists"
fail_if_empty "sudo ip netns list | grep $CON2" "Namespace $CON2 exists"

check "IP in $CON1" "sudo ip netns exec $CON1 ip a show dev veth11"
check "IP in $CON2" "sudo ip netns exec $CON2 ip a show dev veth21"
check "Ping $IP2 from $CON1" "sudo ip netns exec $CON1 ping -c 3 $IP2"
check "Ping $IP1 from $CON2" "sudo ip netns exec $CON2 ping -c 3 $IP1"
check "Ping bridge from $CON1" "sudo ip netns exec $CON1 ping -c 3 $BRIDGE_IP"
check "Ping bridge from $CON2" "sudo ip netns exec $CON2 ping -c 3 $BRIDGE_IP"
check "Bridge info" "ip a show br0"

Run with make test.

Makefile workflow

run:
	multipass exec $(NAME) -- bash /home/ubuntu/setup.sh
test:
	multipass exec $(NAME) -- bash /home/ubuntu/test.sh
shell:
	multipass shell $(NAME)
destroy:
	multipass delete $(NAME) --purge || echo "Nothing to destroy."
make run # Run the setup script
make test # Run the test script
make shell # Drop into the VM
make destroy # Clean up

Key Takeaways

  • A Linux bridge behaves like a virtual switch
  • All devices on the same bridge can talk L2 directly
  • Namespaces need explicit routing (default route via bridge IP)
  • Debugging tools like ip a, ip r, ip link help massively when it comes to networking & troubleshooting.

Conclusion

You now have a working L2 network across multiple isolated namespaces. This is the basis for pod networking on a single node.

Next up in Part 3: we’ll span this setup across two VMs, and show how to keep everything on the same subnet using a shared L2 segment or VXLAN overlay.

Related Posts