Systemd Networking

systemd-networkd should be the future. That being said it is somewhat arcane to say the least.

The desire: Create a BONDED LAG, and attach a bunch of bridged VLANs to it.

The script below should create a folder /tmp/systemd-networkd and dump all the needed configuration. Despite what I continually read, order does not seem to be important. This works on Debian Bullseye. I built this because I keep rebuilding my LAB KVM/QEMU server, and typing in all these files is tedious at best. This used to be about 15 or so lines in /etc/network/interfaces. This ranks right up there with using m4 to compile sendmail.cf. The more layers of obfuscation the better.

NOTE TO SYSTEMD DEVELOPERS:

I get the drop a file to configure idea. Makes sense for a number of things. Not so much with networking. This is like the RedHat sysconfig/network-scripts only worse. The fan-out here is outrageous. I could actually write a script using iproute2 and accomplish this with a fraction of the effort. I should be able to trivially define a VLAN on any interface, building a stack up from the physical interfaces:

From 15 lines to 18 Files to create 4 bridged vlans

[root@nas-7] /nas-3/admin/install/systemd-networkd<351>ls /tmp/systemd-network/
bond1.1099.netdev   bond1.1100.network	bond1.1102.netdev   bond1.netdev     br0.1099.network  br0.1101.netdev	 br0.1102.network
bond1.1099.network  bond1.1101.netdev	bond1.1102.network  bond1.network    br0.1100.netdev   br0.1101.network  br0.netdev
bond1.1100.netdev   bond1.1101.network	bond1.br0.network   br0.1099.netdev  br0.1100.network  br0.1102.netdev	 br0.network

[root@nas-7] /nas-3/admin/install/systemd-networkd<354>iflist
Interface |IP Address      |Broadcast    |Point-Point|Netmask      |
----------|----------------|-------------|-----------|-------------|
lo        |127.0.0.1/8     |             |           |255.0.0.0    |
eth0      |                |             |           |             |
eth1      |                |             |           |             |
eth2      |                |             |           |             |
eth3      |                |             |           |             |
bond1     |                |             |           |             |
br0.1099  |10.29.99.254/24 |10.29.99.255 |           |255.255.255.0|
br0.1100  |10.29.100.254/24|10.29.100.255|           |255.255.255.0|
br0.1101  |10.29.101.254/24|10.29.101.255|           |255.255.255.0|
br0.1102  |10.29.102.254/24|10.29.102.255|           |255.255.255.0|
br0       |10.29.0.9/24    |10.29.0.255  |           |255.255.255.0|
bond1.1099|                |             |           |             |
bond1.1100|                |             |           |             |
bond1.1101|                |             |           |             |
bond1.1102|                |             |           |             |

I suggest you remove all the other networking cruft:

I hacked in “line” for a line_edit binary I wrote/use if it can't find it. Might give you grief, just comment out the cmd_ok at the bottom and manually move the files if it doesn't fly. There are surely some issues with this, I have done a file-by-file comparison, seems to be pretty close. Not sure if LLDP should be on the bridge or the physical with a bond or whatever. The setup seems to match my working manual configs almost to the letter. I haven't fully verified any of this from scratch, as there are some manual additions to some of the working files. It should produce working output.

Hack up the variables at the top of the script to meet your needs.

#!/bin/sh
#
#        Script: create_network
#
# Create the necessary files for systemd to create bridged vlan interfaces
# against a LAG or an interface.  Not super pretty
#
# Last Modified: 2023-04-06 16:09:48

# Pick A scenario:
# To LAG multiple interfaces use these two
BRIDGE_DEV="bond1"
BOND_IFACES="eth0 eth1 eth2 eth3"
# -- OR --
# To just use a single interface for the bridge
#BRIDGE_DEV="eth0"
#

BRIDGE_PREFIX="br0"
VLAN_LIST="1099 1100 1101 1102"
# Bind addresses as desired to the various bridges, front end of variable should match prefix
# Format is BRIDGE_VLAN_WORD
br0__address="10.29.0.13/24"
br0__gateway="10.29.0.1"
br0_1099_address="10.29.99.253/24"
br0_1100_address="10.29.100.253/24"
br0_1101_address="10.29.101.253/24"
br0_1102_address="10.29.102.253/24"
br0_1102_gateway="10.29.102.1"

# Some default cruft to the primary bridge interface
DOMAIN_LIST="i.ksmith.com i.cfns.net ksmith.com cfns.net"
DNS_LIST="10.29.0.30 10.29.1.20 10.29.0.1"

# Salt and pepper
TMP_FOLDER=/tmp/systemd-network
NETWORK_FOLDER=/etc/systemd/network

#----------------------------------------------------------------------
# CMD_OK
# Run command with confirmation
#----------------------------------------------------------------------
cmd_ok() {
    echo "$1"
    if [ -x /usr/local/bin/line_edit ]
    then
        ok=`line_edit 1 -u -p "OK (Y/N)? "`
    else
        /bin/echo "OK (Y/N)?" && ok=`line`
    fi
    case "$ok" in
      Y) eval "$1" ;;
      N) : ;;
    esac
}

#----------------------------------------------------------------------
# CREATE_BASE
#----------------------------------------------------------------------
create_base() {
    cat - <<EOF_BASE > /$TMP_FOLDER/$base.network
#
# File: $base.network
#
[Match]
Name=$base

[Network]
Bridge=$bridge
LLDP=true
EmitLLDP=true
EOF_BASE
}

#----------------------------------------------------------------------
# CREATE_BOND
#----------------------------------------------------------------------
create_bond() {

    cat - <<EOF_BOND_NETDEV > $TMP_FOLDER/$base.netdev
#
# $base.netdev
#
[NetDev]
Name=$base
Description=Primary LAN LAG
Kind=bond

[Bond]
Mode=802.3ad
EOF_BOND_NETDEV

    (
        echo "#"
        echo "# $base.network"
        echo "#"
        echo "[Match]"
        for iface in $BOND_IFACES
        do
            echo "Name=$iface"
        done
        echo ""
        echo "[Network]"
        echo "Bond=$base"
    ) > $TMP_FOLDER/$base.network
    cat - <<EOF_BASE > /$TMP_FOLDER/$base.${BRIDGE_PREFIX}.network
#
# File: $base.${BRIDGE_PREFIX}.network
#
[Match]
Name=$base

[Network]
Bridge=$bridge
LLDP=true
EmitLLDP=true
EOF_BASE
}

#----------------------------------------------------------------------
# CREATE_BRIDGE
#----------------------------------------------------------------------
create_bridge() {
    cat - <<EOF_BRIDGE_NETDEV > $TMP_FOLDER/$bridge.netdev
[NetDev]
Name=$bridge
Kind=bridge
EOF_BRIDGE_NETDEV

    cat - <<EOF_BRIDGE_NETWORK > $TMP_FOLDER/$bridge.network
[Match]
Name=$bridge

[Network]
EOF_BRIDGE_NETWORK

    eval "address=\$${BRIDGE_PREFIX}_${vlan_id}_address"
    [ "$address" != "" ] && echo "Address=$address" >> $TMP_FOLDER/$bridge.network
    eval "gateway=\$${BRIDGE_PREFIX}_${vlan_id}_gateway"
    [ "$gateway" != "" ] && echo "Gateway=$gateway" >> $TMP_FOLDER/$bridge.network
    # DNS/DOMAIN only on primary bridge ?
    if [ "$vlan_id" = "" ]
    then
        echo "Domains=$DOMAIN_LIST" >> $TMP_FOLDER/$bridge.network
        for dns in $DNS_LIST
        do
            echo "DNS=$dns" >> $TMP_FOLDER/$bridge.network
        done
    fi
}

#----------------------------------------------------------------------
# CREATE_VLAN
#----------------------------------------------------------------------
create_vlan() {
    # First the Device definition
    cat - <<EOF_VLAN_NETDEV > $TMP_FOLDER/$base.$vlan_id.netdev
#
# $base.$vlan_id.netdev
#
[NetDev]
Name=$base.$vlan_id
Kind=vlan

[VLAN]
Id=$vlan_id

EOF_VLAN_NETDEV

    # Then the Network definition
    cat - <<EOF_VLAN_NETWORK > $TMP_FOLDER/$base.$vlan_id.network
#
# $base.$vlan_id.network
#
[Match]
Name=$base.$vlan_id

[Network]
Bridge=$bridge
EOF_VLAN_NETWORK
}

#----------------------------------------------------------------------
# MAIN
#----------------------------------------------------------------------
arg="$1"

rm -rf $TMP_FOLDER
mkdir $TMP_FOLDER || exit 255

vlan_id=
for base in $BRIDGE_DEV
do
    bridge=$BRIDGE_PREFIX
    case "$base" in
      bond*) create_bond ; base_path=$TMP_FOLDER/$base.${BRIDGE_PREFIX}.network ;;
      eth*) create_base ; base_path=$TMP_FOLDER/$base.network ;;
    esac
    create_bridge
    for vlan_id in $VLAN_LIST
    do
        bridge=$BRIDGE_PREFIX.$vlan_id
        echo "VLAN=$base.$vlan_id" >> $base_path
        create_vlan $vlan_id
        bridge="$BRIDGE_PREFIX.$vlan_id"
        create_bridge
    done
done
echo ""
ls $TMP_FOLDER
echo ""
echo ""
cmd="mv $NETWORK_FOLDER $NETWORK_FOLDER.old"
cmd="$cmd && cp -va $TMP_FOLDER $NETWORK_FOLDER"
cmd_ok "$cmd"