Jos Vos 
                   Willy Konijnenberg 


                    X/OS Experts in Open Systems BV
                             Kruislaan 419
                          1098 VA  Amsterdam
                            The Netherlands
                 NLUUG Spring Conference 1996
                   De Reehorst, Ede, The Netherlands
                             May 8-9, 1996

---------------------------------------------------------------
Original of this document is at
http://www.xos.nl/linux/ipfwadm/paper/
---------------------------------------------------------------

The freely available Linux operating system includes a number of facilities for efficient kernel-level IP packet filtering and screening. The acceptance and forwarding of IP packets can be regulated by specifying filter rules, using packet and network device characteristics, such as IP addresses, port numbers, IP flags, and incoming or outgoing interfaces.

Linux also provides a facility comparable (to some extend) with transparent proxies (not requiring any changes for users or application software), which is implemented as part of the IP firewall module and can be configured using a similar set of rules.

Network security, and more specifically the use of Internet firewalls, is one of today's hottest topics in the computer business. Every private network that is going to be connected to the Internet needs an appropriate firewall, being some combination of hardware, software, and procedures, to protect it. Most commercial firewall products are quite expensive, especially for small companies.

An alternative is to use Linux, a freely available operating system. We will focus on one aspect of Linux, the IP packet screening facilities, being one of the components for building firewalls based on Linux. A good firewall certainly needs more than packet filters. At the end you'll find some recommendations for using Linux systems as a complete firewall solution.

This paper is based on release 1.3.88 of the Linux kernel and version 2.0 of the ipfwadm utility. Be aware of the fact that some details might have changed in the next production release of Linux.



Note that this paper is based on Linux 1.3.88 and ipfwadm 2.0, describing the situation in April 1996.
A revision of this paper for Linux 2.0.x and ipfwadm 2.3.0 is planned, but not yet available.

Copyright © 1996 by X/OS Experts in Open Systems BV. All rights reserved.

Before describing the Linux implementation of IP packet filtering, we will briefly introduce the underlying general concepts.

IP packet filters inspect network datagrams (IP packets) and decide whether these packets are allowed to pass the filter or not. These inspections may take place at several stages, like the moment packets arrive on the system or when they are about to be forwarded to another system.

The decision to let a filter block certain packets is usually based on several criteria, being checked against the contents of the IP packet and some environmental parameters:

Some of these criteria are easy to check, such as the ones being part of the IP header. Others may be more difficult to handle, such as the information found in the TCP/UDP headers. One IP packet, for example, may be split up in two or more IP fragments, of which only the first fragment contains the TCP header. Furthermore, IP packets may not pass the filter in sequence, because session control is handled in the TCP layer. Some packet filters address some of these problems, others don't.

An IP packet filter can react in different ways to packets, depending on the filters and the capabilities of the filtering system. The most obvious actions are: let the packet pass the filter normally or drop the packet silently. Some filters also offer the possibility to send some notification (like an ICMP Destination Unreachable message) back to the sender, in case a packet is blocked by the filter. Besides that, filters often include some logging facility, to facilitate the network administrator in detecting if someone is trying to break in.

There are several ways to implement packet filters in a UNIX system. The most efficient way is to implement it in the kernel. One of the disadvantages of this method is that it is not very flexible. Another method is to let some user-level process do the filtering. Such a daemon process has to get the relevant information of all IP packets (at least part of the IP and TCP/UDP headers, possibly even the data part) via some system call, apply its filter rules, and pass the answer back to the kernel. The (freely available) screend package behaves like this. The major drawback of these implementations is the considerable overhead generated by the system calls and process scheduling.

The Linux kernel provides native support for IP packet filtering at several stages: when a packet is received, when packet is sent, and when a packet is being forwarded (see figure 1).

Figure 1

Each of the three filters consists of a default policy and a list of filter rules. Every filter rule defines some packet characteristics, like IP addresses, an optional network device, and several other options. Furthermore, each rule has a policy associated with it, defining what to do when a packet matches with the rule.

The algorithm used in the filters can be described as follows:

  1. Step through the list of filter rules associated with the filter and check whether the packet matches with the rule or not.
  2. The first matching filter rule (if any) determines all further actions:
    • The rule's policy will be applied to the packet.
    • Each rule contains packet and byte counters, which will be incremented when a packet matches.
    • Optionally, some information about the packet is written to the Linux kernel log.
    • Finally, a rule may contain parameters defining how to change the TOS-field in the IP header, dealing with the packet's priority.
  3. If none of the filter rules match with the packet, use the default policy associated with the filter.

There are currently three policies supported in Linux:

Filter rules in Linux contain the following items:

Now that we have seen the basic concepts of the Linux firewall filters, we will show how to manage the filter rules from an administrator point of view.

The interface for managing the kernel-level filter rules at user-level mainly consists of two parts:

The ipfwadm command provides a command-level interface for managing the Linux firewall facilities: it can be used to change or inspect all aspects of the kernel filters. Let's start with a simple example:

   ipfwadm -I -a deny -S 192.168.22.0/24 -D 0.0.0.0/0

This command basically means ``refuse all incoming packets originally coming from network 192.168.22.0''. It appends (-a) a new rule to the list of filter rules belonging to the input firewall (-I). The output and forward filters can be changed by using the -O and -F options, respectively. After the append command the rule's policy is specified. Valid keywords are accept, deny, and reject (refuse the packet, but return an ICMP message). The source (-S) and destination (-D) addresses both include a mask: the suffixes /24 and /0 are equivalent to /255.255.255.0 and /0.0.0.0, respectively. Every IP address will match the specified destination, because the mask only contains 0's (we could also have written something like 11.22.33.44/0).

Another example:

   ipfwadm -I -a accept -k -P tcp -S 0.0.0.0/0 telnet \
           -D 192.168.37.1 1024:65535
   ipfwadm -O -a accept -P tcp -S 192.168.37.1 1024:65535 \
           -D 0.0.0.0/0 telnet

The command creates two rules (one for the input firewall, one for the output firewall) that accepts all packets belonging to an outgoing telnet connection (here it is assumed that our local IP address is 192.168.37.1). The protocol is specified via the -P option and after the IP address a service name (telnet, specifying port 23) and a port range (in this case all unpriviliged ports) are specified. The -k option makes the input rule only match with packets having the TCP ACK flag set. This prevents someone from trying to initiate a connection from the outside (using source port 23) to some unpriviliged port on our system.

Unfortunately, enabling a service is not as easy for all services. The ftp protocol, for example, uses a separate, incoming connection to transfer the data. So, using ftp (unless used in ``passive mode'') requires to allow a connection being initiated from outside your own network. Setting up a set of firewall rules for ftp would look like:

   ipfwadm -I -a accept -k -P tcp -S 0.0.0.0/0 ftp \
           -D 192.168.37.1 1024:65535
   ipfwadm -O -a accept -P tcp -S 192.168.37.1 1024:65535 \
           -D 0.0.0.0/0 ftp
   ipfwadm -I -a accept -P tcp -S 0.0.0.0/0 ftp-data \
           -D 192.168.37.1 1024:65535
   ipfwadm -O -a accept -k -P tcp -S 192.168.37.1 1024:65535 \
           -D 0.0.0.0/0 ftp-data

Here it is assumed that ftp-data is a valid service name for TCP port 20.

With the -p (set policy) command a default policy is specified.

   ipfwadm -F -p deny

In this case, all forwarding is disabled, unless packets match with one of the forward rules, explicitly allowing them to pass the filter. Filters can be listed using the -l command, like in:

   ipfwadm -I -l

This command would produce the following result, after issuing the above ipfwadm commands for the input firewall:

   IP firewall input rules, default policy: accept
   typ prot source             destination        ports
   den all  192.168.22.0/24    anywhere           n/a
   acc tcp  anywhere           gw.foo.com         telnet -> 1024:65535
   acc tcp  anywhere           gw.foo.com         ftp -> 1024:65535
   acc tcp  anywhere           gw.foo.com         ftp-data -> 1024:65535

The printed hostname, gw.foo.com, corresponds to the local system, having IP address 192.168.37.1. There are several options to change or extend the given output of ipfwadm. The above example show the most simple format.

Some more hints for managing firewall filters:

See the ipfwadm(8) manual page for more details and other options.

The Linux kernel provides an additional mechanism to use in firewall solutions: masquerading of IP packets. This means that some or all packets being forwarded by a Linux system can be changed as if there were sent from the local system. So, the source IP address is replaced by the local IP address and the source port is replaced by a locally generated port (e.g., 60005). Because an administration is kept of masqueraded sessions, incoming packets for that port will automatically be ``demasqueraded'' and forwarded to the system that originally initiated the session.

The next table summarizes the masquerading function, given a telnet session from an internal host (192.168.37.15) to an external host (10.42.17.8), passing a Linux system doing masquerading (192.168.37.1):

source destination
IP address port IP address port
original packet 192.168.37.15 1027 10.42.17.8 23
masqueraded 192.168.37.1 60005 10.42.17.8 23
reply packet 10.42.17.8 23 192.168.37.1 60005
demasqueraded 10.42.17.8 23 192.168.37.15 1027

Masquerading takes place after passing the forward firewall filter. Demasquerading is done after receiving a packet and demasqueraded packets bypass the forwarding filter. Figure 2 shows the kernel flow diagram including (de)masquerading.

Figure 2

Masquerading is not as easy as it seems: some protocols need special care. One of the problem areas is found in the widely used ftp protocol, because this protocol uses a second session (normally initiated by the remote site) for transferring the actual data. A similar problem arises with the IRC protocol. The Linux IP masquerading implementation deals with such protocol-specific features in separately loadable modules. Another problem is that a transparent proxy should operate on transport level connections, whereas masquerading is implemented in the network layer. The current implementation tries to address this with a limited session administration, but there are still some weaknesses to work on.

Masquerading can be enabled by specifying a special policy for a forward filter rule. The next command creates a rule that makes every outgoing telnet session being masqueraded (given that our local network has address 192.168.37.0):

   ipfwadm -F -a masquerade -P tcp -S 192.168.37.0/24 \
           1024:65535 -D 0.0.0.0/0 telnet

The masquerade policy is in fact a variant of the accept policy: the packet is accepted (that is, allowed to be forwarded), but it gets masqueraded before being sent out. Because the masquerading mechanism depends on port numbers, it only works for TCP or UDP packets. So, be careful when using commands like:

   ipfwadm -F -a masquerade -S 192.168.37.0/24 -D 0.0.0.0/0

This command creates a rule that will cause all outgoing TCP and UDP traffic to be masqueraded. But it will also let all other packets (like ICMP messages) be forwarded unchanged, because they will also match with this rule! So, it's probably better to explicitly handle those cases, like with:

   ipfwadm -F -p deny
   ipfwadm -F -a masquerade -P tcp -S 192.168.37.0/24 \
           -D 0.0.0.0/0
   ipfwadm -F -a masquerade -P udp -S 192.168.37.0/24 \
           -D 0.0.0.0/0

Especially when using unregistered IP addresses on your internal network (like the addresses defined in RFC1597 or, even worse, illegally used addresses), no packets should ever be forwarded directly.

Please note that there are no ``masquerading rules'', but only forwarding rules with a special policy. So, you can list the rules with a command like:

   ipfwadm -F -l

which will (given the above example) result in something like:

   IP firewall forward rules, default policy: deny
   typ prot source             destination        ports
   msq tcp  192.168.37.0/24    anywhere           any -> any
   msq udp  192.168.37.0/24    anywhere           any -> any

Besides this static information, the list of sessions currently being masqueraded can be inspected. This is dynamic information, changing every moment, which can be used to keep track of the external connections being active. The command

   ipfwadm -M -l

might for example produce the following output:

   IP masquerading entries
   prot expire   source           destination      ports
   tcp  13:00.15 int1.foo.com     ext2.bar.com     1017 (60001) -> login
   tcp  14:15.60 int2.foo.com     ext1.bar.com     1346 (60010) -> telnet
   tcp  14:52.82 int1.foo.com     ext1.bar.com     1348 (60015) -> ftp

The above table shows three sessions being masqueraded. The information is read from the pseudo-file /proc/net/ip_masquerade, which is converted to a human-readable format by ipfwadm.

In Linux, IP traffic can be counted using accounting rules, defined by the same characteristics as the firewall rules. Accounting is done at two places: when a packet is received and when a packet is sent out (see figure 3).

Figure 3

So, a packet being forwarded is counted twice: the first time just after its arrival, the second time when it is being sent out again. There is one single list of accounting rules, that is being used for both incoming and outgoing traffic. For every packet, all rules in this list are checked and the packet and byte counters of every matching rule are incremented. Note the difference with the firewall lists: scanning a list there stops at the first match.

The following ipfwadm command counts all http traffic related to people using your WWW-server from the outside:

   ipfwadm -A -a -b -W eth1 -P tcp -S 0.0.0.0/0 \
           -D 192.168.37.1 www

Here it is assumed that the local system, hosting the WWW-server, has IP address 192.168.37.1. We see some new options in this command. The -b option means ``bidirectional'', and makes that also packets coming from 192.168.37.1 (port 80) are counted. The -W option has an interface name as parameter, so that only traffic via that particular interface is taken into account. Packets passing another interface (e.g., an interface eth0 connected to your internal network) are not counted here.

Some suggestions to use accounting most effectively:

When listing the accounting rules (and the associated values) with ipfwadm, the pseudo-file /proc/net/ip_acct is read.

This section lists a complete example for a set of firewall filters on a Linux system acting as a gateway between the Internet and a private network. Note that this example is only included for illustrative purposes. Although it will protect the internal network to some extend, we strongly discourage to consider this to be a complete, robust firewall solution.

The example applies to a gateway system (gw.foo.com) connected to the Internet using interface 192.168.22.15 and to an internal network (192.168.37.0) via interface 192.168.37.1. The system is as a public WWW and ftp server, it can send and receive mail, it acts as a mail relay host for the internal network, and it is the primary DNS server for the foo.com domain.

Hosts on the private network can directly use telnet, WWW, ftp, gopher and WAIS services on the Internet (which is not a recommended firewall architecture). Also, ICMP traffic is allowed without any restrictions (e.g., to enable ping). Note that traceroute will not work, because this is using UDP packets to some unpriviliged ports.

   # Some definitions for easy maintenance.
   LOCALHOST="gw.foo.com"
   IFEXTERN="192.168.22.15"
   IFINTERN="192.168.37.1"
   LOCALNET="192.168.37.0/24"
   ANYWHERE="0.0.0.0/0"
   UNPRIVPORTS="1024:65535"

   # ====== Basic rules.

   # Sure we're paranoid, but are we paranoid enough?
   ipfwadm -I -p deny
   ipfwadm -O -p deny
   ipfwadm -F -p deny

   # Handle spoofed packets.
   ipfwadm -I -a deny -V $IFEXTERN -S $LOCALNET -D ANYWHERE
   ipfwadm -I -a deny -V $IFEXTERN -S $IFEXTERN -D ANYWHERE

   # Unlimited traffic within the local network.
   ipfwadm -I -a accept -V $IFINTERN -S $ANYWHERE -D $ANYWHERE
   ipfwadm -O -a accept -V $IFINTERN -S $ANYWHERE -D $ANYWHERE

   # Unlimited ICMP traffic (not recommended).
   ipfwadm -I -a accept -P icmp -S $ANYWHERE -D $ANYWHERE
   ipfwadm -O -a accept -P icmp -S $ANYWHERE -D $ANYWHERE
   ipfwadm -F -a accept -P icmp -S $ANYWHERE -D $ANYWHERE

   # ====== External use of our system.

   # Public access for e-mail, ftp, WWW, and DNS.
   ipfwadm -I -a accept -P tcp \
           -S $ANYWHERE -D $LOCALHOST smtp ftp www domain
   ipfwadm -I -a accept -P udp \
           -S $ANYWHERE -D $LOCALHOST domain
   ipfwadm -I -a accept -k -P tcp \
           -S $ANYWHERE -D $LOCALHOST ftp-data
   ipfwadm -O -a accept -P tcp -S $LOCALHOST smtp ftp \
              ftp-data www domain -D $ANYWHERE
   ipfwadm -O -a accept -P udp \
           -S $LOCALHOST domain -D $ANYWHERE

   # ====== Internal use of the Internet.

   # Outgoing packets.
   ipfwadm -O -a accept -P tcp -S $LOCALNET $UNPRIVPORTS \
           -D $ANYWHERE smtp ftp ftp-data www telnet gopher \
              z3950 domain
   ipfwadm -O -a accept -P tcp -S $IFEXTERN $UNPRIVPORTS \
           -D $ANYWHERE smtp ftp ftp-data www telnet gopher \
              z3950 domain
   ipfwadm -O -a accept -P udp -S $LOCALNET $UNPRIVPORTS \
           -D $ANYWHERE z3950
   ipfwadm -O -a accept -P udp -S $LOCALHOST $UNPRIVPORTS \
           -D $ANYWHERE z3950 domain
   ipfwadm -F -a accept -P tcp -S $LOCALNET $UNPRIVPORTS \
           -D $ANYWHERE ftp ftp-data www telnet gopher z3950
   ipfwadm -F -a accept -P udp -S $LOCALNET $UNPRIVPORTS \
           -D $ANYWHERE z3950

   # Incoming packets.
   ipfwadm -I -a accept -k -P tcp \
           -S $ANYWHERE ftp www telnet gopher z3950 domain \
           -D $LOCALNET $UNPRIVPORTS
   ipfwadm -I -a accept -k -P tcp \
           -S $ANYWHERE ftp www telnet gopher z3950 domain \
           -D $IFEXTERN $UNPRIVPORTS
   ipfwadm -I -a accept -P tcp \
           -S $ANYWHERE ftp-data -D $LOCALNET $UNPRIVPORTS
   ipfwadm -I -a accept -P tcp \
           -S $ANYWHERE ftp-data -D $IFEXTERN $UNPRIVPORTS
   ipfwadm -I -a accept -P udp \
           -S $ANYWHERE z3950 -D $LOCALNET $UNPRIVPORTS
   ipfwadm -I -a accept -P udp -S $ANYWHERE z3950 domain \
           -D $LOCALHOST $UNPRIVPORTS
   ipfwadm -F -a accept -k -P tcp \
           -S $ANYWHERE ftp www telnet gopher z3950 \
           -D $LOCALNET $UNPRIVPORTS
   ipfwadm -F -a accept -P tcp \
           -S $ANYWHERE ftp-data -D $LOCALNET $UNPRIVPORTS
   ipfwadm -F -a accept -P udp \
           -S $ANYWHERE z3950 -D $LOCALNET $UNPRIVPORTS

Some further remarks about the above example:

Although the current Linux firewall facilities are very useful, there are still some weaknesses and missing features. Therefore, possible areas for improvement in future Linux versions might be:

Given this, there is a good chance that Linux will soon be able to compete with the more advanced commercial firewall solutions on the market.

In general, most of the optimal firewall solutions are some mixture of IP filters and application-level proxies, although the detailed architecture highly depends on the target environment. Proxies are mostly used for enabling extra authentication methods and advanced logging facilities. Linux systems can be used as a complete firewall solution, when using additional packages like the Firewall Toolkit of Trusted Information Systems, Inc. (TIS), S/Key or one of its derivatives like OPIE (One-time Passwords In Everything), SOCKS, etc. These packages are freely available and are known to work on Linux. But keep in mind that the use of a single bastion host without a filtering router is not recommended practice.

The pro's en con's of using free software for mission-critical applications like a firewall often focus on the black box versus crystal box debate. A commercial product usually doesn't come with source code, so nobody can study that code to find security leaks and try to misuse them. This is the black box concept, sometimes called ``security by obscurity''. On the other hand, when using freely available software, everybody, including the user, can look for possible bugs or weaknesses. The source code is available, so this approach is often referred to as a crystal box (of course, configuration details, local enhancements, etc., are not known publically). This usually will make bugs be found earlier, and fixes are just made available via the Internet, often within hours after detecting a bug.

The costs of a Linux firewall solution are relatively low. Given today's decreasing prices of PC's, a single Linux PC acting as a filtering router might cost less than Dfl. 1,500. A more advanced Linux system, also hosting several proxy services, a WWW-server, etc., will cost no more than Dfl. 3,000. Of course, these prices do not include consulting services to configure a customized firewall solution. But, be aware of the fact that you'll also have additional costs when buying a commercial firewall product. The quality of a total firewall solution highly depends on a well-designed configuration scheme, no matter whether it's based on commercial or free components.

The newest Linux kernel is available from:

   ftp://ftp.cs.helsinki.fi/pub/pub/Software/Linux/Kernel/
   ftp://ftp.nluug.nl/pub/os/Linux/system/kernel/

The home page of ipfwadm on the World Wide Web:

   http://www.xos.nl/linux/ipfwadm/

The ipfwadm package can be obtained via:

   ftp://ftp.xos.nl/pub/linux/ipfwadm/

Some recommended books about firewalls:



Note that this paper is based on Linux 1.3.88 and ipfwadm 2.0, describing the situation in April 1996.
A revision of this paper for Linux 2.0.x and ipfwadm 2.3.0 is planned, but not yet available.

Copyright © 1996 by X/OS Experts in Open Systems BV. All rights reserved.


Популярность: 7, Last-modified: Wed, 06 Aug 1997 18:51:24 GmT