Netfilter Tutorial


I first wrote this guide in 2003, when the 2.4.x kernel was still relatively new. Back then, I was an avid user of Debian GNU/Linux. I kept the guide updated until about 2007, when I became unhappy enough with Linux in general to jump ship. I used to love that Linux development had such an evolutionary feel to it, with various sets of features improving or being pruned, leading to a system that improved by leaps and bounds. However, my excitement for that pace of development lessened as I found myself updating older systems which depended on parts of the kernel that were just no longer there. The limbs my machines walked on had become vestigial. I wanted something with a bit more of a development plan. Something with a bit more structure. I now happily use FreeBSD and OpenBSD.

So, I no longer use iptables, and no longer have a linux box to even test iptables configurations on. I'll gladly offer what answers I can via email about netfilter, but my knowledge of it is both outdated and rusty. I'm leaving this guide up as a reference, as most of the material in it is still valid. Please, if you find an error or have a suggestion, please feel free to contact me.

I can't recommend the iptables manpage enough, I found it to be an incredible reference all the years I was using iptables. Seriously, read it.

Tables, Chains, and Rules

The tables are filter, nat, and mangle. The names of the filter and nat tables pretty much explain what they do. The mangle table is used for 'specialized packet alteration', and is really beyond the scope of this tutorial. I've never had a reason to use it, and for the rest of this document will ignore it.

The purpose of a table is to contain chains. Inside the filter table are the INPUT, FORWARD, and OUTPUT chains. Inside the nat table are the PREROUTING, POSTROUTING, and OUTPUT chains. The user can also define more chains, and place them in whatever table they want.

Chains contain rules. Rules can be terminating, or non-terminating. A packet enters evaluation based on its type (incoming, outgoing, or forwarded), then traverses tables and rules until it encounters a terminating rule. The order of traversal is as follows:

incoming nat/PREROUTING -> filter/INPUT
forwarded nat/PREROUTING -> filter/FORWARD -> nat/POSTROUTING
outgoing nat/OUTPUT -> filter/OUTPUT -> nat/POSTROUTING

Adding Rules to a Chain

The command for adding a rule to a chain follows this form:

iptables -t table -A chain specifiers -j target

This will add the new rule at the end of the specified chain


Specifiers include (but are not limited to)

-s ADDRPacket came from source ADDR
-d ADDRPacket is going to ADDR
-i IFACEPacket came from interface IFACE
-o IFACEPacket will leave through IFACE
-m CONDPacket matches the condition COND.
-p PROTOPacket uses the protocol PROTO

Addresses can be fully qualified domain names (""), IP addresses (""), CIDR style network specifications (""), or the logical not of any of those things (ie "!").

Interfaces are eth0, ppp0, tun0, and so on. Protocols are tcp, udp, and icmp.

Conditions can be a complicated beast, and I won't talk much about them. For more information, read the 'man iptables'. I only use two conditions '-m state --state STATES' and '-m limit'.

Possible states for '-m state' are NEW, RELATED, ESTABLISHED, and INVALID. The names describe the meaning pretty well.

The '-m limit --limit TIME' lets you limit how frequently a given rule is matched. This is most useful when used with the LOG target, to keep your system logs from filling up. This pops up in the example script at the end.


Targets include (but are not limited to)

ACCEPT Accept this packet.
Valid in the filter table
DROP Ignore this packet. The sender gets no notification.
Valid in the filter table
REJECT Reject this packet, and send an icmp message back to the sender to indicate that this packet died.
Valid in the filter table
SNAT --to-source ADDR Change the source address of this packet to ADDR.
Valid in the nat table
DNAT --to-destination ADDR Change the destination address of this packet to ADDR.
Valid in the nat table.
LOG Log this packet to syslog.
Valid in all tables
CHAIN Punt processing of this packet to CHAIN
Terminates if CHAIN contains a terminating rule that matches this packet
Valid in all tables

Chain Manipulations

iptables -t table -N chain Create a new chain called chain inside table
iptables -t table -F chain Remove all rules from chain in table
iptables -t table -X chain Delete chain from table

Built-in chains (INPUT, FORWARD, OUTPUT) can also have a 'policy', which will be invoked if no other rule on the chain matches. These policies can be ACCEPT, REJECT, or DROP. Policies are set with 'iptables -t filter -P CHAIN policy'.

Tips and Style Issues

Things to keep in mind when using netfilter (mostly style issues):

This example is for a firewall and NAT box

IFACE=#the public interface of your box.
PUBLIC=`ifconfig $IFACE | grep "inet addr" | cut -d ":" -f 2 | cut -d " " -f 1`

#That shell-insanity will extract the public IP of $IFACE for you.
#This can be useful if you're on a PPP connection, and don't always know
#What the IP of $IFACE will be.

#The IP ranges of your local net. Example:

/sbin/modprobe -k ip_conntrack_ftp

#Clear all chains, and set default policies.
#I consider it good practice to set your policies to DROP
#whenever you are changing your firewall configuration

iptables -t filter -P INPUT DROP; iptables -t filter -F INPUT
iptables -t filter -P FORWARD DROP; iptables -t filter -F FORWARD
iptables -t nat -F POSTROUTING

#Set up a chain for bad packets.
#Packets sent here will be logged, no more than five per hour from the
#same source, and then dropped.

iptables -F log; iptables -X log; iptables -N log
iptables -A log -m limit --limit 5/hour -j LOG
iptables -A log -j DROP

iptables -t nat -A POSTROUTING -s $LOCALNET -d \! $LOCALNET -j SNAT --to-source $PUBLIC

#Packets to accept on the public interface
#Catch and log 'INVALID' packets
iptables -t filter -A INPUT -d $PUBLIC -m state --state INVALID -j log

#Accept packets that are from and to the loopback interface.
iptables -t filter -A INPUT -s localhost -d localhost -j ACCEPT

#Accept packets relating to already-established connections
iptables -t filter -A INPUT -d $PUBLIC -m state --state RELATED,ESTABLISHED -j ACCEPT

#Accept ssh connections
iptables -t filter -A INPUT -p tcp -d $PUBLIC --dport 22 -j ACCEPT

#Add entries for other services that you want to accept here.

#Anything else gets dropped and logged.
iptables -t filter -A INPUT -d $PUBLIC -j log

#Packets to forward from private outward.
#Catch and log any INVALID data.
iptables -t filter -A FORWARD -m state --state INVALID -j log

#Accept packets related to pre-existing connections
iptables -t filter -A FORWARD -d $LOCALNET -m state --state RELATED,ESTABLISHED -j ACCEPT

#Allow the private network to connect to anything
iptables -t filter -A FORWARD -s $LOCALNET -j ACCEPT

#Drop and log anything else
iptables -t filter -A FORWARD -j log

Common Mistakes (or Don’t Follow in my Footsteps)

More will be added to this as I remember all of the various idiotic things I’ve done in the past.

Overly General Rules

Consider the following situation: You have a firewall (say it’s Behind it is both your private internal network, and your webserver. You want to redirect traffic on port 80 to the webserver (say it’s You write the following:

iptables -t nat -A PREROUTING -p tcp –dport 80 -j DNAT –to-destination

This will redirect traffic from outside to your webserver. However, it will also redirect traffic from inside to your webserver as well. God help you if your firewall is your webserver, and you want to go to a website. A more appropriate rule would be:

iptables -t nat -A PREROUTING -p tcp -s ! –dport 80 -j DNAT –to-destination

Similar kinds of things can happen if you have a web proxy that you want to redirect traffic from inside thorough. Remember to let traffic from the proxy out.

Other References