christoph ender's

blog

wednesday the 27th of may, 2026

using fwmark for routing

After getting my LTE emergency link online last weekend, I wanted to be able to access my system remotely over the new link. The goal is to SSH into my system from anywhere in case the main link does not work.

Usually, logging in might be as simple as obtaining the public IP of one's internet connection, making SSH accessible from the outside and then pointing the SSH client at the public IP. For mobile connections, however, providers often use carrier-grade NAT, which means that the public IP of the mobile link is shared between multiple users, mostly due to IPv4 address exhaustion. This is also the case for my LTE connection, so a direct connection from outside is not possible.

In order to work around this, the only thing I could think of was to keep a tunnel permanently active. The tunnel is initiated from the LTE side of the link at home and connects to a cloud instance I'm permanently using. The problem is that there are two active routes at home – the standard default route over the fibre connection and the second one, with a larger metric value, pointing at the LTE link. That means that initiating a tunnel to the outside automatically uses the fibre connection, not LTE. While that would still allow access to the home system when the main link is down, I wanted to keep the tunnel over LTE available at all times.

It turns out that WireGuard supports FwMark settings. This “firewall mark” is simply a value attached to the packet – as long as the packet is handled by the system which has assigned it – and can be used in firewall rules or ip rule settings. In order to make use of this I set up an LTE-specific routing table.

First, I changed /etc/iproute2/rt_tables accordingly by adding a new routing table using ID 100:

#
# reserved values
#
255	local
254	main
253	default
0	unspec
#
# local
#
#1	inr.ruhep
100	lte1

Second, the WireGuard interface gets an FwMark setting to mark all outgoing packets with the fwmark value 10240:

[Interface]
PrivateKey = AH/Tr8nGx/6UUgNzadIAeKjunVgP0a8w6AxvNSexCm0=
Address = 192.168.101.2
FwMark = 10240
PostUp = systemctl restart ssh.service

[Peer]
PublicKey = SAnr+zy3mWU28p0Wj3q2CeLWP7vbjSKJpwVonjXpLmk=
AllowedIPs = 192.168.101.1
Endpoint = 192.0.2.10:51820
PersistentKeepalive = 25

Third, ip rule is used to route packets that have the 10240 fwmark assigned:

ip rule add fwmark 10240 table lte1

And last, we need to add a default route to the still empty “lte1” routing table:

ip route add default via 192.168.0.1 dev eth1 table lte1

At this point, all outgoing WireGuard packets are routed using the new “lte1” routing table. Since the table only contains a single default route, all outgoing WireGuard packets for the connection defined above always travel through the 192.168.0.1 LTE gateway. Using the new tunnel, the home system, reachable via 192.168.101.2, is now accessible from the cloud instance.