LinuxXXI · Advanced Linux NetworkingPolicy routing
Policy routing - choosing routes by source, mark, or interface
What you'll learn
- Explain the difference between destination routing and policy routing
- Use ip rule to add source-based, mark-based, and interface-based rules
- Mark packets with iptables or nftables to drive routing
- Recognise when policy routing is the right tool
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
Destination routing (the default) picks a route based on the destination IP. Policy routing picks a route based on any combination of source IP, incoming interface, packet mark, or other criteria. Policy routing is the tool for multi-homing, tenant isolation, and traffic steering.
Rule selectors
ip rule add accepts many selectors:
| Selector | Meaning |
|---|---|
from <prefix> | Source IP in prefix |
to <prefix> | Destination IP in prefix |
iif <iface> | Incoming interface |
oif <iface> | Outgoing interface (rarely matches) |
mark <value> | Packet mark (set by iptables/nftables) |
fwmark <value> | Same as mark, alias |
tos <value> | Type of Service |
uid <range> | Local UID range |
sport <range> | Source port |
dport <range> | Destination port |
Selectors can be combined:
ip rule add from 10.0.0.0/24 iif eth0 lookup 100 priority 100
A multi-uplink example
A host has two uplinks:
- Uplink A: eth0, 10.0.0.10/24, default route via 10.0.0.1.
- Uplink B: eth1, 192.168.1.10/24, default route via 192.168.1.1.
You want:
- Replies to traffic from Uplink A to go out Uplink A.
- Replies to traffic from Uplink B to go out Uplink B.
- New connections to use Uplink A by default.
This is the “two default routes” problem. The solution is policy routing:
echo "100 backup" >> /etc/iproute2/rt_tables
ip route add 192.168.1.0/24 dev eth1 src 192.168.1.10 table backup
ip route add default via 192.168.1.1 dev eth1 table backup
ip rule add from 192.168.1.10 lookup backup priority 100
ip route add default via 10.0.0.1 dev eth0 metric 100
Verify:
ip route get 8.8.8.8 from 10.0.0.10
ip route get 8.8.8.8 from 192.168.1.10
The two queries should pick different routes.
Mark-based routing
A packet mark is a 32-bit value attached to a packet in
the netfilter hook. The mark can be set by iptables or
nftables and read by ip rule. This lets the firewall
decide routing.
sudo nft add table inet mark_table
sudo nft add chain inet mark_table prerouting { type filter hook prerouting priority -150; policy accept; }
sudo nft add rule inet mark_table prerouting iif eth0 tcp dport 8080 meta mark set 0x10
sudo nft add rule inet mark_table prerouting iif eth0 tcp dport 8443 meta mark set 0x20
ip rule add fwmark 0x10 lookup 100 priority 100
ip rule add fwmark 0x20 lookup 200 priority 100
Now traffic to TCP/8080 follows table 100, and traffic to
TCP/8443 follows table 200. The firewall is steering
traffic into different routing tables.
Use cases
Policy routing is the right tool when:
- Multi-homing: more than one uplink with different characteristics.
- Tenant isolation: routes for one tenant’s traffic should not affect another’s.
- Service-specific routing: “database traffic should always go via the storage VLAN”.
- Source-based routing for SNAT/MASQUERADE: a NAT gateway with multiple external IPs.
- Steering based on port or mark: routing web traffic differently from backup traffic.
It is not the right tool when:
- One interface, one route - use the main table.
- The decision is per-destination only - the main table is enough.
- The complexity can be avoided with a different network design.
Common pitfalls
- Asymmetric routing: incoming traffic on eth0, outgoing
reply on eth1. Breaks connection tracking and many
firewalls. Test with
ip route getfrom every source. - Reverse-path filtering: see the callout below. This is the single most common reason a correct-looking second uplink silently receives nothing.
- Existing flows keep their old path: the IPv4 route cache
was removed in kernel 3.6, so there is nothing to flush -
ip route flush cacheis a no-op on any kernel you will meet in production. What does persist is conntrack state and already-established sockets, which keep using the path chosen when they were created. New rules apply to new flows. If a change appears not to take effect, check the rule priority order (ip rule show), confirm the target table actually holds a matching route (ip route show table 100), and checkrp_filter- do not reach for a cache flush. - Marks not surviving: a mark set in
preroutingis visible to routing. A mark set ininputis not visible to routing. Place marks in the right table.
Knowledge check
Knowledge check · 3 questions
Q1. You add `ip rule add from 192.168.1.10 lookup backup priority 100`, but traffic still leaves via the primary uplink. What should you check first?
Q2. Policy routing can use packet marks set by nftables to choose a route.
Q3. Which of the following are valid ip rule selectors? Select all that apply.
Passing score: 75%. Answers are checked in this browser.