Software-Defined Networking with Open vSwitch
Open vSwitch (OVS) is a production-quality multilayer virtual switch for Linux that supports OpenFlow, VLANs, GRE/VxLAN tunnels, and DPDK acceleration. This guide covers installing OVS, creating bridges, configuring VLANs, writing OpenFlow rules, setting up VxLAN tunnels, and monitoring traffic with port mirroring.
Prerequisites
- Ubuntu 20.04/22.04 or CentOS/Rocky Linux 8+
- Root or sudo access
- For DPDK: a NIC with DPDK driver support and hugepages configured
Install Open vSwitch
# Ubuntu/Debian
sudo apt update
sudo apt install -y openvswitch-switch openvswitch-common
# CentOS/Rocky Linux
sudo dnf install -y openvswitch libibverbs
sudo systemctl enable openvswitch
sudo systemctl start openvswitch
# Verify installation
sudo systemctl status openvswitch
ovs-vsctl --version
# Check OVS kernel module is loaded
lsmod | grep openvswitch
Create Bridges and Ports
OVS uses bridges (virtual switches) as the core networking construct:
# Create a bridge
sudo ovs-vsctl add-br ovs-br0
# Verify the bridge was created
sudo ovs-vsctl show
# Add a physical NIC to the bridge (the NIC loses its IP)
sudo ovs-vsctl add-port ovs-br0 eth1
# Assign IP to the bridge interface instead
sudo ip addr add 192.168.1.1/24 dev ovs-br0
sudo ip link set ovs-br0 up
# Add multiple ports
sudo ovs-vsctl add-port ovs-br0 eth2
sudo ovs-vsctl add-port ovs-br0 eth3
# List all bridges and ports
sudo ovs-vsctl list-br
sudo ovs-vsctl list-ports ovs-br0
# Add an internal port (virtual NIC on the host)
sudo ovs-vsctl add-port ovs-br0 internal0 -- set Interface internal0 type=internal
sudo ip addr add 10.0.0.1/24 dev internal0
sudo ip link set internal0 up
# Remove a port
sudo ovs-vsctl del-port ovs-br0 eth2
# Delete a bridge
sudo ovs-vsctl del-br ovs-br0
VLAN Configuration
# Configure a port as an access port (untagged, single VLAN)
sudo ovs-vsctl set port eth1 tag=100
# Configure a port as a trunk port (multiple tagged VLANs)
sudo ovs-vsctl set port eth2 trunks=100,200,300
# Remove VLAN tag (set back to access with no tag)
sudo ovs-vsctl clear port eth1 tag
# Create a VLAN subinterface (internal port per VLAN)
sudo ovs-vsctl add-port ovs-br0 vlan100 -- set Interface vlan100 type=internal
sudo ovs-vsctl set port vlan100 tag=100
sudo ip addr add 192.168.100.1/24 dev vlan100
sudo ip link set vlan100 up
sudo ovs-vsctl add-port ovs-br0 vlan200 -- set Interface vlan200 type=internal
sudo ovs-vsctl set port vlan200 tag=200
sudo ip addr add 192.168.200.1/24 dev vlan200
sudo ip link set vlan200 up
# Verify VLAN configuration
sudo ovs-vsctl list port eth1
sudo ovs-vsctl show
OpenFlow Rules
OpenFlow lets you program precise forwarding behavior:
# View the default flow table
sudo ovs-ofctl dump-flows ovs-br0
# Add a flow: forward traffic from port 1 to port 2
sudo ovs-ofctl add-flow ovs-br0 "in_port=1,actions=output:2"
# Add a flow matching on MAC address
sudo ovs-ofctl add-flow ovs-br0 \
"dl_dst=aa:bb:cc:dd:ee:ff,actions=output:3"
# Add a flow matching IP and TCP port (drop SSH from a subnet)
sudo ovs-ofctl add-flow ovs-br0 \
"priority=100,ip,nw_src=10.0.0.0/24,tcp,tp_dst=22,actions=drop"
# Allow established connections (stateless — use connection tracking for stateful)
sudo ovs-ofctl add-flow ovs-br0 \
"priority=50,ip,actions=normal"
# Connection tracking (requires Linux 4.3+ and OVS 2.5+)
# Allow new and established connections:
sudo ovs-ofctl add-flow ovs-br0 \
"priority=100,ct_state=-trk,ip,actions=ct(table=1)"
sudo ovs-ofctl add-flow ovs-br0 \
"table=1,ct_state=+trk+new,ip,actions=ct(commit),normal"
sudo ovs-ofctl add-flow ovs-br0 \
"table=1,ct_state=+trk+est,ip,actions=normal"
# VLAN-based forwarding: strip VLAN tag on egress
sudo ovs-ofctl add-flow ovs-br0 \
"in_port=1,dl_vlan=100,actions=strip_vlan,output:2"
# Delete all flows
sudo ovs-ofctl del-flows ovs-br0
# Delete a specific flow
sudo ovs-ofctl del-flows ovs-br0 "in_port=1"
GRE and VxLAN Tunnels
# Create a GRE tunnel between two OVS bridges
# On Host A (192.168.1.1):
sudo ovs-vsctl add-port ovs-br0 gre0 -- \
set interface gre0 type=gre options:remote_ip=192.168.1.2
# On Host B (192.168.1.2):
sudo ovs-vsctl add-port ovs-br0 gre0 -- \
set interface gre0 type=gre options:remote_ip=192.168.1.1
# Verify tunnel is up
sudo ovs-vsctl list interface gre0 | grep -E "type|options|status"
# Create a VxLAN tunnel (VNI 100)
# On Host A:
sudo ovs-vsctl add-port ovs-br0 vxlan0 -- \
set interface vxlan0 type=vxlan \
options:remote_ip=192.168.1.2 \
options:key=100 \
options:dst_port=4789
# On Host B:
sudo ovs-vsctl add-port ovs-br0 vxlan0 -- \
set interface vxlan0 type=vxlan \
options:remote_ip=192.168.1.1 \
options:key=100 \
options:dst_port=4789
# Verify VxLAN tunnel
sudo ovs-vsctl show
sudo ovs-ofctl show ovs-br0
# Test connectivity across the tunnel
ping -I ovs-br0 10.0.0.2 # Use IP configured on the remote bridge
Port Mirroring
Port mirroring copies traffic to a monitoring port for analysis:
# Mirror all traffic on eth1 to a monitoring interface
sudo ovs-vsctl add-port ovs-br0 mirror-port -- \
set interface mirror-port type=internal
# Create the mirror
sudo ovs-vsctl \
-- --id=@p get port eth1 \
-- --id=@m create mirror name=my-mirror select-all=true \
output-port=@p \
-- set bridge ovs-br0 mirrors=@m
# More precise mirror: only traffic to/from eth1
sudo ovs-vsctl \
-- --id=@eth1 get port eth1 \
-- --id=@mirror-port get port mirror-port \
-- --id=@m create mirror name=eth1-mirror \
select-src-port=@eth1 \
select-dst-port=@eth1 \
output-port=@mirror-port \
-- set bridge ovs-br0 mirrors=@m
# Capture mirrored traffic with tcpdump
sudo tcpdump -i mirror-port -n -v
# Remove mirrors
sudo ovs-vsctl clear bridge ovs-br0 mirrors
sudo ovs-vsctl del-port ovs-br0 mirror-port
DPDK Acceleration
DPDK provides kernel-bypass networking for maximum performance:
# Install DPDK libraries
sudo apt install -y dpdk dpdk-dev
# Configure hugepages (DPDK requirement)
echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
sudo mkdir -p /mnt/huge
sudo mount -t hugetlbfs nodev /mnt/huge
# Persist hugepages on reboot
echo "vm.nr_hugepages=1024" | sudo tee -a /etc/sysctl.conf
echo "nodev /mnt/huge hugetlbfs defaults 0 0" | sudo tee -a /etc/fstab
# Bind NIC to DPDK driver (replace PCI address)
sudo dpdk-devbind.py --bind=vfio-pci 0000:01:00.0
# Start OVS with DPDK support
sudo ovs-vsctl set Open_vSwitch . \
other_config:dpdk-init=true \
other_config:dpdk-socket-mem="1024,1024"
sudo systemctl restart openvswitch
# Create a DPDK bridge
sudo ovs-vsctl add-br dpdk-br -- \
set bridge dpdk-br datapath_type=netdev
# Add DPDK port
sudo ovs-vsctl add-port dpdk-br dpdk0 -- \
set interface dpdk0 type=dpdk \
options:dpdk-devargs=0000:01:00.0
sudo ovs-vsctl show
Troubleshooting
OVS service not starting:
sudo journalctl -u openvswitch -n 50
# Common cause: kernel module not loaded
sudo modprobe openvswitch
lsmod | grep openvswitch
Tunnel not passing traffic:
# Check tunnel interface status
sudo ovs-vsctl list interface gre0 | grep status
# Verify remote IP is reachable (underlay must work first)
ping 192.168.1.2
# Check OVS datapath for the tunnel
sudo ovs-dpctl show
sudo ovs-dpctl dump-flows
OpenFlow rules not matching:
# Dump packet trace through the flow table
sudo ovs-appctl ofproto/trace ovs-br0 "in_port=1,dl_src=aa:bb:cc:dd:ee:01,dl_dst=aa:bb:cc:dd:ee:02"
# Check flow table statistics (packet counts)
sudo ovs-ofctl dump-flows ovs-br0 | grep "n_packets"
Port not forwarding traffic:
# Verify the port is in a normal forwarding state
sudo ovs-ofctl show ovs-br0
# Check OVS bridge MAC learning table
sudo ovs-appctl fdb/show ovs-br0
Conclusion
Open vSwitch provides a flexible, programmable virtual networking layer for Linux that powers many cloud and NFV deployments. Its support for VLANs, OpenFlow, VxLAN, and DPDK makes it suitable for everything from simple host networking to large-scale SDN architectures. The ovs-vsctl and ovs-ofctl tools give you complete control over bridge configuration and flow programming, while integration with controllers like OpenDaylight or OVN enables centralized network management at scale.


