In some circumstances we want to route packets differently depending not only on ip destination addresses, but also on other packet fields: source address, IP protocol, transport protocol ports or even packet payload. This task is called policy based routing - PBR.
When a router receives a packet it normally decides where to forward it based on the destination address in the packet, which is then used to look up an entry in a routing table. However, in some cases, there may be a need to forward the packet based on other criteria. For example, a network administrator might want to forward a packet based on the source address, not the destination address. This permits routing of packets originating from different sources to different networks even when the destinations are the same and can be useful when interconnecting several private networks.
Policy Based Routing - PBR in linux is based on routing tables and routing rules
Policy routing tables: When the linux kernel needs to make a routing decision, it finds out which table needs to be consulted. By default, there are three tables.
- local - which cannot be modified or deleted
- main - where routes is added when routing table is not specified
maintable, not the default or local table.
Policy routing rules: Again, Linux comes with default three rules, one for each of the default routing tables.
debian@router:~] ip rule show 0: from all lookup local 32766: from all lookup main 32767: from all lookup default
This lists the priority of all rules. We see that all rules apply to all packets ('from all'). Rule with smaller number has greater priority.
When you want see routes in another routing table than main:
debian@router:~] ip route show table local broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 broadcast 192.168.221.0 dev ens192 proto kernel scope link src 192.168.221.221 local 192.168.221.221 dev ens192 proto kernel scope host src 192.168.221.221 broadcast 192.168.221.255 dev ens192 proto kernel scope link src 192.168.221.221 broadcast 192.168.222.0 dev ens224 proto kernel scope link src 192.168.222.222 local 192.168.222.222 dev ens224 proto kernel scope host src 192.168.222.222 broadcast 192.168.222.255 dev ens224 proto kernel scope link src 192.168.222.222
We have this network topology with two customer's networks and two internet service providers. Our goal is configure debian/ubuntu router this way:
- customer 1 uses ISP-1
- customer 2 uses ISP-2.
We have multiple network interfaces in debian router. But 2 interfaces are connected to ISP:
debian@router:~] ip addr show ... 2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:50:56:8f:77:f5 brd ff:ff:ff:ff:ff:ff inet 192.168.221.221/24 brd 192.168.221.255 scope global ens192 valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe8f:77f5/64 scope link valid_lft forever preferred_lft forever 3: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 00:50:56:8f:65:d2 brd ff:ff:ff:ff:ff:ff inet 192.168.222.222/24 brd 192.168.222.255 scope global ens224 valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe8f:65d2/64 scope link valid_lft forever preferred_lft forever ...
The first step is to create a custom policy routing table. Each table is represented by an entry in the file /etc/iproute2/rt_tables, so creating a new table is generally accomplished using a command like this:
echo 200 my_table >> /etc/iproute2/rt_tables
This creates the table with the ID 200 and the name my_table. You’ll reference this name later as you create the rules and populate the table with routes, so make note of it. Because this entry is contained in the rt_tables file, it will be persistent across reboots.
For our goal, we need create two new tables - one table for each for our customers.
debian@router:~] echo 100 customer1 >> /etc/iproute2/rt_tables debian@router:~] echo 101 customer2 >> /etc/iproute2/rt_tables
The content of rt_tables file now:
# # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep 100 customer1 101 customer2
The next step is to create the policy routing rules that will tell the system which table to use to determine the correct route. In this particular case, I’m going to use the source address (i.e., the originating address for the traffic) as the determining factor in the rule. This is a common application of policy routing, and for that reason it’s often referred to as source routing.
To create the policy routing rule,we can use this command:
ip rule add from <source address> lookup <table name or table ID>
But I recommend using a ip command containing a rule priority:
ip rule add prio <priority> from <source address> lookup <table name or table ID>
We need two rules:
debian@router:~] ip rule add prio 100 from 184.108.40.206/24 lookup customer1 debian@router:~] ip rule add prio 100 from 220.127.116.11/24 lookup customer1
You can see all the policy routing rules that are currently in effect using this command:
debian@router:~] ip rule show 0: from all lookup local 100: from 18.104.22.168/24 lookup customer1 100: from 22.214.171.124/24 lookup customer1 32766: from all lookup main 32767: from all lookup default
Once we have the custom policy routing table created and a rule defined that directs the system to use it, we need to populate the table with the correct routes. The generic command to do this is the ip route add command, but with a specific table parameter added.
ip route add <our route> via <next hop> table <table name or table ID>
For our goal we must populate two routes:
debian@router:~] ip route add 0.0.0.0/0 via 192.168.221.1 table customer1 debian@router:~] ip route add 0.0.0.0/0 via 192.168.222.1 table customer2
Check the execution of previous commands:
debian@router:~] ip route show table customer1 default via 192.168.221.1 dev ens192
debian@router:~] ip route show table customer2 default via 192.168.222.1 dev ens224
IP packets from customer1 are served with ip rule with ID 100 (from 126.96.36.199/24 lookup customer1) and not with ip rule with ID 32766 (from all lookup main). It is because rule with ID 100 has higher priority that rule with ID 32766. And table customer1 has route that says: default route is via next hop with ip address 192.168.221.1.
The same applies to customer 2.
But we have a small problem. Routes and Rules added this way with ip command are not persistent. How make ip rules persistent you can learn in my previous article Debian Permanent Static Routes