Virtual network with Open vSwitch

The Ostseepinguin banner showing a baltic penguin on the beach.

Virtual network with Open vSwitch

In this project, I want to show, how to designed and implemented a virtual network using Openvswitch and network namespaces under Linux. By leveraging virtual Ethernet tunnels and a central virtual switch, we create a flexible and scalable network environment. My main goal with this - besides understanding Linux namespaces better – is to have an isolated network environment. As a teacher I sometimes want to show my students how a single network protocol works, or what happens when I ping a domain. However even in a virtual machine there is often are many other network connection running in the background that it is difficult to filter to what I want I'm interested in. In that setup I create here the only noise in the virtual network is IPv6 Router Solicitation.

The plan

The following diagram illustrates a virtual network setup I want to achieve: The virtual Switch serves as the central point, connecting three distinct network namespaces represented by red, green, and blue. Each namespace is allocated an IP address (eth0-r, eth0-g, eth0-b) within the 10.0.0.0/24 subnet and is connected to the vSwitch via virtual Ethernet interfaces (veth-r, veth-g, veth-b).

Network Diagram showing the relationship between the namespaces as described in the paragraph above.

Figure 1: Network Diagram

Create functions for each name space

To begin with we define two variables to setup the intended namespaces and there associated color. The first variable is a simple index list, with the name red, green, and blue. The second variable is create with the bash-builtin declare with the option -x for export and -A for associated list. Here we associate escape codes for the color output in the terminal with the colorful namespace names. We put this definition in the file files/ovs_setup.sh.

export namespaces=(red green blue)
declare -xA colorlist=(red '\e[31m'
                      green '\e[32m'
                      orange '\e[33m'
                      blue '\e[34m'
                      magenta '\e[35m'
                      cyan '\e[36m')

We source this file in the second script files/ovs.sh to which we add all the following code. So we can easily recreate and even adopt this setup in the future. Within this script we first create a convenience function netns(). It takes a namespace name and the commands to be run in the namespace as input and excutes the command in the assigned namespace while color coding the output in the color associated with the name space. This way later on, we can easily see in which namespace a command was run.

script_dir="$(dirname ${BASH_SOURCE[0]})"
. ${script_dir}/ovs_setup.sh
function netns () {
    color=$1
    shift
    args=$@
    if [[ ${args[0]} = "bash" ]]
    then
        echo "To risky for my taste"
    else
        if [[ $(ip netns list | grep -o ${color}) == ${color} ]]
        then
            echo -ne ${colorlist[$color]}
            sudo ip netns exec ${color} ${args[@]}
            echo -ne '\e[0m'
        else
            echo "namespace ${color} does not exist"
        fi
    fi
    }

For even more convenience we create aliases for each namespace, so that a simple red, green or blue followed by the command will call the netns function for each of these namespaces.

for ns in ${namespaces[@]}
do
    alias ${ns}="netns ${ns}" && alias ${ns} && export ${ns} 
done

Create namespaces

The next step is initializing the three network namespaces named red, green, and blue using the ip netns add command. I use a for loop to iterate over the namespace names. So it's easy to add new namespaces later on, if needed. Where needed I refer to them with the ${ns} variable.

for ns in ${namespaces[@]}
do
    if [[ ! $(ip netns list | grep -o ${ns}) == ${ns} ]]
    then
        sudo ip netns add ${ns}
        echo "${ns} namespace added."
    fi
done
ip netns list
sleep 1

Subsequently, the loopback interface within each namespace is brought up using the ip l dev lo up command to enable local communication within the namespace.

for ns in ${namespaces[@]}
do
  netns ${ns} ip link set lo up
    echo "Loopback in ${ns} is up."
done

Finally, we can check the current state of network interfaces within each namespace using the ip l command.

for ns in ${namespaces[@]}
do
  netns ${ns} ip link
done

Create tunnels

Now we create virtual Ethernet tunnels (veth) named veth-r, veth-g, and veth-b, each paired with an interface in its respective network namespace (eth0-r, eth0-g, eth0-b) for communication. These tunnels facilitate communication between the vSwitch and the network namespaces. Note that in the device names, I only use the first letter to distinguish the namespaces. For this I use bash's variable substitution mechanism with ${ns::1}, I get just the first letter of each string. Be aware, that this is a bash feature, when your using another shell.

for ns in ${namespaces[@]}
do
    sudo ip link add veth-${ns::1} type veth peer eth0-${ns::1}
    echo "Linked veth-${ns} to eth0-${ns}."
done

Place link in namespaces

After creating the tunnels, the next step is to assign the interfaces to their respective namespaces. This is achieved by associating each tunnel's endpoint (eth0-r, eth0-g, eth0-b) with its corresponding namespace (red, green, blue).

for ns in ${namespaces[@]}
do
    sudo ip link set eth0-${ns::1} netns ${ns}
done

Add IPv4-Addresses

Finally, IPv4 addresses are assigned to the interfaces within each namespace to enable network communication. The IP addresses 10.0.0.2, 10.0.0.3, and 10.0.0.4 with a /24 subnet mask are allocated to eth0-r, eth0-g, and eth0-b interfaces, respectively. Additionally, the interfaces are brought up to activate the network configuration.

ip=1
for ns in ${namespaces[@]}
do
    ip=$((ip+1))
  netns ${ns} ip address add 10.0.0.${ip}/24 dev eth0-${ns::1}
  netns ${ns} ip link set dev eth0-${ns:0:1} up
    echo "Add IP 10.0.0.${ip} to eth0-${ns::1}."
done

Open virtual switch

To set up the virtual switch, first, the Openvswitch package is installed using the appropriate package manager, followed by starting the ovs-vswitchd service to manage the switch.

Install

if [ -f /etc/os-release ]; then
    . /etc/os-release
else
    echo "Cannot determine the Linux distribution."
    exit 1
fi
case $ID_LIKE in
    debian|ubuntu)
        sudo apt install -y openvswitch-switch
        ;;
    fedora|rhel|centos)
        sudo yum install -y openvswitch
        ;;
    suse)
        sudo zypper install -y openvswitch
        ;;
    arch)
        sudo pacman -Syu openvswitch
        ;;
    *)
        echo "Unsupported distribution."
        exit 1
        ;;
esac
if  [[ $ID == arch ]]; then
    sudo pacman -Syu openvswitch
else
    echo "Unsupported distribution."
    exit 1
fi
sudo systemctl start ovs-vswitchd.service
echo "Started ovs-vswitchd"

Add Switch

After installation, I create a virtual switch named SW1 using the ovs-vsctl add-br command.

sudo ovs-vsctl add-br SW1

The switch configuration is displayed using ovs-vsctl show.

sudo ovs-vsctl show

Add ports

Next, we add ports to the virtual switch to connect it with the network namespaces. Each port is associated with a corresponding tunnel interface (veth-r, veth-g, veth-b).

for ns in {r,g,b}
do
    sudo ovs-vsctl add-port SW1 veth-${ns}
    echo "Added veth-${ns} to SW1."
done

Again we can confirm everything has work as intended with:

sudo ovs-vsctl show

These ports must now be activated.

for ns in {r,g,b}
do
    sudo ip link set veth-${ns} up
    echo "Link veth-{ns} is up."
done

I always like to double check. So I grep for the veth interface and use the -A option to always show the first 3 lines of each interface configuration.

sudo ip a | grep veth -A3

Cleanup

A reboot will always clean most of the mess behind us, but I want to be a good child and clean up for my self, so I created another script to do exactly this.

script_dir="$(dirname ${BASH_SOURCE[0]})"
#Source Setup
. ${script_dir}/ovs_setup.sh

# Create a list of target interfaces
for ns in ${namespaces[@]}
do
    target_interfaces=(${target_interfaces[@]} veth-${ns::1})
done

# Check for existing namespaces
if [[ -n $(ip netns list) ]]; then
  echo "Removing network namespaces..."

  # Loop through each namespace and remove only matching ones
  for ns in $(ip netns list | awk '{print $1}'); do
    if [[ "${namespaces[@]}" =~ "$ns" ]]; then
      sudo ip netns del $ns
      echo "Removed namespace: $ns"
    fi
  done
else
  echo "No network namespaces found."
fi

# Check for existing veth interfaces
if [[ $(ip link show | grep veth -c) -gt 0 ]]; then
  echo "Removing veth interfaces..."

  # Loop through each veth interface and remove only matching ones
  for veth in $(ip link show | grep veth | awk '{print $2}' |sed 's/@.*$//g'); do
    if [[ "${target_interfaces[@]}" =~ "$veth" ]]; then
      sudo ip link del $veth
      echo "Removed interface: $veth"
    fi
  done
else
  echo "No veth interfaces found."
fi

echo "Stopping and disabling Open vSwitch..."
sudo systemctl stop ovs-vswitchd.service

# check if named run directory for blue namespace exitsts
if [[ -d $(ls -d blue_named_run_?????) ]]; then 
    rm -rf blue_named_run_?????
    echo "Removed run directory for named service in blue".
fi

for ns in ${namespaces[@]}
do
    if [[ $(alias ${ns}) ]] ; then
        unalias ${ns}
        "Unaliased ${ns}."
    fi
done


echo "Unseting netns function and variables"
unset -f netns
unset namespaces
unset colorlist

echo "Cleanup complete!"

Conclusion

It is surprisingly easy to create this basic setup.

We can now go and explore the different name spaces, by calling network related command with the red(), blue() and green() functions like:

sudo blue ip route
sudo red ping 10.0.0.3 -c 5
sudo green nstat -s | grep -i icmp

This is a great playground to explore basic network functionality.

Acknowledgment

Date: 2024-02-24 Sa 00:00

Author: Sebastian Meisel

Created: 2025-06-06 Fr 20:06

Validate