ToC Home Issues Hearts Links

Issue #4, November 2005

Slack Essence-1*: How do I construct a firewall for my dial-out or PPPoE environment?

Author: Lew Pitcher

I want a strong firewall which protects my PPP/PPPoE established IP address while not interfering with traffic over my LAN. The firewall is implemented on my router/gateway/NAT machine, which also provides number of IP-enabled services for my LAN, and a couple of IP-enabled services for the internet.

A Strong, Permanent Firewall

The first step is to establish a basic set of firewall rules that will apply even if the system has no external connection. I create a script called /etc/rc.d/rc.firewall which contains some generic firewalling rules that prevent any access between the outside and my LAN. These rules are activated on startup by the following script segment in /etc/rc.d/rc.inet2

    # If there is a firewall script, run it before enabling packet forwarding.
    if [ -x /etc/rc.d/rc.firewall ]; then
     /etc/rc.d/rc.firewall start
    fi

The rc.firewall rules are aimed to protect my router from unauthorized access, even if there were no other rules in place. These "default" rules disallow any packets from anywhere or to anywhere if they do not come from either my LAN (via eth0) or the loopback (via lo). This makes it more difficult for someone to add an unauthorized interface (say, a VPN interface as tun0) and get packets in or out of the default configuration. The default is to deny everything that isn't explicitly permitted.

Basically, it performs the following actions when started:

  1. it resets the built-in iptables' chains and defines the default policies ("DENY" for INPUT and FORWARD chains and "ACCEPT" for OUTPUT chain),
  2. permits incoming traffic via loopback and the interior interface (eth0),
  3. creates two chains (log_and_drop and log_and_deny) that will be envoked in another script called ip-up.firewall.

Before performing the three actions, rc.firewall loads all iptables-related modules that may be needed. Actually, one doesn't have to load all of them explicitly when running a kernel compiled with CONFIG_KMOD=y (as, for example, is done for the stock Slackware kernel) but this is an attempt at a "bulletproof" ruleset, and should they be run on a system with a self-compiled kernel (which I usually do). I prefer to leave them in unnecessarily rather than risk an exposure by removing them when they should be there. The same is true for a number of "rmmod" commands envoked when the firewall is stopped.

Poking Holes in the Firewall

In /etc/ppp/ip-up, I add this line:

    /etc/ppp/ip-up.firewall "$1" "$2" "$3" "$4" "$5" "$6"

Here,

$1 is the name of the interface over which the PPP connection has been established. This will be 'ppp0' for the ppp0 device, 'ppp1' for the ppp1 device, etc. If you run multiple PPP or PPP0E connections, each connection will be given its own IP address and its own device name.

$2 is the name of the serial device on which the PPP connection was made. For true serial connections this will be something like /dev/ttyS0.

$3 is the serial linespeed of the PPP link. For dial-up connections, this is the speed at which the modem converses with the CPU. For PPPoE, this is an empty parameter, and can be ignored.

$4 is the IP address assigned to the local side (our side) of the PPP connection. This is our IP address on the PPP link.

$5 is the IP address assigned to the remote side (the ISP side) of the PPP connection. I don't use this value in these firewall scripts, but may at some future time add rules to permit or deny access to packets originating specifically at my ISP's site, in which case, this address will come in handy.

$6 is the text string given to the pppd daemon by the ipparm parameter of pppd. It can be used to name a config file, or to provide a set of additional, site specific, parameters to the ppp scripts.

When pppd establishes an IP session with my ISP, it invokes /etc/ppp/ip-up, giving it the interface name as parameter $1 and the assigned IP address as parameter $4. The additional lines in /etc/ppp/ip-up will invoke my firewall-building script, giving it the device name that's been started, and the IP address assigned to that device. /etc/ppp/ip-up.firewall establishes the specific firewalling rules for the device and IP address that ppp has connected to the Internet with.

The chains built by /etc/ppp/ip-up.firewall are all named so as to relate to the interface that they apply to. This way, the script can independently manage more than one ppp device (i.e. ppp0 and ppp1), with rules that do not conflict. The rule chains built are named

Plugging the holes in the Firewall

In /etc/ppp/ip-down I add:

    /etc/ppp/ip-down.firewall "$1" "$2" "$3" "$4" "$5" "$6"

When pppd terminates the IP session, it invokes /etc/ppp/ip-down, giving it the interface name as parameter $1. The additional lines in /etc/ppp/ip-down will invoke my firewall-tightening script, giving it the device name that's been stopped and the IP address assigned to that device. /etc/ppp/ip-down.firewall removes the specific firewalling rules for the device and IP address that /etc/ppp/ip-up.firewall installed when ppp connected to the Internet on it.

IP Tables flow diagram

The diagram below shows the flow of packets through the various IP Tables evaluation stages.

iptables flow diagram

About the diagram:

Scenario A represents the flow of packets from an outside source through a local interface (via the IP address assigned to that interface) to an application on the local system. These packets have the local IP address of the receiving interface as their target address. For example, these could be 'ping request' packets coming from the internet targetted at the system's public IP address.

Scenario B represents the flow of packets from the an application on the local system through a local interface (via the IP address assigned to that interface) to an outside destination. These packets have a target IP address that the system can access by way of a route through the selected interface, and packets will have the IP address of that interface as their source address. For example, these could be 'ping response' packets coming from the system's public IP address targetted at the internet.

Scenario C represents the flow of packets from the an application on the local system through a local interface (via the IP address assigned to that interface) to another local application. These packets have the local IP address of the sending interface as their source address, and will have the IP address of that same interface as their target address. For example, this could represent the traffic flow between two local applications that use TCP/IP as an interprocess communications medium.

Scenario D represents the complex flow of packets from an application on the local system through a local interface (via the IP address assigned to that interface) to an application on a foreign system. These packets have a target IP address that the system can access by way of a route through the selected interface, and packets will have the IP address of the first local interface as their source address. For example, this could represent the traffic flow from a local application bound to one IP address and a target that's routed through a different interface than the one bound to by the application.

Scenario E represents the flow of packets from an outside source through a local interface, into the "routing" logic, and out through another local interface to an application on another system. These packets do not have a local IP address as their target address, but instead have the IP address for a system that the firewall routes for. For example, this would be the flow of packets through the firewall system for a NAT conversation between an intranet system and an internet system.

Scenarios C and D rarely occur in "real life" use. Both of these scenarios postulate that a local application has opened a socket on a specific IP address (say, the local IP address of the PPP connection), and that application then directs packets to a target reachable through that or another local interface (say, through lo or the LAN interface). There are very few apps that default to such a binding, and these scenarios are included mostly for completeness sake. The diagram is part of my "working notes" from when I built my firewall scripts. I used this diagram to dry run the firewall rules by checking if and how the rules covered packets traveling through each of these scenarios. Local packets (for instance) should flow without restriction through the filters in scenario C and D, but should be filtered for unroutable addresses and other conditions in scenarios A, B, or E. Since the filter rules are not associated to a particular packet flow scenario, but to a specific point in the packet processing, some rules may have unwanted side effects because they process not only 'suspect' packets, but 'good' packets as well.

Overall, the ruleset that I wrote was intended to be both as "bulletproof" as I could make it, and to accommodate additional interfaces with little or no modification. I have to say that it works as I intended it; I have used these scripts for about 5 years now, without any problems or network invasions.


[*] Slack Essence, as in "doing as little as possible, yet mysteriously benefiting at the same time" from here.

[1] Assume, for the moment, that I have two PPP connections active. ppp0 and ppp1 will each have different IP addresses, and will be activated and deactivated independently. I didn't want the ip-down.firewall rule for ppp0 (say) to deactivate the rules in place for ppp1, and vice versa. So I name my chains according to the interface they act on and belong to.



BerliOS Logo