home

Network Namespaces For Fun And Profit

Jul 2021

Network namespaces are a copy of the network stack that can run inside of your machine. They have all sorts of interesting technical reasons for existing. They are also excellent tools for prototyping networking applications.

Here we’ll walk through creating two namespaces on a Linux machine and sending traffic through them. We’ll then conclude with an example of using network namespaces to test the performance of a simple TCP proxy.

Topology

We’ll start by creating a topology that looks something like this:


    left                                 right
  +----------------+                   +----------------+
  |                |     bridge        |                |
  |   10.0.1.1/24  |        |          |   10.0.0.1/24  |
  |            +---+        |          +----+           |
  |            |   +--------+----------+    |           |
  |            +---+        |          +----+           |
  |                |        |          |                |
  |                |        |          |                |
  +----------------+                   +----------------+

Here we have two namespaces, left and right, connected by veths to a bridge. You can think of a veth as a virtual Ethernet cable and a bridge as a virtual switch. One way to imagine this setup is two computers plugged into an Ethernet switch.

Each namespace is responsible for the CIDR 10.0.1.1/24 and 10.0.0.1/24 respectively. This means that they are responsible for traffic destined for those CIDRS and personally have the addresses 10.0.1.1 and 10.0.0.1 respectively.

By the end of this our goal is that right should be able to ping left by running ping 10.0.1.1.

Implementation

In the interest of having good form we’ll organize our shell script to create these namespaces into two functions setup and teardown. The setup function will be responsible for creating the topology and the teardown function for destroying it.

Outline

We can start by creating an outline of this script as follows:

#!/bin/bash

function setup() {
    echo "setup"
}

function teardown() {
    echo "teardown"
}

case $1 in
    up)
    setup
    ;;
    down)
    teardown
    ;;
    *)
    echo "usage: $0 <up/down>"
    ;;
esac

We can now invoke our script as follows:

$ bash demo.sh
usage: demo.sh <up/down>
$ bash demo.sh up
setup
$ bash demo.sh down
teardown

Creating namespaces

Now we can start filling in our setup function and creating some namespaces.

We can create our namespaces as follows:

ip netns add left
ip netns add right

With namespaces on the system we can inspect them by running ip netns exec left bash. Let’s enter the left namespace and look at what links have been configured.

$ sudo ip netns exec left bash
$ ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: [email protected]: <NOARP> mtu 1452 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/gre 0.0.0.0 brd 0.0.0.0
3: [email protected]: <BROADCAST,MULTICAST> mtu 1462 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff
4: [email protected]: <BROADCAST,MULTICAST> mtu 1450 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

An astute observer will notice that the looback interface is not up. This can be verified by running ping 127.0.0.1 from inside the namespace.

Our first task inside the namespace is to fix that. Using the ip netns exec command every time we want to do something inside of a namespace is a little tedious so as an alternative we can use the ip -n command to execute an ip command inside of namespace.

For example, to set our loopback interfaces to up from the root namespace:

ip -n left link set lo up
ip -n right link set lo up

Now ping 127.0.0.1 works as expected. Nice.

Our setup function now looks like this:

function setup() {
    ip netns add left
    ip netns add right

    ip -n left link set lo up
    ip -n right link set lo up
}

Adding some logic to delete namespaces our teardown function looks like this:

function teardown() {
    ip netns del right
    ip netns del left
}

Creating some cables and plugging things in

We’ve now created our namespaces but they have no way of communicating with each other. Let’s start by creating some virtual Ethernet cables and plugging them into the namespaces.

Create them:

ip link add left_outer type veth peer name left_inner
ip link add right_outer type veth peer name right_inner

Plug them in:

ip link set left_inner netns left
ip link set right_inner netns right

Turn them on:

ip link set dev left_outer up
ip link set dev right_outer up

ip -n left link set dev left_inner up
ip -n right link set dev right_inner up

These commands may seem daunting but they’re really reasonably straightforward. Rather than try and explain the details and get something wrong, I’ll point you towards man ip-link if you’re curious about the technical details.

Now that we have our cables, let’s create our bridge.

ip link add br0 type bridge
ip link set br0 up

With our bridge created we can plug our Ethernet cables into it.

ip link set left_outer master br0
ip link set right_outer master br0

Nice.

Our setup and teardown functions now look like this:

function setup() {
    ip netns add left
    ip netns add right

    ip -n left link set lo up
    ip -n right link set lo up

    ip link add left_outer type veth peer name left_inner
    ip link add right_outer type veth peer name right_inner

    ip link set left_inner netns left
    ip link set right_inner netns right

    ip link set dev left_outer up
    ip link set dev right_outer up

    ip link add br0 type bridge
    ip link set br0 up

    ip link set left_outer master br0
    ip link set right_outer master br0
}
function teardown() {
    ip netns del right
    ip netns del left
    ip link del br0
}

Routing

At this point all of our cables are plugged in but trying to run a ping command from one of the namespaces to the other one will result in an error connect: Network is unreachable.

This might make some sense because we have said literally nothing about ip addresses thus far. Let’s go ahead and add some addresses to the Ethernet cables that we plugged into our namespaces.

ip -n left addr add 10.0.1.1/24 dev left_inner
ip -n right addr add 10.0.0.1/24 dev right_inner

With those addresses in place namespaces can ping their own addresses but can not reach the other namespace.

$ ip netns exec right bash
$ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.025 ms
$ ping 10.0.1.1
connect: Network is unreachable

The reason they can not reach each other is that we have yet to inform them that they can reach those addresses via their ethernet cables. We can tell them as such with some ip route commands.

ip -n left route add 10.0.0.0/24 dev left_inner
ip -n right route add 10.0.1.0/24 dev right_inner

With those routes in place our pings work!

$ sudo bash demo.sh up
$ sudo ip netns exec left bash
$ ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.052 ms
  C-c C-c
--- 10.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 9ms
rtt min/avg/max/mdev = 0.052/0.065/0.079/0.015 ms
$ ping 10.0.1.1
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.043 ms
  C-c C-c
--- 10.0.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 5ms
rtt min/avg/max/mdev = 0.023/0.033/0.043/0.010 ms

Nice!

Our final setup and teardown functions look like this:

function setup() {
    ip netns add left
    ip netns add right

    ip -n left link set lo up
    ip -n right link set lo up

    ip link add left_outer type veth peer name left_inner
    ip link add right_outer type veth peer name right_inner

    ip link set left_inner netns left
    ip link set right_inner netns right

    ip link set dev left_outer up
    ip link set dev right_outer up

    ip -n left link set dev left_inner up
    ip -n right link set dev right_inner up

    ip link add br0 type bridge
    ip link set br0 up

    ip link set left_outer master br0
    ip link set right_outer master br0

    ip -n left addr add 10.0.1.1/24 dev left_inner
    ip -n right addr add 10.0.0.1/24 dev right_inner

    ip -n left route add 10.0.0.0/24 dev left_inner
    ip -n right route add 10.0.1.0/24 dev right_inner
}
function teardown() {
    ip netns del right
    ip netns del left
    ip link del br0
}

Up next

With these tools in your toolbelt you can use network namespaces to prototype increasingly complex network topologies without using something heavy like docker containers or actual virtual machines.

For an example of this see this script that I use to benchmark the performance of a little TCP proxy.