Debian – the systemd-networkd equivalent of post-up? (dynamic bridge MAC configuration)

bridgedebianlinux-networkingnetworkingsystemd

In Linux distributions that use /etc/network/interfaces (such as Debian) I could get a (kernel) bridge to use the MAC48 address of one of its static bridge slave interfaces, such as a built in wlan0, using post-up, as in:

 post-up ip link set br0 address `cat /sys/class/net/wlan0/address`

This ensured that both 1) each cloned system used its very own unique MAC48 (the one from the unique wlan0), and 2) the bridge MAC kept stable even when hot-plugging further bridge interfaces with lower MAC48's.

Does systemd-networkd support any kind of post-up commands that can be run after a network (or netdev) has been brought up? I've tried to find such a thing, but may have missed it.

Or is the correct way in systemd completely different, i.e. to have a device unit, and a service that wraps the ip link... command and depends on the device unit? If so, how would the device unit and the service unit files look like?

Best Answer

It turns out that due to the heavy modular concept of systemd and systemd-networkd the scripting question needs to be tackled from a different angle: instead of looking for scripting with the bridge .netdev definition, the systemd way is to have a (very small) one-shot .service unit that is wanted by the bridge .netdev.

As a side note: it seems that in more recent Linux kernels, the kernel bridges actually don't use the dynamically changing lowest MAC48 scheme for the bridge MAC48 anymore. Instead, they create a static MAC48 for the bridge itself. So, in the very strict sense this solution is not really needed anymore, unless one prefers to use a "real" hardware MAC48; which is what is done here in the following service unit.

The necessary new service unit (in lieu of the old post-up from /etc/network/interfaces) lives in /etc/systemd/system/bridge-stable-mac.service and assigns the MAC48 from (built-in, fixed) wlan0 to the bridge itself:

[Service]
Type=oneshot
ExecStart=/bin/bash -c "/bin/echo 'br0 available, setting MAC ' `/bin/cat /sys/class/net/wlan0/address`"
ExecStart=/bin/bash -c "/sbin/ip link set br0 address `/bin/cat /sys/class/net/wlan0/address`"

[Install]
WantedBy=sys-subsystem-net-devices-br0.device

The central point here is the WantedBy= clause: whenever br0 starts, then this service should be run (exactly once, Type=oneshot). Systemd is really neat here, as it doesn't need to edit the existing device definition in order to add our dependency, but instead calculates this dependency using our inverse WantedBy= link. This is really where I think that systemd does shine.

The service unit above assumes that your bridge is named br0. You should use a corresponding .netdev file to define this bridge br0. For instance, in /etc/systemd/network/10-br0.netdev:

[NetDev]
Name=br0
Kind=bridge

When it comes to hotplugging bridge ports, systemd actually does this already out-of-the-box, which is very neat; in /etc/systemd/network/10-br0-ports.network:

[Match]
Name=eth0 wlan0

[Network]
Bridge=br0

That's it!

Related Topic