Policy routing with IP Filter on FreeBSD

In this post I’ll write about implementation of policy routing with IP Filter on FreeBSD. Policy routing is a process of forcing packets to follow a particular route not necessary through default gateway. This is very useful in a multihomed environment when your FreeBSD server acts as a router and you want different networks to be routed differently based on a source network or interface.

Let’s take a look at the following case study put into production recently.

We own a server running FreeBSD 7.0-STABLE and acting as a router for one of our projects. There are three network interfaces in this server:

  1. tl0 — has a public IP address assigned (XXX.YYY.ZZZ.35 / The default gateway (XXX.YYY.ZZZ.33) is from the same subnet where tl0 belongs to.

  2. fxp0 — has a public IP address assigned as well (XXX.YYY.ZZZ.50 / Note that this is the same network but different subnet. The next hop for this subnet is XXX.YYY.ZZZ.49.

  3. fxp1 — has a private IP address ( / and connected to our private LAN.

We want to NAT network and route all outgoing packets through fxp0 interface. tl0 will be only used for providing http services for outside visitors, but all outgoing traffic should be routed through fxp0. The kernel was compiled with IP Filter support:

  1. %ipf -V
  2. ipf: IP Filter: v4.1.28 (404)
  3. Kernel: IP Filter: v4.1.28              
  4. Running: yes
  5. Log Flags: 0 = none set
  6. Default: block all, Logging: available

Here we go with the implementation.

  1. %cat /etc/rc.conf
  2. gateway_enable="YES"
  3. defaultrouter="XXX.YYY.ZZZ.33"
  4. ifconfig_tl0="inet XXX.YYY.ZZZ.35 netmask"
  5. ifconfig_fxp0="inet XXX.YYY.ZZZ.50 netmask"
  6. ifconfig_fxp1="inet netmask"
  7. ipfilter_enable="YES"
  8. ipfilter_rules="/etc/ipf.rules"
  9. ipmon_enable="YES"
  10. ipmon_flags="-Ds"
  11. ipnat_enable="YES"
  12. ipnat_rules="/etc/ipnat.conf"
  14. %cat /etc/ipnat.conf
  15. map fxp0> 0/32 portmap tcp/udp 30000:60000
  16. map fxp0> 0/32

This is how our routing table looks like:

  1. %netstat -rn
  2. Routing tables
  4. Internet:
  5. Destination        Gateway            Flags    Refs      Use  Netif Expire
  6. default            XXX.YYY.ZZZ.33     UGS         0    40285    tl0
  7.       link#3             UC          0        0   fxp1
  8.        ff:ff:ff:ff:ff:ff  UHLWb       1        7   fxp1
  9. XXX.YYY.ZZZ.32/28  link#1             UC          0        0    tl0
  10. XXX.YYY.ZZZ.33     00:09:7c:61:93:30  UHLW        2     1196    tl0   1118
  11. XXX.YYY.ZZZ.35     00:50:8b:50:f2:0e  UHLW        1     3271    lo0
  12. XXX.YYY.ZZZ.48/28  link#2             UC          0        0   fxp0
  13. XXX.YYY.ZZZ.50     00:02:b3:eb:cc:24  UHLW        1        3    lo0
  14.          UH          0     2193    lo0

IP Filter is configured with basic ruleset to protect tl0 and fxp0 from outside intrusions and all outgoing traffic is allowed from fxp1.

So far so good, but, it didn’t work. 10.0.14/24 users are not able to access outside resources, you don’t see anything NAT-ed on fxp0 interface and traceroute from 10.0.14/24 terminates on fxp1 interface:

  1. %ipnat -l
  2. List of active MAP/Redirect filters:
  3. map fxp0> portmap tcp/udp 30000:60000
  4. map fxp0>
  6. List of active sessions:

Here comes the beauty of policy routing. We need to add the following rule to our IP Filter ruleset:

  1. pass in quick on fxp1 from to
  2. pass in quick on fxp1 to fxp0:XXX.YYY.ZZZ.49 from to any keep state

The first rule will allow to utilize whatever resources you have configured on fxp1 interface without any redirecting. We use dnscache running on fxp1 and don’t want to forward incoming DNS traffic.

The second rule literally means the following. Whatever traffic hitting fxp1 interface and coming from network should be redirected or forwarded to fxp0 interface with the gateway set to XXX.YYY.ZZZ.49. And finally, we do keep state of the traffic to get it back.


Tags: , ,

Leave a Reply