Setting Up a Virtual Routing Bridge (brouter)

Peter Boy, Kevin Fenzi Verze F37,F38 Last review: 2023-04-18

A virtual bridge is a convenient means of providing the virtual machines hosted on a server with access to the public network by sharing its physical interface. More complex configurations are also possible, such as setting up an internal local network of several servers via one or more additional physical interfaces. But the basic structure of a virtual bridge remains the same.

Basically there a two ways to set up a virtual bridge.

The virtual (plain) bridge

Typically, the bridge "captures" the physical interface of the server and assigns the server as a slave device. To attach Virtual Machines, virtual interfaces are added as needed. The bridge operates at layer 2 of the OSI model and uses unique MAC addresses to determine the recipient of a data packet.

The virtual routing bridge

This bridge leaves the host’s interface untouched and instead creates an independent bridge to which VMs are attached. It uses the forwarding capability to forward incoming packets that are not destined for the host to the bridge. The destination of data packets is determined locally to the bridge based on IP addresses and routing tables.

This article deals with the latter variant.

Prerequisites

  1. Fully updated Fedora Server, any version of F33 or newer, preferrable F38.

  2. Installed virtualization support according to the Adding Virtualization Support guide.

  3. Completed preparations for installing VMs according to the Provisioning the Server VM image guide.

  4. Set up DNS entries for the projected VMs

Steps to configure a basic routing bridge

  1. Check the forwarding configuration

     […]# cat /proc/sys/net/ipv4/ip_forward
     […]# cat /proc/sys/net/ipv6/conf/default/forwarding

    In both cases a value of 1 must be returned. Libvirt will activate IPv4 forwarding, but probably not IPv6. If necessary, activate forwarding temporarily

    […]# echo 1 > /proc/sys/net/ipv4/ip_forward
    […]# echo 1 > /proc/sys/net/ipv6/conf/all/forwarding

    The following file must be set up for permanent setup.

    […]# vim /etc/sysctl.d/50-enable-forwarding.conf
    # local customizations
    #
    # enable forwarding for dual stack
    net.ipv4.ip_forward=1
    net.ipv6.conf.all.forwarding=1
  2. Checking the existing interfaces

    […]# ip a
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     …
    2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> …  state UP group default qlen 1000
         inet 148.251.152.29/32 scope global noprefixroute enp2s0
          …
         inet6 2a01:xxx:yyy:zzz::2/64 scope global noprefixroute
          …
    3: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> …  state UP group default qlen 1000
          …
  3. Adjusting the IPv6 subnet

    As the listing indicates, the external IPv6 subnet is a common full /64 network. This must be changed to trigger IPv6 forwarding.

    […]# nmcli con mod enp2s0 ipv6.addresses '2a01:4f8:210:512d::2/128'
    […]# nmcli con up  enp2s0
  4. Creating a routing bridge

    The (public) bridge is named vbr1s0, based on the name of the accompanying (public) interface.The IP addresses are the same, but with a different subnet range to trigger forwarding.

    In the listing of interfaces, the IPv4 address is a point-to-point connection. Therefore, the bridge uses a subnet, if any, the range that is also assigned in DNS. If the IPv4 interface is also created as a subnet, the bridge would be created as a p2p connection instead.

    […]# nmcli con add  con-name vbr1s0  ifname vbr1s0  type bridge  stp off \
         ipv4.method manual  ipv4.addresses '148.251.152.29/27'  \
         ipv6.method manual ipv6.addresses '2a01:4f8:210:512d::2/64' ipv6.addr-gen-mode eui64

    No zone is specified! Thus the bridge is assigned to the default zone (FedoraServer), to which the Ethernet interface also belongs by default. This is very important for the firewall permissions!

    Finally, for IPv4, the routes must be created and the public addresses of all VMs must be listed

    […]# nmcli con mod vbr2s0 +ipv4.routes "148.251.152.49/32"
    […]# nmcli con mod vbr2s0 +ipv4.routes "148.251.152.52/32"
    […]# nmcli con mod vbr2s0 +ipv4.routes "148.251.152.56/32"
  5. Double check your entries, especially the IP addresses, to avoid incorrect configuration and time-consuming troubleshooting.

  6. Activate the routing bridge

    […]# nmcli con up vbr1s0
  7. Installing a VM

    Use Cockpit or the command line

    […]# cp /var/lib/libvirt/boot/Fedora-Server-KVM-37-custom.qcow2 /var/lib/libvirt/images/vm-01.qcow2
    […]# virt-install --name vm-01 --memory 4096 --cpu host --vcpus 4 --graphics none \
    --os-variant fedora37 --import  --disk /var/lib/libvirt/images/vm-01.qcow2,format=qcow2,bus=virtio \
    --network bridge=vbr1s0,model=virtio --network bridge=virbr0,model=virtio

    Complete the First Boot Sceen. Leave the network configuration as it is. It is easier to configure it after the first login.

  8. Login to the VM and configure the public interface

    […]# nmcli con mod 'Wired connection 1' ipv4.method manual ipv4.addresses '148.251.152.49/32' \
         ipv4.gateway '148.251.152.29'  ipv4.dns '213.133.98.98' ipv6.method 'manual' \
         ipv6.addresses '2a01:4f8:210:512d::10/64' ipv6.gateway '2a01:4f8:210:512d::2'  connection.id enp1s0
    […]# nmcli con up  enp1s0
  9. If exist adjust the internal interface.

    […]# nmcli con mod 'Wired connection 2'  ipv4.method auto ipv6.method disabled connection.zone 'internal' connection.id enp2s0
    […]# nmcli con up  enp2s0
  10. Optionally reboot to reinitialize everything

    […]# reboot

Testing the configuration

  1. Check the forwarding configuration

     […]# cat /proc/sys/net/ipv4/ip_forward
     […]# cat /proc/sys/net/ipv6/conf/default/forwarding

    In both cases a value of 1 must be returned.

  2. Check the host configuration SELinux should be in enforcing mode and firewalld active with zone FedoraServer with both the external interface and the virtual bridge attached.

     […]# getenforce
     […]# firewall-cmd  --list-all
     […]# firewall-cmd  --get-active-zones
  3. Check IPv6

    (a) ping6  external desktop → host (using ipv6 notation)
    (b) ping6  host → VM
    (c) ping6 external desktop → VM
    (d) ssh external desktop → VM
    (e) traceroute6 external desktop → host
    (f) traceroute6 external desktop → VM
  4. Check IPv4

    (a) ping  external desktop → host (using ipv4 notation)
    (b) ping  host → VM
    (c) ping external desktop → VM
    (d) ssh external desktop → VM
    (e) traceroute external desktop → host
    (f) traceroute external desktop → VM