Debian - Source Routing

Debian - Source Routing

Policy Based Routing - PBR

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.

In the Cisco IOS, PBR is implemented using route maps. Linux supports multiple routing tables since version 2.2. FreeBSD supports PBR using either IPFW, IPFilter or OpenBSD's PF.

Policy Based Routing in Linux


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
  • default

Routes added to the linux system without a routing table specified go to the main table, 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  

Rule with smaller ID number has greater priority than rule with greater number ID. Rule with ID 0 has greatest priority.

Setup source routing in Debian linux


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.

![example network topology](/img/post/posts/0084/source routing.svg "source routing example network topology")

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
...

Creating a Custom Policy Routing Table


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:

/etc/iproute2/rt_tables
#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
100 customer1
101 customer2

Creating Policy Routing Rules


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 172.32.1.0/24 lookup customer1
debian@router:~] ip rule add prio 100 from 172.32.2.0/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 172.32.1.0/24 lookup customer1 
100:    from 172.32.2.0/24 lookup customer1 
32766:  from all lookup main 
32767:  from all lookup default

Populating the Routing Table


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

Permanent settings


IP packets from customer1 are served with ip rule with ID 100 (from 172.32.1.0/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