Controlling Dial-on-Demand with Firewall Rules

This document describes a solution for the problems common with dial-on-demand, dynamic IP networking connections: It is intended for Linux 2.2, specifically the ipchains packet-filter architecture, and ISDN/syncppp connections (but should work with modem/PPP too). Throughout this description we assume the common case of a "single machine" connection with one IP address. Whether it also carries a masqueraded network doesn't matter, but we have to remember that there may be other networking devices around.

The idea

The general idea behind this solution is to pre-sort the packets going out to the ISDN device, depending on whether the device is up. Packets are sorted
  1. by source address: only those which have the source address of the ISDN device are allowed through. This, aside from being a good idea in general, specifically prevents packets from connections with an old address from going out.
  2. by dialout permission: when the device is down, packets are routed to a special dialout chain first. This chain selects which packets are allowed to trigger a dialout and rejects others. When the device is up, this chain is bypassed.
The chains which sort by the address and status of the ISDN device are updated from the ip-up/ip-down scripts.

The implementation

Firewall chains

An initialization script, to be run at system startup, sets up the static parts of the firewall chains.

We need a new chain, here named dod, which handles the dialout permissions. This chain accepts and logs each packet which is allowed to trigger a dialout, and silently denies everything else. In the following example outgoing packets to telnet, ssh, http, smtp, nntp, pop3, imap ports and echo requests (ping) are allowed.

        ipchains -N dod
        ipchains -A dod -p icmp --icmp-type ping        -j ACCEPT -l
        ipchains -A dod -p tcp --dport telnet           -j ACCEPT -l
        ipchains -A dod -p tcp --dport ssh              -j ACCEPT -l
        ipchains -A dod -p tcp --dport http             -j ACCEPT -l
        ipchains -A dod -p tcp --dport smtp             -j ACCEPT -l
        ipchains -A dod -p tcp --dport nntp             -j ACCEPT -l
        ipchains -A dod -p tcp --dport pop3             -j ACCEPT -l
        ipchains -A dod -p tcp --dport imap2            -j ACCEPT -l
        ipchains -A dod -p tcp --dport imap3            -j ACCEPT -l
        #ipchains -A dod -p udp --dport domain          -j ACCEPT -l
        ipchains -A dod                                 -j DENY

The -l parameter is to log which packet triggered the dialout. (Not strictly needed with ISDN, which can do this logging by itself, but useful in more complicated setups involving tunnels, request-route scripts, etc.) Note the last line which denies everything not specifically allowed (and does not log because this would perhaps log too much - of course logging can be useful for debugging here too).

Note also the commented rule for DNS lookups. This is a conscious policy decision. On one hand, you probably want this enabled, because most actions on the network start with a DNS lookup. On the other hand, perhaps you don't want the stray lookups done by many applications (like sendmail) to trigger a dialout. Disabling them is more safe but less convenient.

Now we set up the output chain. We check first that only the correct IP address goes out, and we use a placeholder address and dummy device when the device is inactive. If a packet is sent from the placeholder address to an ISDN device, this means the network is down and should probably be brought up: we route it to the dialout chain. Everything else going out over ISDN must be invalid.

        ipchains -I output 1 -i ippp99  -s 192.168.254.254      -j ACCEPT
        ipchains -A output   -i ippp+   -s 192.168.254.254      -j dod
        ipchains -A output   -i ippp+                           -j REJECT
The first rule is placed at a fixed location in the chain because it has to be updated dynamically.

Updating the output chain

When an ISDN device goes up, we replace the first rule in the output chain by one with the right parameters. Likewise, when it goes down again, we re-instate the placeholder. Using a placeholder instead of inserting/deleting rules has the advantage that nothing will accidently cause this rule to be duplicated or lost.

        # /etc/ppp/ip-up
        ipchains -R output 1 -i $1 -s $4 -j ACCEPT
        ifconfig lo:1 down

        # /etc/ppp/ip-down
        ifconfig $1 192.168.254.254
        ipchains -R output 1 -i ippp99 -s 192.168.254.254 -j ACCEPT
        ifconfig lo:1 $4 netmask 255.255.255.255
The lo:1 stuff is explained below.

The kernel ip_dynaddr hack

When a new connection is set up, it gets the placeholder address at the local end. When a packet (via the second output rule) goes out, the address of the device changes, and it is necessary to change the socket address too. The kernel does this if told to, the command to activate this mode is (goes into the initialization script):
        echo "1" >/proc/sys/net/ipv4/ip_dynaddr
Unfortunately, this works only for TCP sockets in SYN_SENT state, i.e. before the connection is established. (Not a problem with the kernel implementation but a limitation of TCP.)

For the second output rule to work, new sockets have to get the placeholder address (192.168.254.254) when the connection is down. We ensure this by assigning the device this special address in ip-down. It is also necessary to initialize all potential dialout devices to this address in the system startup somewhere (not shown here).

So far we have already the complete configuration!

Advanced considerations

Handling dead connections

Note what happens when the ISDN device goes down and there are still TCP connections open over it. With dynamic IP addressing these are now dead and should be aborted immediately, if possible. Unfortunately there is no way to do this currently in the official kernel. But we have at least to care for the packets these connections generate, so they don't do any damage.

Look at the output chain after ip-down. The first rule is the match-nothing dummy again. The second one does not match because it always has the placeholder source address. It won't match old sockets who carry the now invalid ISDN address. But the third rule matches and generates a reject packet. Normal routing would send this over the default route (think about where the source and destination addresses are!), which is not what we want. So we send them over the loopback device back to ourselves, which is achieved by bringing up a loopback alias.

This is not completely what we want, because the reject packet doesn't cause the connection to be aborted. To help with this case, a kernel patch has been developed. It is at ftp://ftp.suse.com/pub/people/ak/v2.2/iff-dynamic-2.2.14-2.gz. With this patch, just give the "dynamic" flag to ifconfig for the ippp devices. Then all active TCP connections on this device will be reset immediately when the connection goes down.

Extensions

Further output filtering

If further output filtering for the accepted outgoing packets is needed, replace the ACCEPT target in the first output rule with a jump to an appropriate new chain. In that case the scripts which update this rule also need that change.

Using tunnels

In a network involving IP-in-IP tunnels (such as CIPE), where the tunnel's carrier runs over a dial-on-demand link, the tunnel itself may also indirectly cause dialing. Therefore it can be beneficial to route the tunnel over the dod chain too: insert a rule like
        ipchains -I output 2 -i cipcb0                          -j dod
(cipcb0 is the tunnel device) into the output chain on initialization, change it in ip-up to ACCEPT, and change it back in ip-down.

Using the request-route daemon

Instead of the autodialing capability of an ISDN device I use a special daemon which implements a network device (over which the default route goes) and calls an arbitrary script whenever a packet goes out over that device. (Linux 2.0 can do a similar thing from the kernel.) Useful applications for this include: A program which does this can be found at http://sites.inka.de/bigred/devel/rrouted.txt. It works similar to "diald", but is much simpler and less tied to SLIP/PPP dial handling.

With this daemon, the default route goes to sl0 instead of an ISDN device, so the output rule matching the dial-out candidates has to use this device: replace the second rule by

        ipchains -A output   -i sl0     -s 192.168.254.254      -j dod
and add at the end another "guard" rule:
        ipchains -A output   -i sl0                             -j REJECT

Olaf Titz
Last modified: Mon Apr 23 19:27:50 CEST 2001