Packet Filtering in the 2.4 Kernel

The Netfilter project has brought the 2.4 Linux kernel more powerful and easier-to-use packet filtering and Network Address Translation capabilities.

Ever since the Linux 2.0 days, folks have been using Linux instead of dedicated routers and firewalls. For small and medium-sized networks which needed packet filtering or network address translation (NAT), Linux proved to be an excellent solution. Back then, ipfwadm told the kernel which packets to accept, reject, and so on. The 2.2 kernel shipped with ipchains, a re-worked packet filtering infrastructure. ipchains was more flexible than its predecessor and quite a bit more complicated to use. If you need to brush up on packet filtering and ipchains, see the Best Defense column in our May 1999 issue (http://www.linux-mag.com/1999-05/bestdefense_01.html). Now that the 2.4 kernel is in wide use, it’s time to examine this topic again. The Netfilter project built iptables for 2.4 as the successor to ipchains.

Change, Change, Change…

Right now, you’re probably thinking, “Why should I have to learn yet another mechanism for configuring filtering and NAT? Maybe I should just buy a real router!” Don’t go running off to Cisco quite yet. The 2.4 kernel contains compatibility modules that allow you to configure filtering and NAT just as you did with 2.2. You don’t need to change anything unless you want to. If that’s the case, why should you take the time to learn about iptables? The design of iptables is simpler and easier to understand for both users and kernel hackers. It was created based on how people had actually been using the filtering and address translation features in previous kernels. One of the iptables design goals was to make the interface cleaner. For more information about the motivation behind these changes, see the Best Defense column in our October 1999 issue (http://www.linux-mag.com/1999-10/bestdefense_01.html).

Using iptables to filter packets is very similar to ipchains. It still uses lists of packet filtering rules, called “chains,” and the common command-line arguments are identical. The three changes you really need know about are:

1. The changes in the built-in chains

2. The separation of NAT from filtering rules

3. The existence of extensions, including the powerful “state” extension

Let’s look at these in detail…

Chains, Chains, Chains…

iptables has three built-in chains, which have names similar to those used in the 2.2 kernels: INPUT, OUTPUT, and FORWARD. However, in iptables, the chain names are in all uppercase letters.

In older versions of Linux (2.0 and 2.2), every incoming packet passed through the “input” chain, whether it was for the local machine itself or destined to be forwarded. Similarly, every outgoing packet passed through the “output” chain, whether locally created or being forwarded from another machine. This was a bad model, because we tend to separate traffic more naturally into these three groups:

1. packets terminating at the Linux box

2. packets generated by the Linux box

3. packets passing through the Linux box

In iptables, the INPUT chain applies only to packets that are destined for the local machine itself (group 1). The OUTPUT chain applies to locally-generated packets only (group 2). The FORWARD chain only sees packets that are just passing through (group 3). In this architecture, there is only one “place” (or chain) to do packet filtering for any given packet. Not only does this reduce confusion, it also simplifies filtering rules quite a bit. The two architectures are shown in Figure One.

Netfilter Figure one
Figure One: Different packet filtering architectures.

Assuming you have a home network with a dialup PPP connection and a Linux box as your gateway/router, this rule will disallow all inbound traffic from outside (the other side of your PPP link) to the Web server running on your Linux box:

iptables-AINPUT-ptcp –dportwww-ippp+-j DROP

This command appends a rule to the INPUT chain (-A INPUT), which says that any TCP packets (-p tcp) destined for the World Wide Web port (–dport www) and coming in any interface starting with “ppp” (-i ppp+) should be dropped (-j DROP). If you wanted to stop access to any internal Web server (except those running on non-standard ports, of course), you would insert the same rule in the FORWARD chain (-A FORWARD):

iptables-AFORWARD -ptcp–dportwww-ippp+ -j DROP

If you’re still not convinced that using iptables is easier than ipchains or ipfwadm, try writing that first rule using either of those tools. You’ll find that you will need the IP address of your machine, which isn’t easy to determine if your ISP assigns your address dynamically.

Packet Filtering Extensions

As users migrated to Linux 2.2 and began using its enhanced filtering capabilities, it became clear that there are many niche uses of packet filtering that it didn’t accommodate very well. Trying to accommodate all of them was a recipe for disaster — most of us don’t want those features weighing down our kernels, and no one person could maintain them all. The solution, of course, was to make iptables extensible. In fact, you’ve already seen one extension; the -ptcp line above actually loads the tcp protocol extension, after which the following options become available for the rest of the command line:


The command iptables-ptcp-h lists all the options and prints the standard iptables help. Other types of match extensions can be loaded with the -m flag, and more than one match extension can be used in a single rule. There are also new targets that can sometimes have options; these are loaded with -j option, and a rule can only have one of them. Table One lists all iptables command-line options.

Table One: IPTables Command-Line Options

Option Provides Options Description
-p tcp –dport,–syn,… TCP protocol packet match
-p udp –sport,–dport UDP protocol packet match
-p icmp –icmp-type ICMP protocol packet match

-m limit –limit,… Match at a limited rate
-m mac –mac-source Match an Ethernet source address
-m mark –mark Match packet mark (set with -j MARK)
-m multiport –sports,–dports,… Match several TCP or UDP ports
-m owner –uid-owner,… Match packet owner (OUTPUT only)
-m state –state Match connection tracking state
-m tcpmss –mss Match the MSS option in a TCP SYN
-m tos –tos Match the TOS IP header field
-m unclean Match “suspicious” packets

-j LOG –log-prefix,… Log a packet to syslog
-j MARK –set-mark Set a packet’s mark (for routing, etc)
-j REJECT –reject-with Drop packet and send a response
-j TCPMSS –set-mss,… Set the MSS of a TCP packet
-j TOS –set-tos Set the TOS of an IP packet

The State Extension

The most interesting new extension is the state extension. This extension builds upon the connection tracking support in the kernel, which is also used by the NAT code. The connection-tracking module remembers which packets it has seen before and attempts to associate new packets with existing connections, or failing that, a new connection. In doing this, it classifies packets into four states.

NEW means that the packet appears to be part of a new connection; this does not necessarily mean it is a SYN packet, just that we haven’t seen any like it before. Retransmissions of unreplied NEW packets are also classified as NEW.

ESTABLISHED means that it looks like a reply to a packet that was let through before. After one reply is seen, all packets in that connection will be classed as ESTABLISHED.

RELATED means that the packet is not actually part of the connection, but is related to an existing one. This is used for ICMP errors that refer to a known connection. It is also used for FTP data connections, as well as IRC DCC connections.

INVALID means that it could not be associated with any existing connection and cannot start a new connection (maybe invalid, maybe we ran out of memory).

So, using the matching provided by the state extension, we can construct rules based on how the connection tracking module classified each packet. Listing One shows a simple packet filter that allows traffic out of your PPP links and filters all inbound traffic that appears not to have been initiated by an outbound connection. Of course, this only applies to packets passing through (in the FORWARD chain), but you can also apply to local packets by using -A INPUT.

Listing One: Simple Packet Filter

# Let everything out.
iptables -A FORWARD -o ppp+ -j ACCEPT

# Only let replies in.
iptables -A FORWARD -i ppp+ -m state –state ESTABLISHED,RELATED -j ACCEPT

It is important to realize that connection tracking does not drop packets (unless it runs out of memory or is otherwise under stress). Malformed packets or those it does not understand will be marked INVALID. But it is up to a packet-filtering rule to deal with them as it sees fit.

Limits of Connection Tracking

While connection tracking begins to put iptables-based firewalls on par with some of the commercial alternatives, it does have a few limitations:

  • The connection tracking code does not currently understand multicast traffic, so it will not classify the packets correctly.
  • It needs special help to understand complex protocols such as FTP, IRC, and others. There is a great deal of work being done to write the “helpers” to support these and other complex protocols.

Also, because iptables is relatively new, it hasn’t been nearly as widely deployed as ipchains. Just like with any new software, there may be several remaining bugs in the code.

Network Address Translation

Network Address Translation (NAT) is the process of altering packets as they pass one way through a gateway, and altering the replies as they pass back through the other way, in such a way that the machines communicating don’t know it is happening. Linux users most often think of “masquerading” when they think of NAT, because that was the main form on NAT available for the 2.0 and 2.2 kernels.

In masquerading, the Linux box doing the translation puts its own source address on outgoing packets. To the outside world, it looks like that is the box sending traffic. This is very useful when you have a dialup Internet connection, with a single, often dynamic IP address. The static addresses on your internal network should be chosen from the list in RFC 1918 (192. 168.1.* is a common choice) to avoid conflicting with real addresses on the Internet.

In older kernels, the method for controlling the masquerading behavior left users confused by the complicated interactions between masquerading and the packet filter itself. Neither could be designed independently of the other; your packet filtering rules had to be changed significantly when masquerading was added. Plus, the increased complexity meant that you were more likely to make a mistake, miss something, or simply run out of time trying to set up your packet filter in finite time.

In 2.4, not only was the entire system redesigned, support for new types of NAT were added, as well as Full Network Address Port Translation. The latter can replace not only masquerading, but also port forwarding and redirection (transparent proxying).

Netfilter Figure two
Figure Two: Network Address Translation in Linux 2.4.

Before we get into the details of writing NAT rules, we need to establish some terminology. Source NAT (SNAT) refers to changing the Source IP Address of a connection (where it appears to come from). Destination NAT (DNAT) is about changing the Destination IP Address of a connection (where it goes to). So masquerading is a kind of Source NAT, because the first packet we see, we change the source IP address.

Now, as shown in Figure Two, we can insert NAT rules at two places. Destination NAT can be done at the PREROUTING point, which is as the packet comes in, and Source NAT can be done at the POSTROUTING point, just before it goes out. You can perform DNAT on the OUTPUT chain as well, to change locally generated packets, but it will likely only work for certain cases. This is discussed in the NAT HOWTO. For more information, see Netfilter and IPTables Resources, .

Source NAT

Let’s assume we have a PPP link to an ISP with static IP address ( and a home network which we want to translate onto that IP address so the entire network can use the Internet. We’ll use iptables, as we did for packet filtering, but tell it to operate on the nat, rather than filter table, using -tnat as shown here:

iptables -t nat -A POSTROUTING -o ppp0 -j SNAT –to-source

That command appends a rule to the POSTROUTING chain (-A POSTROUTING), which applies to packets going out the first PPP interface (-oppp0): jump to the Source NAT target (-jSNAT) and change the source address to (–to-source

If our IP address were dynamic, we’d use the MASQUERADE target to tell the tracking code to forget all the old connections when the interface goes down. This is done so that new packets get mapped to the new address upon re-dial:

iptables -t nat -A POSTROUTING-o ppp0 -jMASQUERADE

It’s that easy.

Destination NAT

On the other side of the coin, you can change the destination address of a connection. This is often used to pass an inbound connection through to an internal box that does not have a routable IP address (such as This is called port forwarding, and in the 2.2 kernel was controlled by the ipmasqadm command. For example, if we wanted any Web connections coming in the first PPP interface to be sent through to (presumably our Web server), we would use a NAT rule like the following:

iptables-tnat-APREROUTING-ippp0-p tcp–dport www\
-j DNAT –to-dest

This appends a rule to the PREROUTING chain (-A PREROUTING), which applies to TCP packets destined for the WWW port (-ptcp-dport www) coming in on the first PPP interface (-ippp0). The rule says, “jump to the Destination NAT target (-jDNAT) and change the destination address to (–to-dest”

There is a special shortcut if you want to change the destination to the packet filtering box itself. This is commonly used for “transparent proxies,” such as squid (which can be configured as a transparent proxy):

iptables-t nat -APREROUTING-i ppp0 -p tcp –dport www \
-j REDIRECT –to-port 8080

This rule redirects all connections destined for a Web server (–dport www) to port 8080 (“–to-port 8080″) on this machine (-jREDIRECT), where squid or some other proxy daemon is presumably listening for connection. This rule has exactly the same effect as -j DNAT –to-dest 1. 2.3.4:8080 (assuming the IP address of this machine is

Transparent proxying is a very powerful technique for directing all such traffic through another daemon (content filter, cache, ad blocker) before it leaves your network. It removes the need to explicitly configure every Web browser (or client) to use the proxy. With transparent proxying, there’s no way to avoid the proxy.

NAT Helpers

As mentioned earlier, the connection tracking code needs helpers for complex protocols like FTP and IRC, which involve multiple connections. The NAT code also needs helpers for these protocols. For FTP, the helper is called ip_nat_ftp and is usually compiled as a kernel module. This means you should run the command insmod ip_nat_ftp before any ftp connections pass through your Linux NAT box. IRC should be in the official kernel by the time you read this.

Patch-o-matic: Other Extensions

A number of people have written extensions to iptables. These extensions cover packet filtering and NAT as well as other interesting (and sometimes experimental) ideas. These extensions are included with the iptables source distribution, and you can add these to your kernel by running make patch-o-matic before you do a normal make of the iptables binary. Patch-o-matic needs to know where your kernel tree is (unless it’s in /usr/src/linux, the default), so it is usually invoked as:

make patch-o-matic KERNEL_DIR=/path/to/kernel

Patch-o-matic will present you with a description of each extension. Some are very experimental, and a few are written by first-time kernel hackers, so you probably shouldn’t apply all of them. Look through all the selections, though. You might see something that you will find really useful — or at least fun to test.

Now It’s Up to You…

You can ignore everything you’ve read here and continue using the tools and configurations you’re familiar with. The compatibility modules give you that option. However, they are mutually exclusive with the new iptables module, so you cannot combine both.

And, of course, there is a catch. The old masquerading modules for some protocols (such as Quake, CuSeeMe, and others) are not supported. If you need them, you will either have to migrate to iptables or continue using an older kernel until you’re ready to upgrade.

Using the ipchains, the Linux 2.2 compatibility module is as easy as:

insmod ipchains.o

Your existing ipchains rules (including REDIRECT and MASQ) will now work as they always have.

If you’re still hooked on the Linux 2.0′s ipfwadm, you’ll instead want to use:

insmod ipfwadm.o

Your existing ipfwadm rules (including -r and -m) will now work as before.

Netfilter and IPTables Resources

Paul “Rusty” Russell develops for the Linux Kernel for WatchGuard. He can be reached at paul.russell@rustcorp.com.au.

Comments are closed.