Or: A beginners guide to iptables

Before You Start

OK, I'm going to assume that you have the following first:

  • A computer running linux, 2.4.* kernel and two working network cards.
  • An Internet connection, be it by modem (in which case you only need one network card), ADSL (if it’s over USB or a serial port then only one network card is needed), DSL, Cable etc.
  • A network where all the computers can see each other, the above linux box should have one of its network cards connected to this network. I'm assuming that the network is made of a mix of Linux and Windows machines. If you can't set your own network up then there are a lot of tutorials if you search on google
  • You have enough knowledge of linux to compile a kernel
  • You have the source for your kernel, I'll assume that it’s in /usr/src/linux
  • the latest version of iptables - it can be downloaded from http://netfilter.org
  • Optional: iproute2. This isn't strictly necessary (unless you want to do one of the things I'm detailing here). The version I'm using (which seems to be the most stable one) is 2.4.7 ss010824. It can be downloaded from ftp://ftp.inr.ac.ru/ip-routing/

Through out this write-up I am going to be referring to my own network as an example. For comparisons sake my network is as follows:

  • Internet Connection via an ADSL connection. Our ADSL modem uses a 10mbs ethernet port. The modem has its own IP.
  • A P133 based Linux server. It runs debian woody with a custom 2.4.18 kernel. One outgoing network interface (eth0). One internal network interface (eth1)
  • On the internal network there are six computers. 2 run XP, 2 run 98, 1 runs 2000 (but will move to linux when there are drivers for its 802.11b card) and one runs Lunar Linux (2.4.20ac2 kernel).
  • I've got a block of 6 IPs. One is for the modem, one for the server and four shared between the internal network. All of the following will still work even if you only have one (dynamic) IP.
  • Internal net is all on the block. Two wireless cards on the block, but they form a totally separate sub-net


Now that you're sitting at the console of the computer you're setting up we can begin. I really recommend that you work at the physical computer since this will require at least one reboot and there are plenty of places where you can easily drop your network connection to the computer, thus requiring you to go sit at the computer itself to bring it back up.

The first thing that we'll need to do is to download and install iptables. This can just be done with the normal ./configure, make, su, make install combination (you do have root don't you...) or, if you're using debian, with a simple apt-get install iptables. Once this is installed then we will need to setup the kernel. Some kernels might have the right modules installed to start with, but I'm going to assume that they don't. If you don't have the source for your kernel then either get it as a package for your distribution or download it from http://www.kernel.org

I tend to configure my kernel with make menuconfig but use which ever tool you're most comfortable with - the options will be the same. Make sure that you've selected all the options that your computer needs. If you screw up here then you'll end up with a useless kernel. Make sure you enable the following options:

  • Networking Options->Network packet filtering (replaces ipchains)
  • Then under Network Options->IP: Netfilter Configuration enable all the non-experimental options

I tend to enable all the non-experimental options so that I've got the greatest amount of leeway as to what I can do later. Its really annoying to compile your kernel just to enable an option that you could have enabled to start off with. I personally compile all of these in to the kernel rather than as modules since I would rather not have to think about modprobeing them myself but let iptables and kernel deal with it. Once you've set everything up just run make dep, make bzImage, make modules, make modules-install you will only need the last two if you have actually selected some options to compile as modules. Now go have a cup of tea, or lunch depending on the speed of your computer. Once its done all you need to do is copy the bzImage into the right place and set up lilo properly. I'd recommend keeping your old kernel as a boot option in case it all goes horribly wrong.

Set Up

Now that you've compiled your new kernel and rebooted we're ready to start setting everything up.

I'm going to assume that everything started up as it should and that your computer is working with the same functionality as it was prior to your starting this process. I'm also going to assume that you compiled all the netfilter options in to the kernel rather than as modules, so I'm not going to cover the functions of each module and which ones you'll need to modprobe.

Since you'll want this to run every time you reboot you'll want to create a shell script some where like /etc/init.d and symlink to from places like /etc/rc3.d but this will differ from distribution to distribution, so you're on your own there.

Since this is going to be a shell script lets start of by defining a few variables to make our life easier.

because I'm using bash here

use whereis to work out where iptables is for you

external interface, eth if you're using ethernet, ppp for ppp

internal interface. its customary to have the external as eth0 and internal as eth1

echo "1" > /proc/sys/net/ipv4/ip_forward 
enable IP Forwarding

Now that we've set up those variables and turned IP forwarding on then we can continue with the set up. The first thing that we need to do is to flush all the chains in iptables. This is because iptables uses three chains of rules to decide what to do with any packet. Those three chains are: INPUT, OUTPUT and FORWARD. Each one does exactly what the title suggests. INPUT deals with packets that are arriving on that computer - as such it only knows what interface it arrived on (as well as the data in the packet header). OUTPUT deals with packets that are leaving your computer - like FORWARD it only knows what interface they are leaving though, it doesn't know what interface they came in on. FORWARD deals with packets that are being forwarded from one interface to the other, so it knows both the interface the packet came from and the one that it will leave by. Each rule can do only one of four things. It can ACCEPT the packet, REJECT the packet, DROP the packet (rejects the packet but doesn't tell the remote computer that it has even seen the packet) or it will pass the packet over to the next rule if it didn't match that rule.

We will also be using an extra table of rules called 'nat'. This table contains two chains: PREROUTING and POSTROUTING. PREROUTING deals with packets before they even get to the INPUT chain. POSTROUTING deals with packets after they leave the OUTPUT chain. Similarly they deal with packets before and after they reach the FORWARD chain. The 'nat' table allows packets to be mangled - which is exactly what we're going to need it to do.

So we now need to flush the chains.

Set the default action on this chain to ACCEPT

Clear the chain (this doesn't clear the default action

Set the default to DROP

$IPTABLES -t nat -F 
Flush the 'nat' table

OK, now that we've got all the chains and tables prepared and ready to use we're going to have to split this write-up into three different sections depending on what exactly you need to do. These sections are going to be: Dynamic IP - if you get a different IP every time you sign on, or if you get your IP by DHCP (as many cable internet connections do) where there is a chance that your IP will change when your DHCP lease is renewed. Static IP - if you have... well a static IP (there’s a surprise). IP Block - if (like me) you have a block of IPs and you want to give individual IPs to computers on the internal network.

Dynamic IP

Well you've got it pretty easy here. You only need four more lines and you'll be done. So here goes:



Lets run through this line by line

The first line sets up IP Masquerading in the nat table. Basically, one the packet has been processed by the FORWARD chain it changes the source address on the packet to be the address of the external interface (if the packet had its source address as an internal IP (like 192.168.*) then the packet would never get back to you). IP Masquerading means that you don't need to know your external IP address. The "-o $EXTIF" part means that it will only apply to packets that are being sent out though the external interface, so it won't apply to packets being sent from you server to the internal network.

The second line deals with packets that are arriving at your computer. The -i $EXTIF stands for "input $EXTIF" (see why we set up the variables before) and the -o $INTIF is for "output $INTIF". So it deals with packets that are arriving on the external interface and being sent out to the internal network. This line is adding a rule to the FORWARD chain. Basically it only lets in packets that match established connections or are related to established connections (that’s the part of the line that goes "--state ESTABLISHED,RELATED -j ACCEPT"). This also makes it act almost like a cheap and cheerful firewall, by stopping people from being able to hit any of the computers on the internal net with out there being a corresponding outgoing stream.

The third line is simple. It automatically forwards packets from the internal interface to the external interface (remember that after passing through this rule the packets then hit the 'nat' rule that changes the source IP).

The last line logs the packets that don't match any of the other rules. They will be logged to somewhere like /var/log/messages. Remember that the default rule for the FORWARD chain is to DROP the packet, so any packets from the outside that don't match the second line will be DROPed.

That’s pretty much it. Save the file, chmod +x it and run it as root. You should now be setup.

Static IP

This is basically the same as for the Dynamic IP settings above, so I'm only going to explain the changes. For this you'll want these lines:



As you can see this is exactly the same as for a Dynamic IP, with the exception of the first line. If you have a static IP you could use the IP MASQ setting from the previous section and it would work, but in true linux sense, it wouldn’t be the 'right' way to do it.

As you can see the first line is exactly the same as in the previous section up until "-j SNAT ". Here, instead of using the built in MASQ module, we are using Source NAT (SNAT) to rewrite the source IP to one of our choosing, although this obviously isn't very useful unless you put your own IP in.

As above you can now save the file, chmod +x it and run it. It should all work as planned.

IP Block

I've got a block of IP's allocated and registered to me. I was using up two IPs from my six by allocating them to my modem (which demands one) and my server. What I wanted to do was to assign IPs to computers that are internal to my network. So what I did was to bind all my 'real' IPs to the external interface and then use Source NAT and Destination NAT to pass them straight through the server with out too much tampering. There may well be a more elegant way to so this, but I couldn't find it while I was searching for it.

For this we will need the "iproute2" tools that I mentioned at the top. If you don't have them already then go and get them now. Once you have them then you will need to put the following command in at the shell prompt (as root) for each external IP you have, barring the one that the external interface is already set to. This command binds extra IPs to the interface.

ip add dev

We use the same block of commands as we did for Static IP, but before that block we need to add three extra lines for every computer you are assigning an IP to. Obviously you can only do this for as many computers as you have and for as many IPs as you have. The remaining computers will just have to look like their IP is that of server (which is what happens in both the Dynamic and Static IP sections above). So onwards! These are the lines that you need:

$IPTABLES -t nat -A POSTROUTING -p all -s  -j SNAT --to 
$IPTABLES -t nat -A PREROUTING -p all -d  -j DNAT --to 

Its very important that these lines are above the lines used in the Static IP section, or it will fail since iptables will never get to these rules as it will have matched the previous rules already.

The first line. Any packet from a certain ip ("-s <internal ip>" - source <internal ip>) will get its headers rewritten so that its source is now the external ip that you've set. It will then get send out by the rule in the FORWARD chain that forwards all the packets from the internal interface to the external interface.

The second line. This line will match any packet that has arrived for the external IP that you set. It uses Destination NAT (DNAT) to rewrite the destination of the packet from the external ip to the internal ip. This will allow the server to then route the packet properly.

The third line allows the packet to bypass the FORWARD rule that requires it to match an outgoing stream. It just tells iptables to forward all packets which have that internal IP as their destination out through the internal interface.

Now all you need to do (as with the other sections) is to save, chmod +x and run the script. Hopefully it will run with out any problems, providing you have the IPs set up right.

The End

That’s it... if you have any problems or questions then send me a message and I'll do my best to help.

man pages
iptables HOWTO
Advanced Linux Networking HOWTO
Trial and Error on my own network....