Policy-based routing with Debian Lenny
This blog post is dedicated to explaining what policy-based routing (PBR) is and how it can work in Debian GNU/Linux. There are many articles and tutorials about this topic, but I wasn’t able to find complete and up-to-date source of information. Since it took many moons for me to figure out the right setup I decided to document the whole thing for everyone else that may need to do the same thing.
This guide is written with Debian Lenny in mind, and tries to be complete and easy to understand. Nevertheless you will need some basic network skills to get through it.
So, what is PBR? And do I need it?
Routers don’t usually need to make difficult choices: they often connect a network to another one, for example your LAN to the Internet, and just need the so-called static routing. In simple words, they just look at the recipient of the packet and forward it to the next router on the right channel.
The problem arises when a router is connected to the same network through two or more different channels, and needs to choose which route is the best one based on some predefined criteria. This is what PBR is about. PBR could act, for example, as a load-balancer for high-demand networks, or to route preferential traffic on dedicated channels. We will cover the latter case, since the former is much more trivial to implement.
The right tools for the job
There are two tools we will need to get the job done, iproute2 and iptables: iproute2 takes care of all the routing part, while iptables, traditionally used as a firewall, has the task to tell iproute2 which packets go to which channel.
For simpler cases the sole iproute2 is needed, since it already allows to define basic rules, like discriminating on the sender of a particular packet, but sometimes this is isn’t enough. We want to be able to decide routing for every single packet without limits on the rule to follow. This is where iptables comes into play: it will use its powerful packet matching abilities to mark packages and help iproute2 taking decisions.
Our setup
We will work with a machine that acts as a firewall/router for a LAN and has two available uplinks. The LAN interface will be eth0, while the WAN interfaces will be eth1 and eth2. Let’s suppose we want to route all the web traffic from the LAN through eth1, while forwarding all the rest through eth2.
For simplicity, eth0 address will be 192.168.0.1, eth1 will be 192.168.1.1 and eth2 will be 192.168.2.1. All networks will have a netmask of 255.255.255.0, and their next hop will be a machine on the same network with an IP address of 192.168.x.2.
The LAN will be masqueraded by the firewall/router with iptables, as this is the most common case in home and company networks.
The hard work
Creating the routing tables
First we need to define two new routing tables, one for every outgoing line. Let’s modify /etc/iproute2/rt_tables and add a couple of lines under the local section, so that it looks this way:
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
100 web
200 rest
Here we have defined two tables called web and rest, respectively with an ID of 100 and 200. IDs are arbitrary, just stay within the 1-252 range. I chose 100 and 200 because they allow for further tables to be added before or after them.
Setting up routing
Next step is explaining Debian which routes are available and how routing should be done. The file we need to modify is /etc/network/interfaces, and these are the relevant parts:
auto lo eth0 eth1 eth2
allow-hotplug eth0
allow-hotplug eth1
allow-hotplug eth2iface lo inet loopback
post-up ip route add 127.0.0.0/8 dev lo table web
post-up ip route add 127.0.0.0/8 dev lo table restiface eth0 inet static
address 192.168.0.1
netmask 255.255.255.0
post-up ip route add 192.168.0.0/24 dev eth0 table web
post-up ip route add 192.168.0.0/24 dev eth0 table restiface eth1 inet static
address 192.168.1.1
netmask 255.255.255.0
post-up ip route add 192.168.1.0/24 dev eth1 src 192.168.1.1 table web
post-up ip route add default via 192.168.1.2 table web
post-up ip rule add from 192.168.1.1 table web
post-up ip rule add fwmark 0×1 table webiface eth2 inet static
address 192.168.2.1
netmask 255.255.255.0
post-up ip route add 192.168.2.0/24 dev eth2 src 192.168.2.1 table rest
post-up ip route add default via 192.168.2.2 table rest
post-up ip route add default via 192.168.2.2
post-up ip rule add from 192.168.2.1 table rest
post-up ip rule add fwmark 0×2 table rest
WARNING: the interfaces file doesn’t support end-of-line comments, so don’t use them or you’ll probably break the setup!
WARNING #2: this is just a minimal setup, it’s missing the post-down commands to clean up routing tables and rules, this means that ‘/etc/init.d/networking restart‘ will only make a mess, and you’ll need a reboot or some commands to make everything work again.
What have we done here? After the usual headers, we told our two tables how to reach the loopback interface and the LAN. Then, the fun part: two similar sections were written for eth1 and eth2. The only real difference is in the default route: we want eth2 to be the default, 192.168.2.2 being the default gateway, while eth1 should be a dedicated channel for web traffic.
Four post-up commands, which will be executed as the relative interface come up, are “shared” between the two interfaces. Taking eth1 as an example, the first command defines that this interface is attached to the 192.168.1.0/24 network, that all packets going to that network should have a sender address of 192.168.1.1 and that they should be routed through the table web. The second one says that the default gateway for the web table is 192.168.1.2.
The first rule forces a packet with a defined sender address to come out on the right interface, while the second one routes all packets marked 0×1 through the web table.
Marking packets
If you got through here and your network was successfully started with all the rules, then the worst part is done. Now let’s see where the magic happens: iptables. Our firewall script will have to contain a couple simple rules:
iptables -t mangle -A PREROUTING -p tcp –sport 80 -j MARK –set-mark 0×1
iptables -t mangle -A PREROUTING -p tcp –dport 80 -j MARK –set-mark 0×1
This will ensure that all traffic coming from or going to port 80 will be marked with 0×1, which corresponds to our web routing table. And this is it: iproute2 will see the mark (which is at operating system level, it doesn’t modify the packet in any way), assign the packet the correct source address and route it through the right interface.
Neat, isn’t it?
Conclusions
PBR is a very powerful tool that allows us to define routing in every little detail. Even though it can be a bit hard to setup, it proves to be invaluable in network environments which need a high level of customization, load-balancing or prioritization or certain kinds of traffic.
I hope you had a nice trip, and if you find something is missing or wrong (very easy) just leave a comment, I’ll try to fix it as soon as I can. Thanks for reading
References
- Linux Advanced Routing & Traffic HOWTO: surely the best PBR source around, a bit out of date, though
- Netfilter.org: official iptables documentation
- Linux Networking 2: a router with port forwarding: very good distro-agnostic PBR howto, goes much more in deep than this one, but it’s less practical
Posted by bardo under english, g33k1ng around |