Systemd-Networkd as a Router

speckx1 pts0 comments

Systemd-Networkd as a Router :: apalrd's adventures

Systemd-Networkd as a Router

2026-08-13

#networking

Today I’m taking a look at my MONO Gateway - but with my own software twist. You may know I am not a fan of the OpenWRT project for end users (mostly due to their very poor and inconsistent documentation), and usually recommend OPNsense, but this time I want to build something DECLARATIVE! So I’m of course going back to just plain Debian.

I’m using systemd-networkd for this, so all of the configs are in systemd’s ini-style. I rather like this syntax.

Networkd Configs⌗

Here are my example configs

Backup Interface⌗

/etc/systemd/network/06_backup.network

# 'backup' network interface<br># This does not depend on anything else, so we can always<br># use it to SSH in via gateway.local<br>[Match]<br>Name=eth1

[Network]<br>IPv6AcceptRA=yes<br>IPv6SendRA=yes<br>MulticastDNS=yes

# RA options<br>[IPv6SendRA]<br>Managed=no<br>OtherInformation=no<br>#Not a router!<br>RouterLifetimeSec=0

# ula prefix (for internet-outage connectivity)<br>[IPv6Prefix]<br>Prefix=fdf9:ff03:0f91:0001::/64<br>Assign=true

Vlan Parent Interface⌗

This interface just has VLAN slaves. /etc/systemd/network/10_eth0_vlans.network:

# Systemd Network unit for interface which is just a<br># parent for VLAN interfaces<br>[Match]<br>Name=eth0

[Network]<br>DHCP=no<br>IPv6AcceptRA=no<br>#Carry VLANs on this physical interface<br>VLAN=eth0.20<br>VLAN=eth0.21

WAN Interface⌗

This interface actually does the WAN upstream. Some notes in DHCPv6 are provider-specific.<br>/etc/systemd/network/11_wan.network

# Systemd Network unit for WAN interface<br># Replace name with your WAN interface name<br>[Match]<br>Name=eth2

[Network]<br>Description=WAN<br>#DHCP=both means * force ipv6 *<br>#Normally you would set DHCP=ipv4, meaning ipv6 depends on router advertisement M-flag<br>DHCP=both<br>IPv6AcceptRA=yes

[DHCPv4]<br>UseRoutes=yes<br>RouteMetric=100

[IPv6AcceptRA]<br>RouteMetric=100<br>DHCPv6Client=always

[DHCPv6]<br>#Force request even if nothing else indicates it should request PD (should not be needed)<br>ForceDHCPv6PDOtherInformation=yes<br>#Normal values here are /56 and /60<br>PrefixDelegationHint=::/59<br>#This means do not request an address at all<br>#UseAddress=no

LAN Interface⌗

This is a basic LAN interface: /etc/systemd/network/31_lan20.network

# LAN interface on vlan 20<br>[Match]<br>Name=eth0.20

[Network]<br>Address=10.10.20.1/24<br>IPv6SendRA=yes<br>IPv6AcceptRA=no<br>DHCPServer=yes<br>DHCPPrefixDelegation=yes<br>IPMasquerade=ipv6

# DHCP server pool options<br>[DHCPServer]<br>PoolOffset=100<br>PoolSize=100<br>EmitDNS=yes<br>DNS=192.168.0.3

# RA options<br>[IPv6SendRA]<br>Managed=no<br>OtherInformation=no<br>RouterLifetimeSec=1800

# ula prefix (not required)<br>[IPv6Prefix]<br>Prefix=fdf9:ff03:0f91:0001::/64<br>Assign=true

#static prefixes (this can be copied as many times as you want)<br>#[IPv6Prefix]<br>#Prefix=2001:db8:10::/64<br>#Assign=true

#dynamic prefixes<br>[DHCPPrefixDelegation]<br>UplinkInterface=eth2<br>SubnetId=0x2<br>Announce=yes<br>Assign=yes<br>#Token can be used under any Prefix or PrefixDelegation<br>#This means a forced suffix<br>#Not needed, but some people like it<br>Token=::1

VLAN Device⌗

# /etc/systemd/network/31_vlanXX.netdev<br># Network Device for VLAN xx<br>[NetDev]<br>Name=eth0.20<br>Kind=vlan

[VLAN]<br>Id=20

Nftables.conf⌗

This goes in /etc/nftables.conf - you may need a systemd unit to initialize it

#!/usr/sbin/nft -f

flush ruleset

# Local Variables<br>define WAN = eth2<br>define LAN20 = eth0.20<br>define LANS = { "eth0.20" }<br>define LAN_RANGES = "10.10.0.0/16"

# IPv4/IPv6 stateful firewall

table inet filter {

# INPUT chain is sessions who terminate at this box<br># this is for services running on this box, not routed through<br># just to be clear<br>chain input {<br>type filter hook input priority filter;<br>policy drop;

# Loopback<br>iifname "lo" accept

# Backup interface<br>iifname "eth1" accept

iifname $WAN accept

# Established/related connections<br>ct state established,related accept

# Invalid packets<br>ct state invalid drop

# ICMP<br>ip protocol icmp accept<br>ip6 nexthdr icmpv6 accept<br>ip6 nexthdr 41 accept

# DHCP client<br># IPv4 DHCP: client -> server<br>iifname $WAN udp sport 68 udp dport 67 accept

# IPv6 DHCP: client -> server<br>iifname $WAN udp sport 546 udp dport 547 accept

# DHCP server for LAN<br>iifname $LANS udp sport 68 udp dport 67 accept<br>iifname $LANS udp sport 546 udp dport 547 accept

# Router administration from LAN (SSH)<br>iifname $LAN20 tcp dport 22 accept

# FORWARD chain is where your normal firewall rules go<br># this is stuff that is ROUTED<br>chain forward {<br>type filter hook forward priority filter;<br>policy drop;

# Established/related connections<br>ct state established,related accept

# Invalid packets<br>ct state invalid drop

# LAN -> WAN<br>iifname $LAN20 oifname $WAN accept

iifname "tun_waw" accept

# IPv4 port forward<br># At the firewall stage, daddr+dport have been NATed<br>iifname $WAN oifname $LAN20 \<br>ip daddr 10.10.20.150 \<br>tcp dport 80 \<br>ct state new accept

# IPv6: allow TCP/443 to the known EUI-64 host<br>iifname $WAN oifname $LAN20 \<br>ip6 daddr & 0:0:0:0:ffff:ffff:ffff:ffff == ::be24:11ff:fe71:a41f \<br>tcp dport 80...

network accept interface systemd iifname vlan

Related Articles