What is policy routing on Linux, and how do ip rule and multiple routing tables work together?
Show the answer
Say this first
Linux can hold up to 2^32 routing tables, and ip rule decides which table a packet is looked up in. Rules
are checked in priority order and match on source, destination, incoming or outgoing interface, firewall
mark, user ID or TOS. The default rules are priority 0 lookup local, 32766 lookup main and 32767
lookup default. If the chosen table has no matching route, the kernel moves on to the next rule.
Go deeper
Default state
$ ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup defaultTable IDs: local 255, main 254, default 253. Names live in /etc/iproute2/rt_tables (or a file in
rt_tables.d/), but you can always use the number.
Adding a table and a rule
ip route add default via 203.0.113.9 dev ens224 table 200
ip rule add from 203.0.113.10 lookup 200 priority 1000
ip rule add fwmark 0x2 lookup 200 priority 1100
ip rule add iif ens256 lookup 200 priority 1200$ ip rule
0: from all lookup local
1000: from 203.0.113.10 lookup 200
1100: from all fwmark 0x2 lookup 200
1200: from all iif ens256 lookup 200
32766: from all lookup main
32767: from all lookup defaultSelectors and actions
- Selectors:
from,to,iif,oif,fwmark,tos,uidrange, andnotto invert. - Actions:
lookup <table>, orblackhole,unreachable,prohibitto drop. suppress_prefixlength 0: use the table but ignore a default route found in it.wg-quickuses this withnot fwmark 51820 lookup 51820so WireGuard can take the default route without looping its own encrypted packets into the tunnel.
Where it is used
Two uplinks with symmetric return traffic, sending one subnet or one application out a different ISP, VPN split tunnelling, and Kubernetes and cloud CNIs that give each secondary interface its own table.
They will ask next
Do you need to flush a route cache after changing rules?
Not on modern kernels. The IPv4 routing cache was removed in kernel 3.6, so rules and routes apply to the next packet. Existing NAT and conntrack entries still keep their old translations, though.
What happens if the table a rule points at has no route for the destination?
The lookup in that table fails and the kernel continues with the next rule. That is why a table with a few specific routes and no default lets all other traffic fall through to
main, and why a missing route sometimes "works" through the wrong table. A table with a default route matches everything.
Traps
- Thinking
ip routeshows everything. It only shows table main. - Putting a rule at a priority above 32766 and wondering why main always wins.