The official llllloooooo blog

Sunday, January 3, 2021

Adding an Ethernet bridge interface on Raspberry Pi 4 under Debian Buster based Raspberry Pi OS

I am in the process of trying to get QEMU working on a Raspberry Pi 4 running the most recent (December 2020) version of Raspberry Pi OS (aka Raspbian) which is based on Debian Buster.

In order for QEMU networking to work, one needs to have a bridge style interface on the host system. A Bridge is a virtual interface that allows multiple other interfaces to connect to it and exchange information. The idea in QEMU is that when QEMU is started up, QEMU can be configured to create a virtual TAP interface which connects to a bridge interface on the host system. In turn, one of the physical interfaces on the host system is also connected to the bridge. This allows the TAP interface and the physical interface to communicate, which in turn allows the QEMU guest to access the "real" network.

This only typically works with hard wired RJ-45 style ethernet interfaces, normally the eth0 interface. Wireless interfaces (wlan0) don't cope very well in this situation because wireless networks don't cope very well with clients transmitting packets from multiple source MAC addresses. (I won't go into the details. I couldn't be bothered.)

There are a few guides on the internet describing how to add a bridge interface to a Raspberry Pi but most of these are very out of date. They refer to using the /etc/network/interfaces file which is no longer used (by default) in modern Raspberry Pi OS.

The old way of configuring bridge interfaces on a Raspberry Pi.

The steps in the following guide are based loosely on the notes seen in an article called

Setting up a Raspberry Pi as a bridged wireless access point
https://www.raspberrypi.org/documentation/configuration/wireless/access-point-bridged.md

However this article focuses on creating a wireless bridge which isn't what we want to do, so the following steps provide a filtered guide to just creating a hard Ethernet based bridge and leaving the wireless interface alone. Perform all of these steps as root or with the "sudo" command.

1) On the Raspberry Pi network interfaces are created by the systemd network daemon (systemd-networkd). Create a file called /etc/systemd/network/bridge-br0.netdev with the following contents. 

[NetDev]
Name=br0
Kind=bridge

This will configure systemd-networkd to create a bridge interface called br0.

2) Create a file called /etc/systemd/network/br0-member-eth0.network with the following content

[Match]
Name=eth0

[Network]
Bridge=br0

This configures systemd-networkd to add the eth0 interface to the bridge called br0

3) Enable the systemd network daemon with the following shell command. Note that this command will enable the daemon even across reboots.

# systemctl enable systemd-networkd

4) IP and IPv6 addressing is handled by the dhcpcd daemon. To configure the IP properties of the new br0 bridge interface we add the following lines to the bottom of the file /etc/dhcpcd.conf . Note, comment out any other active configuration for eth0 if there is any.

# Don't get a DHCP lease on eth0
# because it's part of a bridge now
denyinterfaces eth0

# Get a lease on br0
interface br0

Note also that there are other parameters you can configure on behalf of br0. Do a search on "man dhcpd.conf" for other available parameters.

Optional extra step) Install the bridge-utils package to get the legacy (but still very useful) brctl command. The brctl command is superseded by the "ip link" set of commands but brctl is easier to use and more informative in my view.

# apt-get install bridge-utils
Finally reboot your system, make sure your RJ45 ethernet is plugged in, and confirm that there is now a bridge interface (br0) active. 

Here are some sample command outputs from my system showing that while the wireless interface is still active as normal, the Ethernet interface now shows no IP address but the br0 interface has managed to obtain an IP address for the copper network instead.

root@raspberrypi:~#brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.dca632000007       no              eth0

root@raspberrypi:~#ip link show master br0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP mode DEFAULT group default qlen 1000
    link/ether dc:a6:32:00:00:07 brd ff:ff:ff:ff:ff:ff

root@raspberrypi:~#ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
    link/ether dc:a6:32:00:00:07 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::dea6:32ff:fe00:0007/64 scope link
       valid_lft forever preferred_lft forever
3: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether dc:a6:32:00:00:07 brd ff:ff:ff:ff:ff:ff
    inet 10.99.99.220/24 brd 10.99.99.255 scope global dynamic noprefixroute br0
       valid_lft 83444sec preferred_lft 72644sec
    inet6 2001:0db8:aaaa:bbbb:88c:c220:218:50fe/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 2591992sec preferred_lft 604792sec
    inet6 fe80::19d1:b0a3:d764:45b5/64 scope link
       valid_lft forever preferred_lft forever
4: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether dc:a6:32:00:00:09 brd ff:ff:ff:ff:ff:ff
    inet 10.99.99.79/24 brd 10.99.99.255 scope global dynamic noprefixroute wlan0
       valid_lft 100853sec preferred_lft 148253sec
    inet6 2001:0db8:aaaa:bbbb:ab7e:fa07:5ee4:4298/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 2591121sec preferred_lft 603921sec
    inet6 fe80::7b77:6ad9:cfbe:dcfd/64 scope link
       valid_lft forever preferred_lft forever

root@raspberrypi:~#ifconfig
br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.99.99.220  netmask 255.255.255.0  broadcast 10.99.99.255
        inet6 2001:0db8:aaaa:bbbb:88c:c220:218:50fe  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::19d1:b0a3:d764:45b5  prefixlen 64  scopeid 0x20<link>
        ether dc:a6:32:00:00:07  txqueuelen 1000  (Ethernet)
        RX packets 10802  bytes 1365340 (1.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1208  bytes 138128 (134.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::dea6:32ff:fe00:0007  prefixlen 64  scopeid 0x20<link>
        ether dc:a6:32:00:00:07  txqueuelen 1000  (Ethernet)
        RX packets 17786  bytes 3658451 (3.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1243  bytes 142024 (138.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 6  bytes 398 (398.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6  bytes 398 (398.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.99.99.79  netmask 255.255.255.0  broadcast 10.99.99.255
        inet6 fe80::7b77:6ad9:cfbe:dcfd  prefixlen 64  scopeid 0x20<link>
        inet6 2001:0db8:aaaa:bbbb:ab7e:fa07:5ee4:4298  prefixlen 64  scopeid 0x0<global>
        ether dc:a6:32:00:00:09  txqueuelen 1000  (Ethernet)
        RX packets 630  bytes 68111 (66.5 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 137  bytes 21081 (20.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
I hope this helps someone. Please let me know if it does!