Multi-homed hosts

Sometimes it can be useful to connect a computer system to more than one network at a time. Such a system is referred to as a multi-homed host. Hosts with more than one interface can be further categorised into those which act as routers between the networks to which they are connected and those which don't.

Filtering by interface

As we have seen, all multi-homed hosts have more than one network interface by definition. It is therefore obvious that sometimes it will be useful to be able to filter the incoming and outgoing traffic based on the interface it will traverse. To accomplish this task the Netfilter system includes two match specifiers which can be used to match packets coming in to and going out of an interface respectively.

Caution:
It should be obvious that packets coming in to an interface have already traversed the interface in question whereas packets which are going out of an interface have merely been assigned to an interface by the routing subsystem. What is perhaps less obvious is that mangling a packet can cause the routing decision to change and thus make packets appear more than once on a filter chain or even leave by a different interface than that specified when filtering was applied.
 

We have already used both of these rules when configuring the firewall to allow traffic to traverse the loop-back interface, the code for which is reproduced below.

iptables -A INPUT  --in-interface  lo -j ACCEPT
iptables -A OUTPUT --out-interface lo -j ACCEPT

Depending on the expected traffic, and the policy which is to be enforced, there are a variety of ways in which rules can be organised. If all interfaces will accept the same traffic then there is probably no need to use an interface specifier at all. If, on the other hand, one interface will be used for one pattern of access and another for a different pattern of access then it may be more sensible to divide the traffic early on and have separate chains to perform filtering for each interface.

This can be achieved by creating separate filter chains for each interface and specifying an interface when binding them to the appropriate protocol filter chain. An example of this approach is shown below.

/usr/local/sbin/config-firewall
LAN_IF='eth1'
WAN_IF='eth0'

...

iptables -N TCPCHECK
iptables -A TCPCHECK -p tcp ! --syn -m state --state NEW -j LOGDROP
iptables -A TCPCHECK -m state --state INVALID -j LOGDROP

iptables -N TCP-IN-REQ
iptables -N TCP-OUT-RESP
iptables -N TCP-OUT-REQ
iptables -N TCP-IN-RESP

iptables -N WAN-TCP-IN-REQ
iptables -N WAN-TCP-OUT-RESP
iptables -N WAN-TCP-OUT-REQ
iptables -N WAN-TCP-IN-RESP

iptables -A WAN-TCP-IN-REQ -p tcp --dport http -m state --state NEW -j ACCEPT
iptables -A WAN-TCP-IN-REQ -p tcp --dport http -m state --state ESTABLISHED -j ACCEPT
iptables -A WAN-TCP-OUT-RESP -p tcp --sport http -m state --state ESTABLISHED -j ACCEPT

...more ACCEPT rules as required...

iptables -N LAN-TCP-IN-REQ
iptables -N LAN-TCP-OUT-RESP
iptables -N LAN-TCP-OUT-REQ
iptables -N LAN-TCP-IN-RESP

iptables -A LAN-TCP-IN-REQ -p tcp --dport ssh -m state --state NEW -j ACCEPT
iptables -A LAN-TCP-IN-REQ -p tcp --dport ssh -m state --state ESTABLISHED -j ACCEPT
iptables -A LAN-TCP-OUT-RESP -p tcp --sport ssh -m state --state ESTABLISHED -j ACCEPT

...more ACCEPT rules as required...

iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK
iptables -A TCP-IN-FILTER -j TCP-IN-REQ
iptables -A TCP-IN-FILTER -j TCP-IN-RESP
iptables -A TCP-IN-FILTER -i $WAN_IF -j WAN-TCP-IN-REQ
iptables -A TCP-IN-FILTER -i $WAN_IF -j WAN-TCP-IN-RESP
iptables -A TCP-IN-FILTER -i $LAN_IF -j LAN-TCP-IN-REQ
iptables -A TCP-IN-FILTER -i $LAN_IF -j LAN-TCP-IN-RESP

iptables -N TCP-OUT-FILTER
iptables -A TCP-OUT-FILTER -j TCPCHECK
iptables -A TCP-OUT-FILTER -j TCP-OUT-REQ
iptables -A TCP-OUT-FILTER -j TCP-OUT-RESP
iptables -A TCP-OUT-FILTER -o $WAN_IF -j WAN-TCP-OUT-RESP
iptables -A TCP-OUT-FILTER -o $WAN_IF -j WAN-TCP-OUT-REQ
iptables -A TCP-OUT-FILTER -o $LAN_IF -j LAN-TCP-OUT-RESP
iptables -A TCP-OUT-FILTER -o $LAN_IF -j LAN-TCP-OUT-REQ

...

As you can see we have used the existing TCP-IN-FILTER and TCP-OUT-FILTER chains which we created earlier so that all packets are checked against the TCP-CHECK chain of rules regardless of the interface they are traversing. We have then used the abbreviated form of the match specifiers discussed above to separate the traffic based on interface at the earliest opportunity. Whilst we have not shown the relevant code the same approach would be used to filter UDP traffic. ICMP traffic on the other hand can probably share the same set of rules in most cases and thus does not usually need to be separated by interface.

Information:
In the above example we have placed the rule to separate WAN traffic before the rule to separate LAN traffic as we expect the WAN to see more packets. If you were configuring a system where this was reversed then the optimal order of these rules would also be reversed.
 

If traffic patterns are generally very similar, differing for only a few rules, it may make more sense to add the interface match specifier directly to the rule in question. An example of this approach is not really required but a simple example is included below for completeness.

/usr/local/sbin/config-firewall
LAN_IF='eth1'
WAN_IF='eth0'

...

iptables -N TCP-IN-REQ
iptables -N TCP-OUT-RESP
iptables -N TCP-OUT-REQ
iptables -N TCP-IN-RESP

iptables -A TCP-IN-REQ -i $WAN_IF -p tcp --dport http -m state --state NEW -j ACCEPT
iptables -A TCP-IN-REQ -i $WAN_IF -p tcp --dport http -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-OUT-RESP -o $WAN_IF -p tcp --sport http -m state --state ESTABLISHED -j ACCEPT

iptables -A TCP-IN-REQ -i $LAN_IF -p tcp --dport ssh -m state --state NEW -j ACCEPT
iptables -A TCP-IN-REQ -i $LAN_IF -p tcp --dport ssh -m state --state ESTABLISHED -j ACCEPT
iptables -A TCP-OUT-RESP -o $LAN_IF -p tcp --sport ssh -m state --state ESTABLISHED -j ACCEPT

iptables -N TCP-IN-FILTER
iptables -A TCP-IN-FILTER -j TCPCHECK

...

As you can see this accomplished exactly the same task as the previous example. Whilst it may look as if this approach is simpler, and the way in which we have presented it certainly adds to this effect as we have not needed to mark so many existing lines as removed, it requires far more packets to have their interface and state inspected than the previous approach. It is also far easier to make a mistake when specifying the interface direction using this approach. As such it is probably best avoided unless the rule-set is extremely small.

Filtering during routing

Figure 4
Figure depicting The filter table
The filter table

So far we have only covered how to filter packets which are destined for, or are generated by, the system on which the firewall resides. We have accomplished this by adding rules to the INPUT and OUTPUT chains of the FILTER table. Some multi-homed hosts also act as routers between the networks to which they are connected however. In this case it can be useful to be able to control the traffic which is routed between the networks. For this reason the FILTER table has an additional built-in chain called FORWARD.

Any packets which are being routed through this host will pass through the FILTER chain. For this reason the only method we can use to identify the direction of the traffic is to use the interface matchers described earlier. Lets begin therefore by adding some rules to provide the infrastructure we need to more easily organise our filtering rules. We shall assume that we are building a firewall for a host acting as a router between two networks called LAN and WAN respectively.

TCP based protocols

/usr/local/sbin/config-firewall
iptables -N WAN-TO-LAN-TCP
iptables -N LAN-TO-WAN-TCP

As you can see we have created two new chains to filter TCP traffic going from the WAN to the LAN and from the LAN to the WAN. We can now add filter rules to accept the traffic which our security policy allows in the same way we did when adding filter rules for local traffic.

/usr/local/sbin/config-firewall
iptables -A LAN-TO-WAN-TCP -p tcp --dport http -m state --state NEW         -j ACCEPT
iptables -A LAN-TO-WAN-TCP -p tcp --dport http -m state --state ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-TCP -p tcp --sport http -m state --state ESTABLISHED -j ACCEPT

The above rule allows outgoing HTTP requests, any outgoing packets associated with the request, and the established incoming packets which will be sent in response to the request. As we have already covered how to add rules for both simple and complex protocols in previous sections we shall not cover that again here. Suffice to say that you will need to add additional rules as before to accept traffic according to your security policy.

Complex protocols which use secondary connections, such as FTP, will require additional rules to allow these packets to pass through the firewall. It should be noted that these additional rules need to handle the case of both active and passive transfers for the FTP protocol and are thus only required once even if FTP traffic is to be routed between networks bidirectionally.

/usr/local/sbin/config-firewall
iptables -A LAN-TO-WAN-TCP -p tcp --dport ftp -m state --state NEW         -j ACCEPT
iptables -A LAN-TO-WAN-TCP -p tcp --dport ftp -m state --state ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-TCP -p tcp --sport ftp -m state --state ESTABLISHED -j ACCEPT

iptables -A LAN-TO-WAN-TCP -m helper --helper ftp -m state --state RELATED -j ACCEPT
iptables -A LAN-TO-WAN-TCP -m helper --helper ftp -m state --state ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-TCP -m helper --helper ftp -m state --state RELATED -j ACCEPT
iptables -A WAN-TO-LAN-TCP -m helper --helper ftp -m state --state ESTABLISHED -j ACCEPT

Now that we have added the required rules to the appropriate chains to allow the routing of the TCP traffic which we have decided to permit all that remains is to create a chain containing some rules to categorise packets according to direction and pass them to the chains we created earlier. This can be accomplished with the code listed below.

/usr/local/sbin/config-firewall
iptables -N FORWARD-TCP
iptables -A FORWARD-TCP -j TCPCHECK
iptables -A FORWARD-TCP -i $LAN_IF -o $WAN_IF -j LAN-TO-WAN-TCP
iptables -A FORWARD-TCP -i $WAN_IF -o $LAN_IF -j WAN-TO-LAN-TCP

It is worth noting that we have specified both the incoming and outgoing interface when deciding which chain should be used to process the packet. We could have just specified the incoming interface and assumed that the outgoing interface would always be correct. This approach would have allowed traffic to be routed from the LAN to the LAN or from the WAN to the WAN however which is something probably best avoided. It is also worth pointing out that we have included the TCPCHECK chain so that all TCP packets which are forwarded through this system will also have the simple validity checks which we implemented earlier applied to them.

UDP based protocols

The code required for filtering forwarded UDP packets is exactly as one would expect given what we have learned so far. The example listing below allows DNS requests made using the UDP protocol to be routed from the LAN to the WAN and also allows the responses to such requests to be routed back from the WAN to the LAN.

/usr/local/sbin/config-firewall
iptables -N WAN-TO-LAN-UDP
iptables -N LAN-TO-WAN-UDP

iptables -A LAN-TO-WAN-UDP -p udp --dport domain -m state --state NEW -j ACCEPT
iptables -A LAN-TO-WAN-UDP -p udp --dport domain -m state --state ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-UDP -p udp --sport domain -m state --state ESTABLISHED -j ACCEPT

iptables -N FORWARD-UDP
iptables -A FORWARD-UDP -j UDPCHECK
iptables -A FORWARD-UDP -i $LAN_IF -o $WAN_IF -j LAN-TO-WAN-UDP
iptables -A FORWARD-UDP -i $WAN_IF -o $LAN_IF -j WAN-TO-LAN-UDP

As before we have also included an unconditional jump to the UDPCHECK chain so that all UDP packets which are forwarded through this host will be checked for validity.

ICMP messages

While allowing TCP and UDP packets to traverse the FILTER chain enables us to communicate using the protocols which our security policy dictates it does not handle any error messages sent in response to our connection attempts. As we learned earlier these messages are sent using the ICMP protocol so we shall need to create some infrastructure to allow us to categorise our rules.

/usr/local/sbin/config-firewall
iptables -N WAN-TO-LAN-ICMP
iptables -N LAN-TO-WAN-ICMP

We can now add some rules to the chains we just created to allow ICMP messages which indicate connection issues that require the attention of either the user or the transmitting host to pass through the firewall.

/usr/local/sbin/config-firewall
iptables -A WAN-TO-LAN-ICMP -p icmp --icmp-type destination-unreachable -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-ICMP -p icmp --icmp-type time-exceeded -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A WAN-TO-LAN-ICMP -p icmp --icmp-type fragmentation-needed -m state --state RELATED,ESTABLISHED -j ACCEPT

If you want to be able to use the ping and traceroute utilities to diagnose connection issues then ICMP echo requests and their associated replies will need to be allowed also. This can be accomplished using the code below.

/usr/local/sbin/config-firewall
iptables -A LAN-TO-WAN-ICMP -p icmp --icmp-type echo-request -j ACCEPT
iptables -A WAN-TO-LAN-ICMP -p icmp --icmp-type echo-reply -m state --state ESTABLISHED -j ACCEPT

Now that we have the rules we need to allow ICMP traffic to traverse the firewall we just need to create some rules to direct the packets to the appropriate filter chains exactly as we did for the TCP and UDP protocols. The code below performs this task.

/usr/local/sbin/config-firewall
iptables -N FORWARD-ICMP
iptables -A FORWARD-ICMP -j ICMPCHECK
iptables -A FORWARD-ICMP -i $LAN_IF -o $WAN_IF -j LAN-TO-WAN-ICMP
iptables -A FORWARD-ICMP -i $WAN_IF -o $LAN_IF -j WAN-TO-LAN-ICMP

We have also included an unconditional jump to the ICMPCHECK chain so that all forwarded ICMP packets will first be checked for validity.

Putting it all together

All that remains now is to connect the various filters to the built-in FORWARD chain and route all remaining packets to the LOGDROP chain. We shall use the protocol type matchers we met earlier to categorise the packets and pass them to the appropriate filter chains which we have created in this chapter.

/usr/local/sbin/config-firewall
iptables -A FORWARD -p tcp  -j FORWARD-TCP
iptables -A FORWARD -p udp -j FORWARD-UDP
iptables -A FORWARD -p icmp -j FORWARD-ICMP

iptables -A FORWARD -j LOGDROP

You should now be able to modify the firewall script to implement your own security policy which can be used to control the traffic forwarded between any number of networks.