Modern Linux networking increasingly relies on declarative configuration and dynamic routing protocols to manage complex infrastructure. While systemd-networkd provides an excellent declarative foundation for interface management, it does not natively speak dynamic routing protocols like BGP (Border Gateway Protocol). To achieve robust, scalable routing, administrators must bridge systemd-networkd with FRRouting (FRR), the industry-standard routing suite for Linux.
This tutorial explains how to architect a seamless integration between systemd-networkd and FRRouting. By combining these tools, you can dynamically announce routes, maintain high availability, and build self-healing network topologies on enterprise Linux servers.
Why Combine systemd-networkd and FRRouting?
When managing large-scale Linux deployments or Kubernetes clusters (where Calico or Cilium use BGP), static routing becomes administratively unviable. FRRouting allows a Linux host to act as a first-class router, participating in BGP peering sessions with Top-of-Rack (ToR) switches or other nodes.
However, FRRouting relies on the underlying Linux kernel routing table. If network interfaces are managed by traditional scripts or NetworkManager, interface state changes can be unpredictable. systemd-networkd solves this by providing a highly predictable, event-driven mechanism to configure links, IP addresses, and static routes, ensuring that the interface is perfectly prepared before FRR attempts to establish BGP sessions.
Prerequisites for BGP Integration
Before proceeding, ensure your environment meets the following requirements:
- A modern Linux distribution using systemd (e.g., Ubuntu 22.04+, Debian 12+, Arch Linux).
- Root access or passwordless sudo privileges.
- Two distinct network interfaces or VLANs to establish redundant BGP peerings (recommended).
- An understanding of your local Autonomous System Number (ASN) and peer ASNs.
Step 1: Configure Base Interfaces with systemd-networkd
First, we must configure the base network interfaces using systemd-networkd. We will assign a loopback IP (often used as the Router ID in BGP) and configure the physical uplinks.
Create a configuration for the loopback interface. This is critical because the BGP Router ID should be highly available, tied to the host rather than a physical link.
Create /etc/systemd/network/10-lo.network:
[Match]
Name=lo
[Network]
Address=127.0.0.1/8
Address=10.255.255.10/32 # BGP Router ID and Anycast IP
Next, configure the physical uplinks. In this example, eth0 and eth1 connect to two different ToR switches.
Create /etc/systemd/network/20-eth0.network:
[Match]
Name=eth0
[Network]
Address=10.0.1.10/24
# Do not specify a Gateway here; BGP will provide default routes
LinkLocalAddressing=ipv6
IPv6AcceptRA=no
Apply the configurations and enable the daemon:
sudo systemctl enable systemd-networkd
sudo systemctl restart systemd-networkd
Step 2: Install and Enable FRRouting
Install the FRR package. On Debian/Ubuntu systems, it is available in the standard repositories:
sudo apt update
sudo apt install frr frr-pythontools
By default, FRR disables all routing protocols. You must explicitly enable the BGP daemon. Edit the FRR daemons configuration file:
sudo nano /etc/frr/daemons
Change bgpd=no to bgpd=yes:
bgpd=yes
ospfd=no
isisd=no
Restart the FRR service to apply the change:
sudo systemctl restart frr
Step 3: Configure FRR for BGP Peering
FRR is configured via the vtysh shell, a unified command-line interface resembling Cisco IOS. We will configure BGP to peer with two upstream switches (AS 65001) while our local host operates in AS 65010.
Enter the FRR shell:
sudo vtysh
Enter configuration mode and set up the BGP process:
configure terminal
router bgp 65010
bgp router-id 10.255.255.10
neighbor 10.0.1.1 remote-as 65001
neighbor 10.0.1.1 description ToR-Switch-A
neighbor 10.0.2.1 remote-as 65001
neighbor 10.0.2.1 description ToR-Switch-B
! Announce our loopback IP
address-family ipv4 unicast
network 10.255.255.10/32
exit-address-family
exit
Write the configuration to disk so it persists across reboots:
write memory
Step 4: Synchronise systemd-networkd Route Tables
When FRR learns routes via BGP, it injects them into the Linux kernel routing table. By default, systemd-networkd manages the main routing table and may aggressively remove routes it does not recognise, causing route flapping.
To prevent this, you must instruct systemd-networkd to ignore routes added by the FRR routing daemon. In systemd-networkd, you can specify ManageForeignRoutes=no in your network configurations.
Edit both /etc/systemd/network/20-eth0.network and the corresponding eth1 file, adding the following under the [Network] section:
[Network]
ManageForeignRoutes=no
ManageForeignRoutingPolicyRules=no
Restart systemd-networkd to enforce the policy:
sudo systemctl restart systemd-networkd
Step 5: Verifying the BGP Integration
To confirm that BGP is functioning correctly and exchanging routes over the interfaces managed by systemd-networkd, use the vtysh shell to inspect the BGP summary.
sudo vtysh -c "show ip bgp summary"
You should see your neighbours listed, with the “State/PfxRcd” column showing a number (the count of received prefixes) rather than “Active” or “Idle”.
Verify that the Linux kernel has accepted the routes:
ip route
You will see routes tagged with proto bgp, indicating that FRR has successfully populated the kernel routing table based on the BGP session established over your systemd-networkd links.
By carefully delegating interface provisioning to systemd-networkd and route management to FRRouting, you establish a resilient, deterministic, and highly available Linux routing architecture.