When configuring direct routes through systemd-networkd, I encountered a situation where the standard IP address assignment method (e.g., 192.168.0.2/24) couldn't reach the gateway. This unusual networking scenario required specific route configurations:
ip a a 192.168.0.2/32 dev enp0s3
ip r a 192.168.0.1/32 dev enp0s3
ip r a default via 192.168.0.1
My initial approach using /etc/systemd/network/enp0s3.network looked like this:
[Match]
Name=enp0s3
[Address]
Address=192.168.0.2/32
[Route]
Destination=192.168.0.1/32
[Route]
Gateway=192.168.0.1
The key difference between manual ip
commands and systemd-networkd implementation became apparent:
# ip command output
192.168.0.1 dev enp0s3 scope link
# systemd-networkd output
192.168.0.1 dev enp0s3 proto static
The missing scope link
attribute causes the subsequent default route addition to fail with "Network is unreachable" errors. Manually correcting the route with:
ip r c 192.168.0.1/32 dev enp0s3
ip r a default via 192.168.0.1
shows the system can work when the scope is properly configured.
For immediate solutions in CoreOS environments, consider:
# Option 1: Drop-in configuration
[Service]
ExecStartPost=/usr/sbin/ip route change 192.168.0.1/32 dev enp0s3 scope link
# Option 2: Networkd patch (if building from source)
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
--- a/src/network/networkd-route.c
+++ b/src/network/networkd-route.c
@@ -123,6 +123,8 @@
rtnl_route_set_scope(route, scope);
if (r->protocol >= 0)
rtnl_route_set_protocol(route, r->protocol);
+ if (r->scope == RT_SCOPE_UNIVERSE && r->family == AF_INET)
+ rtnl_route_set_scope(route, RT_SCOPE_LINK);
This appears to be a genuine edge case in systemd-networkd's route handling. While not strictly a bug (as the behavior is technically correct), it's certainly a usability issue for specific networking scenarios like:
- VPN configurations with strict route requirements
- Container networking setups
- Cloud environments with unusual routing requirements
The best approach currently is to file an enhancement request with the systemd project while using the workarounds mentioned above in production environments.
When dealing with non-standard network configurations, systemd-networkd sometimes behaves differently than manual ip
commands. The specific case involves:
ip a a 192.168.0.2/32 dev enp0s3
ip r a 192.168.0.1/32 dev enp0s3
ip r a default via 192.168.0.1
This setup is necessary when you can't use conventional subnet addressing (like 192.168.0.2/24) because the gateway handles the entire subnet routing differently.
The initial configuration in /etc/systemd/network/enp0s3.network
:
[Match]
Name=enp0s3
[Address]
Address=192.168.0.2/32
[Route]
Destination=192.168.0.1/32
[Route]
Gateway=192.168.0.1
Comparing manual ip
command and systemd-networkd behavior:
# ip route output
192.168.0.1 dev enp0s3 scope link
# systemd-networkd output
192.168.0.1 dev enp0s3 proto static
The missing scope link
attribute causes the subsequent default route to fail with "Network is unreachable".
Here are several approaches to solve this:
1. Using Route Metric
Force link-local behavior with metric:
[Route]
Destination=192.168.0.1/32
Scope=link
Metric=1024
2. Complete Working Configuration
[Match]
Name=enp0s3
[Address]
Address=192.168.0.2/32
[Route]
Destination=192.168.0.1/32
Scope=link
[Route]
Gateway=192.168.0.1
GatewayOnLink=yes
3. Alternative: Networkd Drop-in
Create /etc/systemd/network/enp0s3.network.d/10-routes.conf
:
[Route]
Destination=192.168.0.1/32
Scope=link
[Route]
Destination=0.0.0.0/0
Gateway=192.168.0.1
After applying changes:
# Reload configuration
sudo networkctl reload
# Check routes
ip route show
ip route get 192.168.0.1
# Debug networkd
journalctl -u systemd-networkd -f
For CoreOS environments where traditional network managers aren't available, understanding systemd-networkd's behavior with direct routes is crucial. The GatewayOnLink
option is particularly important for these edge cases.