Linux IPv6 NBN routing with systemd-networkd: update

Years ago, I posted about how to get IPv6 routing working with PPP connections and Debian buster. Since then, the systemd-networkd configuration schema has changed significantly. Here are updated instructions for how I set up my router with Debian 13 (trixie), using systemd v257.

Here’s what I’ve configured the router to do to establish an internet connection:

  • Create a PPPoE (ppp0) connection to the internet. My ISP, Internode, requires a 802.1q VLAN to be set on the ethernet frames, so in my case this will be over eth1.2.
  • Establish its internet IPv4 address via PPP/IPCP.
  • Solicit an IPv6 prefix via DHCPv6.

And in terms of providing routing for my LAN:

  • Respond to DHCPv4 requests, setting itself as the gateway, and assigning IPv4 addresses on the LAN.
  • Respond to IPv6 router solicitation requests, advertising the IPv6 prefix and SLAAC.
  • NAT for IPv4.
  • Packet forwarding for IPv6.

All of this can be achieved with systemd-networkd and pppd, without the need for any other DHCP client/server software, or NetworkManager.

systemd-networkd

# /etc/systemd/network/20-eth0.network
[Match]
Name=eth0

[Link]
MTUBytes=1492

[Network]
# Set up static IPs here.
Address=192.168.x.x/24
LinkLocalAddressing=ipv6

# Limit for packets forwarded over PPPoE
IPv6MTUBytes=1492

IPv6AcceptRA=no
IPv6SendRA=yes
DHCPPrefixDelegation=yes
DHCPServer=yes

[IPv6SendRA]
OtherInformation=yes

[DHCPServer]
# Set MTU (DHCP Option 26) to 1492 bytes.
SendOption=26:uint16:1492
# /etc/systemd/network/20-eth1.2.netdev
[NetDev]
Name=eth1.2
Kind=vlan

[VLAN]
Id=2
# /etc/systemd/network/20-eth1.network
[Match]
Name=eth1*

[Link]
MTUBytes=1500

[Network]
LinkLocalAddressing=no
IPv6AcceptRA=no
LLDP=no
VLAN=eth1.2
# /etc/systemd/network/60-ppp0.network
[Match]
Name=ppp0

[Network]
LLDP=no
DHCP=ipv6
DefaultRouteOnDevice=yes
IPv6AcceptRA=yes
IPv6SendRA=no

[DHCPv6]
WithoutRA=solicit

udev rules

# /etc/udev/rules.d/99-eth1-ppp0.rules
# Start ppp0 when eth1 is connected
SUBSYSTEM=="net", ACTION=="add", NAME=="eth1", ENV{SYSTEMD_WANTS}="pppd@ppp0.service"

pppd service and configuration

# /etc/systemd/system/pppd@ppp0.service
[Unit]
Description=PPP connection for interface %I
Documentation=man:pppd(8)

[Service]
Type=forking
ExecStart=/usr/sbin/pppd call %I linkname %i updetach nopersist
ExecStop=/bin/kill $MAINPID
ExecReload=/bin/kill -HUP $MAINPID
StandardOutput=null
Restart=always
RestartSec=30
RestartPreventExitStatus=2
TimeoutStartSec=25
# Restrict permissions:
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=strict
ReadWritePaths=/run/
# /etc/ppp/ip-up.d scripts need to write to /sys:
ProtectKernelTunables=no
ProtectControlGroups=yes
# Need @module to load nft modules:
SystemCallFilter=@system-service @module
SystemCallArchitectures=native
LockPersonality=yes
MemoryDenyWriteExecute=yes
RestrictRealtime=yes

[Install]
WantedBy=sys-devices-virtual-net-%i.device
WantedBy=network-online.target
# /etc/ppp/peers/ppp0

# The username is required for internode.
# There should be a matching entry in /etc/ppp/pap-secrets,
# even though the password is not used.
user "xxx@internode.on.net"

# Required for PPPoE:
noaccomp
default-asyncmap
mtu 1492
plugin pppoe.so eth1.2

# Let systemd-networkd configure IPv6.
+ipv6

Enabling routing and NAT

# /etc/sysctl.d/local.conf
# Maybe the same effects could be achieved via /etc/systemd/networkd.conf
net.ipv4.ip_forward=1
net.ipv6.conf.default.forwarding=2
net.ipv6.conf.all.forwarding=2
#!/bin/sh
# /etc/ppp/ip-up.d/nft
# Note this is an illustrative example of enabling NAT - not a functional firewall!
/sbin/modprobe -v -a nfnetlink nf_tables nf_nat || exit 1

/usr/sbin/nft -f - <<END_NFT
add table ip nat
add chain ip nat postrouting { type nat hook postrouting priority 100; policy accept; }
add rule ip nat postrouting oifname $PPP_IFACE masquerade
add rule inet filter input iif $PPP_IFACE ct state related,established accept
END_NFT

Leave a Reply

Your email address will not be published. Required fields are marked *