iptables for 2 public interfaces

Had to install a router to connect the internal LAN to the Internet. Since there was an ancient server just idling around, I wanted to try to setup the connection via that machine before coughing the money for a proper router.

First step, I set up Debian squeeze x64 on the server.

Off to iptables!

The Setup

  • eth0 connected to the LAN, ip 192.168.0.1/16, the router machine is the gateway, .1
  • eth1 connected to fiber, public ip 111.111.111.111/24 - high priority traffic only, gateway at .1
  • eth2 connected to DSL, public ip 88.88.88.88/24 - this will be the default Internet connection, gateway at .1

Note that the numbers are chosen just as an example.

There's also another app server for hosting stuff inside the LAN. This one is intended to be accessible via the Intenet, doesn't really matter on which interface.

The Filtering Table

First, drop almost everything through the filter table:

iptables -t filter -P INPUT DROP
iptables -t filter -P FORWARD DROP
iptables -t filter -P OUTPUT ACCEPT

So basically accept all outgoing traffic, while rejecting anything else (incoming traffic or traffic being routed by the machine).

Accept server connections initiated from this machine (the router):

iptables -t filter -A INPUT -i lo -j ACCEPT

Since the server will act as a router, accept connections from the local network:

iptables -t filter -A INPUT -i eth0 -j ACCEPT

Also accept replies to connections initiated by machines inside the LAN; note that these packets arrive as replies to conversations initiated on the inside network:

iptables -t filter -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

For debugging reasons, I'm also allowing pings:

iptables -t filter -A INPUT -p icmp -j ACCEPT

Given the fact that the nat table is going to route the internal connections through the fiber - eth1 - and through the DSL line - eth2 - let packets pass through the FORWARD chain accordingly.

LAN through fiber:

iptables -t filter -A FORWARD -i eth0 -o eth1 -j ACCEPT

LAN through DSL:

iptables -t filter -A FORWARD -i eth0 -o eth2 -j ACCEPT

Now, keep in mind that after nat does its job, there will be packets going into filter:FORWARD that originate in eth1 and eth2 and are addressed to eth0:

iptables -t filter -A FORWARD -i eth1 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

A similar rule should be in place for eth2 - the DSL line, but I decided to expose the internal app server on that public IP address (88.88.88.88), so I'll just accept everything:

iptables -t filter -A FORWARD -i eth2 -o eth0 -j ACCEPT

Since the packets can enter and pass through the router OK now, time to setup the logic for diverting them to their appropriate destination.

NAT and Mark

Packets exiting the router from eth1 should be SNATted as from 111.111.111.111, and the ones from eth2 as 88.88.88.88.

iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 111.111.111.111
iptables -t nat -A POSTROUTING -o eth2 -j SNAT --to-source 88.88.88.88

Also, since the app server connected in the LAN to this router will be visible on the Internet at 88.88.88.88, DNAT packets entering the router from the DSL line:

iptables -t nat -A PREROUTING -i eth2 -j DNAT --to-destination 192.168.0.3

The thing is to use the mark table to signal high priority connections. For example, Remote Desktop Protocol (port 3389):

iptables -t mangle -A PREROUTING -p tcp -m tcp -s 192.168.0.0/16 --dport 3389 -j MARK  --set-mark 1

So a connection request reaches the router from the LAN, connection directed to some Internet address, port 3389. That connection will be marked. The problem is now to route it through eth1 instead of the default eth0. That is done via iproute2.

First thing, a prioritized routing table, in /etc/iproute2/rt_tables:

#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
101     prioritized

Then define the table; add all the rules from main that don't concern default routes or the fiber channel (it will be constructed manually below):

ip route show table main | grep -Ev '(^default|eth1)' | while read ROUTE ; do
    ip route add table prioritized $ROUTE
done

ip route add table prioritized 111.111.111.1 dev eth1 src 111.111.111.111
ip route add table prioritized default via 111.111.111.1

In the above example, I am using 111.111.111.1 as the gateway address supplied by the ISP for 111.111.111.111.

This should be the output of ip route show table main:

111.111.111.0/24 dev eth1  proto kernel  scope link  src 111.111.111.111
88.88.88.0/24 dev eth2  proto kernel  scope link  src 88.88.88.88
192.168.0.0/16 dev eth0  proto kernel  scope link  src 192.168.0.1
default via 88.88.88.1 dev eth2

And for ip route show table prioritized:

111.111.111.1 dev eth1  scope link  src 111.111.111.111
88.88.88.0/24 dev eth2  proto kernel  scope link  src 88.88.88.88
192.168.0.0/16 dev eth0  proto kernel  scope link  src 192.168.0.1
default via 111.111.111.1 dev eth1

Packets marked with 0x1 from mangle will be routed with the prioritized table

ip rule del fwmark 1
ip rule del from 111.111.111.111
ip rule add fwmark 1 table prioritized
ip rule add from 111.111.111.111 lookup prioritized

The mark flag is decimal in iptables, and hex in iproute; look it up! Obviously, 0x1 == 1.

Now everything should be set!