How to Persistently Enable IP Forwarding for Dynamic Interfaces in Linux with Systemd


4 views

When working with dynamic network interfaces like VPN tunnels (tun0) or temporary interfaces, traditional methods of enabling IP forwarding fall short. The main challenge comes from:

# These fail during early boot:
net.ipv4.conf.enp0s3.forwarding=1
net.ipv4.conf.tun0.forwarding=1

For systemd-based systems (v229+), we can leverage networkd-dispatcher or custom units to handle dynamic interfaces:

# /etc/systemd/network/99-ipforwarding.network
[Match]
Name=enp0s3 tun0

[Network]
IPForward=yes

For physical interfaces that persist across reboots:

# /etc/sysctl.d/99-ipforward.conf
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv4.conf.all.rp_filter=2

Create a udev rule for interface hotplug events:

# /etc/udev/rules.d/99-ipforward.rules
ACTION=="add", SUBSYSTEM=="net", \
    RUN+="/usr/bin/sh -c 'echo 1 > /proc/sys/net/ipv4/conf/%k/forwarding'"

After implementing any solution, verify with:

sudo sysctl -p
ip route show table all
cat /proc/sys/net/ipv4/conf/*/forwarding | grep -v 1

If forwarding still fails:

  1. Check iptables/nftables rules: sudo iptables -L -v -n
  2. Verify interface MTU settings: ip link show dev tun0
  3. Test basic connectivity: ping -I enp0s3 8.8.8.8

When enabling IP forwarding in Linux, many administrators encounter a frustrating scenario: while net.ipv4.ip_forward=1 gets set globally, individual interfaces (especially dynamic ones like VPN tunnels) may still block forwarding. Here's what's happening under the hood:

# Typical sysctl.conf entry
net.ipv4.ip_forward = 1
net.ipv4.conf.default.forwarding = 1
net.ipv4.conf.all.forwarding = 1

Yet checking interface-specific settings reveals the problem:

cat /proc/sys/net/ipv4/conf/enp0s3/forwarding
0
cat /proc/sys/net/ipv4/conf/tun0/forwarding
0

The root cause lies in the timing of systemd-sysctl service execution. During early boot:

  • Persistent network interfaces (like enp0s3) may not be fully initialized
  • Dynamic interfaces (like tun0) don't exist yet

Systemd logs reveal the failure:

journalctl -u systemd-sysctl | grep forwarding
systemd-sysctl[85]: Couldn't write '1' to 'net/ipv4/conf/enp0s3/forwarding'
systemd-sysctl[85]: Couldn't write '1' to 'net/ipv4/conf/tun0/forwarding'

Method 1: NetworkManager Dispatcher Script (Recommended for Dynamic Interfaces)

Create a script that triggers whenever interfaces change:

#!/bin/bash
# /etc/NetworkManager/dispatcher.d/99-ip-forwarding

INTERFACE="$1"
ACTION="$2"

if [ "$ACTION" = "up" ]; then
    echo 1 > /proc/sys/net/ipv4/conf/${INTERFACE}/forwarding
fi

Make it executable:

chmod +x /etc/NetworkManager/dispatcher.d/99-ip-forwarding

Method 2: Systemd Service for Persistent Interfaces

For static interfaces, create a service that runs after networking:

[Unit]
Description=Set interface forwarding
After=network.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo 1 > /proc/sys/net/ipv4/conf/enp0s3/forwarding"

[Install]
WantedBy=multi-user.target

Method 3: Udev Rule for All Interface Events

This catches both existing and new interfaces:

# /etc/udev/rules.d/99-ip-forwarding.rules
ACTION=="add", SUBSYSTEM=="net", RUN+="/bin/sh -c 'echo 1 > /proc/sys/net/ipv4/conf/%k/forwarding'"

After implementing any solution, verify with:

# Check global setting
sysctl net.ipv4.ip_forward

# Check all interfaces
find /proc/sys/net/ipv4/conf/ -name forwarding -exec sh -c 'echo "$1: $(cat $1)"' sh {} \;

For temporary testing, you can manually enable forwarding:

# For existing interfaces
for intf in /proc/sys/net/ipv4/conf/*/forwarding; do
    echo 1 > "$intf"
done

# For new interfaces (run after creation)
echo 1 > /proc/sys/net/ipv4/conf/tun0/forwarding

While these methods work, consider these additional sysctl parameters for complete forwarding:

# /etc/sysctl.d/99-ip-forward.conf
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_redirects = 0