Cover V05, I06
Figure 1
Figure 2
Figure 3
Listing 1
Listing 10
Listing 11
Listing 2
Listing 3
Listing 4
Listing 5
Listing 6
Listing 7
Listing 8
Listing 9
Sidebar 1
Sidebar 2
Sidebar 3
Sidebar 4
Sidebar 5
Sidebar 6
Sidebar 7


Building a Firewall with Linux

Arthur Donkers

Getting access to the Internet is easy. A great many Internet service providers (ISPs) are available, almost always just a local call away. Getting safe access to the Internet is completely different. You want internal users to have convenient access to information on the Internet, but you don't (usually) want outsiders to have any, let alone easy, access to your internal information. The best way to achieve this selective connectivity is to separate your internal network from the Internet with a firewall.

This article shows you how to install and configure a Linux-based firewall that connects to the Internet via a demand-dial PPP link.

My firewall implementation uses a standard SMCEthernet card for the firewall host. The connection to the Internet goes through a high-speed modem on a dial-up PPP link to our service provider. (See Figure 1.)

The Big Picture

The major steps in building a Linux-based firewall are:

  • Load a base installation, probably from a CD-ROM
  • Reconfigure/upgrade the kernel to one with firewall capabilities
  • Add PPP/SLIP support (if necessary)
  • Add IP masquerading support
  • Add ipfw support
  • Configure the firewall rules
  • Configure a name server
  • Revise the startup configuration

    I recommend using a CD-ROM-based distribution for your initial installation. (See the sidebar "Obtaining Linux.") These distributions usually include install scripts and instructions that greatly simplify the installation process. Also, they include all of the drivers and most of the other components you will need, and having all the necessary components in one place is a significant time saver.

    Typically, these distributions come with prebuilt kernels for common configurations. You boot with one of these kernels and then use that as a development environment to build a kernel specific to your machine's configuration.

    Once you have a working, properly configured base kernel running, you should configure PPP, or whatever network interface you will be running, and download the source tree for a kernel with appropriate firewall support. At this point, you should also download any other packages you may need to upgrade. Specifically, you will need current versions of ipfw, PPP, SLIP, and IP masquerading.

    Once you have the source for all the firewall components, you need to build yet another kernel, this time configuring it to enable IP forwarding and other firewall-related features.

    With the firewall-configured kernel running, the last steps are to configure the firewall packet filtering rules, configure an appropriate nameserver, and revise the startup sequence so that only a minimum of daemons are running.

    Upgrading the Kernel Source

    For this firewall, I chose the most recent kernel available version 1.3.68, as of this writing. By the time you read this, there will no doubt be an even newer version of Linux available, maybe even Linux 2.0. I assume any newer versions will have the same, or at least a very comparable, version of the firewall code in the kernel.

    I have a few reasons for choosing this kernel version. First, the firewall code of the kernel has improved. Version 1.3.68 allows you to specify rules for three types of traffic: incoming, outgoing, and forwarding. This in turn, enables you to precisely specify which traffic is trusted and which traffic is considered dangerous. Furthermore, the IP masquerading has improved as well.

    To use the new firewall rulesets, you need a special version of the firewall administration program, ipfwadm. This version "knows" about these new rules. You cannot use older versions (up to kernel version 1.3.65), because they are not compatible and will definitely cause trouble. You can download this new ipfwadm program from :

    And you need at least the 2.0 beta version. When you start to build this new kernel, you first need to do:

    make config

    to enable and disable kernel features.

    During this configuration, you will enable the different hardware devices of your hardware platform. In my firewall setup, I have a SMC8013 Ethernet card for connection to the local network, and use a PPP dial-up link to the Internet. (The complete layout of this test network is shown in Figure 2.) I chose to enable the PPP support as dynamically loadable modules. Unlike statically linked drivers, modules can be loaded and unloaded at runtime. With PPP configured as a loadable module, I can really take the firewall off-line by just unloading the PPP module.

    I also enable support for SLIP as a loadable module. Even though I use PPP and not SLIP as my dial-up Internet protocol, I need to enable SLIP to make the dial-on-demand daemon, diald (version 0.12), available. This daemon will automatically call the Internet service provider if it detects traffic destined for the Internet.

    Listing 1 shows all the make config options related to the firewall and masquerading code. You should use these same responses when building your firewall kernel.

    After configuring the kernel you need to do:

    make dep; make clean; make zImage

    to build it. After building, you can boot this kernel as the software foundation for your firewall.

    You can compile both ipfwadm and diald out of the box. After you build and install them, they will be available for use.

    Configuring the Firewall Rules

    Again, the kernel's firewall supports three distinct groups of packet filtering rules: input, output, and forward. Each of these groups has its own set of firewall rules and, thus, needs to be configured appropriately.

    The rules for the input group describe which traffic is allowed into the firewall. These rules apply to all network interfaces on the firewall, not just the Internet interface. Each rule gives a combination of source address, destination address, and interface address. When an IP packet matches a rule, it is processed according to the action in the rule.

    The rules for the output group specify which packets may leave the firewall. These rules resemble the input rules; it is just the direction of the traffic that differs.

    The rules for the forward group specify which packets may flow from one interface to another. These rules really make the firewall a firewall, either allowing or blocking traffic. Also in the forwarding group you can specify whether or not a packet should be masqueraded. The forwarding rules are the only place where masquerading can be configured. The other two groups only concern the traffic on the physical interface.

    These rules are constructed using various options of the ipfwadm command (See Listing 2). It is very important to become familiar with these options. Incorrect rules may create subtle and not so subtle backdoors through your firewall.

    Here are the most important options. The first option states the group for which the rule is intended. The -I is for the input group, the -O for the output group, and the -F for the forwarding group. You can then specify whether a rule should be added (-a), deleted (-d), or inserted (-i). The parameter of this option is the policy for the rule. This policy can be accept (accept the packet), reject (reject the packet), or deny (ignore the packet).

    Neither reject nor deny allows matching packets in, but there is a subtle, yet important difference. A packet that is rejected will result in an ICMP message transmission to the sender of the original packet. The user originating the rejected message will get some sort of error message stating "connection refused." This error message does give potential intruders some information. It tells them that some host (the one sending the ICMP messages) "owns" the refused address. So, the intruder now knows that perhaps some other attempt will succeed at that host.

    A denied packet is silently ignored; thus the intruder cannot tell whether there is no host responding or the packets are just tossed away. So, my advice is to always use a deny policy instead of a reject policy for your Internet connection. Connections on the local net may be rejected, so the local user will know they've been refused.

    Using the -D and -S options of the ipfwadm command, you can specify the source and destination address. You can even mask bits from the address to specify a group of hosts belonging to the same subnet. This will save you a lot of typing and speed up the matching process in the firewall. Note that matches all IP addresses (i.e., the world). The next two important options are -k and -y. These two specify whether the ACK and SYNC flags in the TCP header should be set. You can use these options to block anyone from initiating a connection to your firewall. Although blocking SYNC packets originating in the Internet is generally a good idea, it does cause problems with SMTP and FTP protocols, as detailed later.

    The last important option is the -p option. It specifies the default policy. The default policy is activated when no rule matches. This rule is a safety net of sorts; it closes all the unspecified possibilities in your packet filtering. For my firewall, I have set the default policy for all groups to deny, so all unknown packets are silently ignored. A few examples of these rules are shown in Listing 3.

    Special Input Rules

    A normal firewall will not allow a client on the Internet to initiate a connection. So, all input rules related to the Internet interface have the -k option set (ACK flag must be set in TCP header). This causes two problems, however. First, if your firewall will be running an SMTP daemon refusing all SYNC packets will cause the SMTP daemon to hang. If a client on the Internet wants to send you mail via SMTP, it opens port 25 on your firewall. You can bypass this problem by specifying a special input rule for the SMTP port. The SMTP rule should specify that anybody on the Internet may open a connection to port 25, and only port 25. This rule may look like this:

    # Add input rule for Internet -> me for mail (stops at firewall)
    ipfwadm -I -a accept -P tcp -S -D 25

    This connection will stop at the firewall, so it does not need an accompanying output or forwarding rule.

    The second problem with refusing SYNC packets is created by ftp (the file transfer protocol). When transfering files, ftp opens two connections. The first connection is a control channel, which ftp uses to send commands such as change directory, obtain file listings, etc. This control connection is initiated by the machine on the local net and thus follows the normal flow (output directed traffic).

    However, when the ftp client retrieves a directory or file, ftp uses its second connection, the data connection for the transfer. The client sends the server a port command that tells the server which port on the client should be opened for transferring the data. Thus, the data connection is initiated in reverse (from outside into the local net). With all SYNC packets denied, the firewall would deny such a connection, blocking the data transfer.

    The general solution is to define a special set of rules for this ftp data connection. Because the server also uses port 20 as its end for the data connection, you can enable data transfer by including a rule that accepts connections initiated on that port number. The newer Linux kernels now fully support these port-specific rules. The pertinent rules involved for this are shown in Listing 4.

    Even without this rule, some ftp clients may be able to successfully transfer data from your host. Some ftp clients know a "passive" mode. In this mode, the client asks the ftp server to create a port for transferring data. The resulting transfer is accomplished with ACK packets, avoiding the SYNC restrictions.

    The Nameserver

    All of these IP mechanisms need access to a name server to resolve names for various destination machines. Running the nameserver on the firewall is not a good idea. First, it's always good practice to limit the number of daemons on the firewall; fewer daemons translate into fewer opportunities for a would-be intruder. Second, the nameserver mapping tables contain information on the structure of your local network. So, you need a nameserver, but it needs to run on some machine other than the firewall. Where?

    The simple answer is to host the nameserver on one of the machines in your local network. This local nameserver should be configured to resolve all name references involving local machines and to forward requests it cannot resolve to some nameserver on the Internet. The firewall will require special rules to properly handle these requests directed to the external name server. These special rules are shown in Listing 5.

    All nameserver traffic is based on the UDP protocol. The firewall itself may need to contact the nameserver, this is described in the rules nameserver > me and me > nameserver. The other rules are used for name queries from the nameserver to a machine on the Internet and back. Wherever possible, the traffic is limited to port 53, the nameserver port.

    Revising the Startup Process

    Once you have the kernel and the associated utilities installed, you can start modifying the kernel startup files. If you used Slackware 3.0 to build your Linux system, you need to change a number of configuration files. Most of these files are located in the /etc/rc.d directory. The files in this directory are executed when the system changes its running state (e.g., from single user to multiuser or back).

    First, make certain that only the necessary daemons are started. The default configuration will also start lpd and others, but you do not need those on a firewall. You also do not need inetd, as you do not want to offer services like telnet and ftp on the firewall itself.

    Last but not least, add startup commands to set the firewall rules automatically when booting. This ensures that your system won't come up in a "totally open" mode after a power outage.

    I place these rule commands in a special rc file (rc.internet) in the /etc/rc.d directory. Make certain that the rc.local script invokes the rc.internet script. With this arrangement, the firewall rules and Internet-related modules are guaranteed to be loaded on every boot cycle. As its final act, this script should start the diald daemon. The complete source for the rc.internet script and its associated files is shown in Listing 6, Listing 7, Listing 8, and Listing 9.

    The two most important files in the /etc/rc.d directory are rc.inet1 and rc.inet2. They take care of starting all the network-related daemons. To ensure that you aren't starting any unnecessary daemons, you should probably remove the call to these two files from the rc.internet script and explicitly add invocations for any daemons you really need. Listing 10 shows my rc.internet script. Once you have managed to tune these scripts you will get a very lean firewall machine with a minimum of processes running. Listing 11 shows a typical process listing from an active firewall.


    With the system in this article, you can build an Internet firewall without a large number of complicated applications. You can use a simple configuration on your local network, without the need for proxy daemons. The firewall itself is also a straightforward machine, with a minimum of processes running.

    The Linux kernel used in this firewall, 1.3.68, is relatively new. It will have to prove itself in the long run, but it looks promising.

    The hardest part of building a good firewall lies in building good packet rules. Careful analysis is important to building good rules, but the ultimate proof is thorough testing. Test these rules, test them again, and then test them some more.

    One good test strategy is to probe your firewall using Satan running on some Internet host. I cannot stress this enough, the heart of your security lies in the rules you specify for your firewall. The rules given here work for me, but that doesn't mean they'll give you a secure installation. I would never presume that they are completely watertight. If you use these rules verbatim, be warned: you do so at your own risk.

    This caution isn't meant to be intimidating; I simply want to make it clear to you that constructing comprehensive and effective rules is not a trivial task.

    About the Author

    Arthur Donkers graduated from the Delft Universiy of Technology with a degree in Electrical Engineering and a major in Computer Architecture. Since then he has worked for a number of major software houses in the Netherlands and participated in several major projects. His primary field of interest in these projects has been, and still is, datacommunications, especially the integration of multivendor networksystems. For the last four years he has worked as an independant consultant for his own company, Le Reseau (french for "The Network").