Open vSwitch Basics: Bridges, Access Ports and VLANs - 夜莺博客

Open vSwitch Basics: Bridges, Access Ports and VLANs

Open vSwitch (OVS) is the production-grade virtual switch behind most cloud platforms: it forwards frames in the kernel datapath, speaks OpenFlow, and exposes a configuration database you manage with ovs-vsctl. The OVS port model mirrors a physical switch — ports on a bridge are either trunks (pass tagged 802.1Q traffic) or access ports assigned one untagged VLAN. This guide creates a bridge, wires up access and trunk ports, adds a GRE tunnel port, and verifies the result.

If you have configured a physical switch, the concepts transfer directly; what changes is the command surface. Physical switches are configured imperatively, one line at a time. OVS is configured declaratively: you write desired state into the OVSDB, and the switch converges to match it. Understanding that distinction — and the transaction syntax that goes with it — is what makes ovs-vsctl stop looking cryptic.

How OVS Stores Configuration

Three components cooperate, and knowing which one to inspect tells you where to look when something is wrong:

  • ovs-vswitchd — the daemon that actually forwards packets, using the kernel datapath module (openvswitch) by default or DPDK userspace where configured.
  • ovsdb-server — stores configuration in the OVSDB and survives reboots, which is why an ovs-vsctl change persists without any "save" command.
  • ovs-vsctl — your interface to the OVSDB. Every command is a database transaction, committed atomically or not at all.
# Verify the stack is installed and running
ovs-vsctl show
ovs-vsctl -V
systemctl status openvswitch-switch
lsmod | grep openvswitch        # kernel datapath loaded?
ovs-dpctl show                  # datapath listing

Because the configuration lives in a database rather than a text file, editor-based workflows do not apply. There is no reload to forget: a successful ovs-vsctl command is already live. There is also no undo — which is why ovs-vsctl accepts --may-exist and --if-exists flags that make scripts idempotent, and why you should capture ovs-vsctl show output before making bulk changes.

Bridge and Port Model

$ ovs-vsctl add-br br0
$ ovs-vsctl add-port br0 eth0            # trunk port (default)
$ ovs-vsctl add-port br0 tap0 tag=9      # access port for VLAN 9

A port added without tag= is a trunk that passes any 802.1Q-tagged VLAN. Adding tag=N makes it an access port on VLAN N — frames egress untagged. Change an existing port the same way:

$ ovs-vsctl set port tap0 tag=9

The three-level hierarchy is worth fixing in your head because every command addresses one of the levels. A bridge (br0) is the virtual switch. A port is a named bundle that carries the VLAN and bonding configuration. An interface is the actual device — a NIC, a tap, a veth end, a tunnel — and a port may contain several interfaces when it is a bond. VLAN tagging is a port property; tunnel options are an interface property. Mixing the two up is the single most common source of "command ran but nothing changed".

$ ovs-vsctl list bridge br0
$ ovs-vsctl list port tap0
$ ovs-vsctl list interface tap0
$ ovs-vsctl get port tap0 tag
$ ovs-vsctl clear port tap0 tag          # back to trunk

Internal Ports with IP Addresses

To give the bridge itself an IP (the gateway for VMs), add an internal port — the OVS equivalent of a switch SVI:

$ ovs-vsctl add-port br0 vlan10 tag=10 -- set Interface vlan10 type=internal
$ ip addr add 192.168.0.123/24 dev vlan10
$ ip link set vlan10 up

Two details cause most of the trouble here. First, the network setup is done by the normal Linux tooling (ip, or a config file for your distribution) — OVS creates the interface, it does not assign the address. Second, address assignment does not survive a reboot unless you declare the interface in the host's network configuration, for example a NetworkManager or systemd-networkd stanza, or a distro-specific ifcfg/interfaces file. If you are using systemd-networkd to manage host networking, our systemd-networkd VLAN, bond and bridge guide shows the corresponding host-side configuration.

VLAN Trunks in Practice

Real deployments rarely want "every VLAN" on a trunk. A VM host uplink usually carries a defined set. OVS lets you constrain a trunk to specific VLANs with trunks=:

# Trunk carrying only VLANs 10, 20 and 30
$ ovs-vsctl set port eth0 trunks=10,20,30

# Trunk carrying a range
$ ovs-vsctl set port eth0 trunks=100:200

# Mixed list and range
$ ovs-vsctl set port eth0 trunks=10,20,100:200

# Back to an unrestricted trunk
$ ovs-vsctl clear port eth0 trunks

Selective trunks are a security control as much as a design choice: a port restricted to VLANs 10, 20 and 30 cannot leak traffic from VLAN 99 even if a hypervisor is misconfigured. For a trunk port that must also carry one untagged (native) VLAN, OVS supports a per-port or per-bridge native VLAN setting:

# Default native VLAN for untagged frames on the bridge
$ ovs-vsctl set bridge br0 other_config:default-vlan=1

# Native VLAN for one port
$ ovs-vsctl set port eth0 vlan_mode=native-tagged tag=1
$ ovs-vsctl set port eth0 vlan_mode=native-untagged tag=1

The vlan_mode values deserve a moment: trunk passes tags untouched, access strips and adds a single tag, native-tagged adds a tag to untagged frames but keeps tags on tagged ones, and native-untagged also strips the native tag on egress. When a VLAN behaves unexpectedly, print vlan_mode before you question anything else.

Bonds and LACP

Redundancy at layer one is a port with multiple interfaces, and OVS implements the same bonding modes as the Linux kernel.

# LACP bond of two uplinks
$ ovs-vsctl add-bond br0 bond0 eth0 eth1 lacp=active \
    bond_mode=balance-tcp other_config:lacp-time=fast

$ ovs-vsctl set port bond0 other_config:lacp-fallback-ab=true

# Verify LACP is negotiating
$ ovs-appctl bond/show bond0
$ ovs-appctl lacp/show bond0

active LACP lets OVS initiate the negotiation; the switch must be configured with a matching port-channel. balance-tcp hashes by L4 flow, which gives better distribution than balance-slb (source-MAC based) but requires the switch to agree on the hash. The lacp-fallback-ab option is genuinely useful in virtualisation: if LACP fails to negotiate, the bond falls back to active-backup rather than passing no traffic at all. The host-side equivalent of these modes is described in our Linux bonding modes: LACP and active-backup article; note that a host bond and an OVS bond are different layers and should not both be configured on the same NICs.

Tunnel Ports: Extending L2 over IP

OVS can stretch a bridge across hosts with GRE or VXLAN tunnel ports — the datapath encapsulates and the remote bridge appears directly connected:

$ ovs-vsctl add-port br0 gre0 -- set Interface gre0 type=gre     options:remote_ip=1.2.3.4

For VXLAN (the modern choice for multi-tenant fabrics) use type=vxlan options:remote_ip=... options:key=100.

# VXLAN point-to-point
$ ovs-vsctl add-port br0 vxlan0 -- set interface vxlan0 \
    type=vxlan options:remote_ip=1.2.3.4 options:key=100

# VXLAN with local source IP pinned (multi-homed host)
$ ovs-vsctl set interface vxlan0 options:local_ip=10.0.0.1

# Verify the tunnel interface and its counters
$ ovs-vsctl list interface vxlan0
$ ovs-ofctl dump-ports br0

Tunnel ports in OVS are point-to-point: you create one per remote endpoint. This scales poorly past a handful of hosts, which is exactly why OpenStack moved to the OpenFlow-managed VXLAN design where a single type=vxlan port with no remote_ip serves as a flow-programmed VTEP. For raw host-to-host tunnelling outside OVS, the kernel implementation is covered in our Linux VXLAN with ip link and static VTEP guide. The MTU consequence applies either way: encapsulation adds overhead, so the underlay and the overlay must be sized deliberately — see VXLAN MTU and fragmentation.

Port Mirroring (SPAN)

Mirror all traffic on eth0/tap0 to a monitor port tap1 for troubleshooting:

$ ovs-vsctl add-port br0 tap1     -- --id=@p get port tap1     -- --id=@m create mirror name=m0 select-all=true output-port=@p     -- set bridge br0 mirrors=@m

That one-liner is where the transaction syntax earns its keep. The -- separates independent commands run in a single atomic transaction, and --id=@p names a variable holding the result of a lookup so a later command in the same transaction can reference it. Read it as four steps: create the monitor port, fetch its identifier as @p, create a mirror object pointing at @p and name it @m, then attach @m to the bridge.

# Mirror only one VLAN or one source port instead of everything
$ ovs-vsctl -- --id=@m create mirror name=m1 \
    select-src-port=@tap0 select-all=false output-port=@p1 \
    -- set bridge br0 mirrors=@m

# Inspect and disable
$ ovs-vsctl list mirror
$ ovs-vsctl clear bridge br0 mirrors      # disable mirroring

Mirroring destinations must be real ports on the same bridge, and a mirrored port carries a full copy of the traffic — size the monitor interface and its destination accordingly, or you will drop frames silently. For the wider toolbox of mirror transports (SPAN, RSPAN, ERSPAN) and when each is appropriate, see SPAN, RSPAN and ERSPAN compared.

Control Plane: Flows, Fail Mode and STP

By default OVS behaves like a learning switch: it floods unknown unicast and learns MACs in the datapath, with no OpenFlow controller required. That default is what makes the examples above work out of the box, and it is also what a controller replaces.

# Inspect what the datapath has learned
$ ovs-appctl fdb/show br0
$ ovs-ofctl dump-flows br0
$ ovs-ofctl dump-ports-desc br0

# What happens if the controller is unreachable
$ ovs-vsctl get bridge br0 fail_mode
$ ovs-vsctl set bridge br0 fail_mode=secure   # drop everything
$ ovs-vsctl set bridge br0 fail_mode=standalone

# Enable spanning tree if you must loop
$ ovs-vsctl set bridge br0 stp_enable=true

fail_mode=standalone is the safe default for a switch with no controller: if the controller disappears, OVS keeps learning and forwarding like a normal switch. fail_mode=secure drops all traffic without a controller, which is correct in a controller-managed fabric where forwarding without controller instruction would be a security hole, and catastrophic if you set it by mistake on a host-only bridge. Note also that OVS bridges do not run STP by default (ovs-vsctl set bridge br0 stp_enable=true enables it) — in a modern leaf-spine or hypervisor design you should not need it, and should treat its necessity as a sign of a design problem rather than a feature to enable casually. Finally, the default datapath ID derives from the lowest non-local MAC among the bridge's ports; set a stable value with ovs-vsctl set bridge br0 other_config:hwaddr=... if your hypervisor setup or controller needs one.

QoS, Policing and Traffic Limits

OVS can rate-limit a port in either direction, which is useful for a VM's public interface or a lab uplink.

# Ingress policing on an interface
$ ovs-vsctl set interface tap0 ingress_policing_rate=10000 \
    ingress_policing_burst=1000

# Verify
$ ovs-vsctl list interface tap0 | grep policing

# Queue-based egress shaping needs a QoS object plus a queue
$ ovs-vsctl -- --id=@q create queue other_config:min-rate=1000000 \
    -- --id=@qos create qos type=linux-htb \
       other_config:max-rate=1000000000 queues=0=@q \
    -- set port tap0 qos=@qos

Ingress policing is measured in kilobit-per-second units and drops excess traffic above the rate; it is a blunt but effective tool for containing a noisy neighbour. Egress shaping uses the kernel's HTB discipline and is the right mechanism when you need guaranteed minimums rather than a ceiling. The underlying tc semantics are the same as on a physical port — Linux tc traffic shaping with HTB explains rate, ceil and filters if you need to reason about what OVS will program.

Verifying the Configuration

$ ovs-vsctl show
$ ovs-vsctl list-ports br0
$ ovs-ofctl dump-ports br0

ovs-vsctl show prints the full bridge/port/interface tree with tags and tunnel options; ovs-ofctl dump-ports shows per-port counters that prove frames are actually flowing. Three more tools complete the picture: ovs-appctl ofproto/trace br0 in_port=eth0,dl_vlan=9,dl_src=...,dl_dst=... simulates a packet through the pipeline and prints exactly which flow and action it hits — the single most useful OVS debugging command; ovs-appctl fdb/show br0 shows the learned MAC table; and ovs-dpctl show confirms the datapath exists and is processing packets.

# Simulate a packet and see which rule matches
$ ovs-appctl ofproto/trace br0 \
    'in_port=1,dl_src=00:11:22:33:44:55,dl_dst=66:77:88:99:aa:bb,dl_vlan=9'

# Learned MACs
$ ovs-appctl fdb/show br0

# Save/restore configuration for backup or migration
$ ovsdb-client dump > ovsdb-backup.txt

That last one matters operationally: since the configuration is a database, the supported way to snapshot it is ovsdb-client dump. Keep a copy before upgrades or before bulk scripted changes.

Troubleshooting Checklist

  • Bridge exists, no traffic. Check ovs-vsctl show for the port being attached to the bridge you think it is — a port added to br1 instead of br0 forwards nothing.
  • VLAN mismatch. Confirm tag and vlan_mode on the port; on a VM host the same VLAN must also be configured on the tap or vnet device, and on ESXi/Proxmox the equivalent layer is a port group or bridge VLAN — see ESXi vSwitch port groups and VLAN IDs and Proxmox VE VLAN-aware bridges.
  • No connectivity after a reboot. The OVSDB persisted, but the IP address on an internal port did not — declare it in host network configuration.
  • Bond shows one active slave. Verify LACP is negotiating with ovs-appctl lacp/show bond0 and that the physical switch port-channel matches.
  • Tunnel up, overlay broken. Check MTU first, then that the underlay routes exist and the local_ip is correct on a multi-homed host.

Where OVS Fits

OVS bridges underpin KVM/libvirt, Docker hosts and Neutron. If you are coming from Linux-native bridging, compare the access/trunk semantics with Linux VLAN tagging with ip link; for the switch-side view of the same VLAN design see MikroTik bridge VLAN filtering. In container platforms the same concepts re-emerge one layer up in the CNI plugin, compared in Kubernetes CNI: Calico vs Cilium vs Flannel, and for isolated host-level testing of the topologies you build here, Linux network namespaces with ip netns and veth pairs is the natural companion.

原文链接:https://docs.openvswitch.org/en/latest/faq/configuration