Learning iptables from scratch.
By José Roberto Negreira a.k.a Xous

Version 2005-MAR-09

Disclaimer

This document is provided AS IS without any warranty. (By the way, you'll hardly find free docs on internet where authors do provide some warranty about things that may go wrong). The eventual reader is responsible about his/her actions and consequences performed after reading this document. I hope this disclaimer hasn't blamed anyone, but it is really needed to avoid any kind of problems, just because a reader decided to misconfigure, touch, break his enterprise firewall following this article's instructions.

Revision History

2005-xxx-xx: published on iptableslinux.com
2005-MAR-08: 3 years after! translated to english
2002-MAR-02: Published on XousLAB (Thanks to myself :p)
2002-FEB-27: Fixed an error in a rule (Gracias Kremar)
2002-FEB-24: Published on PsicoFXP, security Forum
2002-FEB-16: Published on nnl (thanks Cyrano)


Intro

When we connect to the internet at home, we're connecting 'naked' if the analogy is permitted. The goal of this document is, using iptables, try to reach some kind of protection. Note that a firewall doesn't guarantee 100% security (nor any product does!)
To read this, it's required basic/intermediate knowledge about linux and TCP/IP (the linux implementation of TCP/IP obviously)
To use iptables, the ip_tables module must be loaded. This is default on most linux distributions. If you are using ipchains, you will need to unload its modules before loading the iptables ones.
iptables is a nice linux utility, used to send to the kernel some directives. What kind of directives? You can command your kernel about filtering TCP/IP packets. A TCP/IP packet consists of serveral fields with additional information beside data itself. Describing each of them goes beside this article. We'll use the more relevants, the ones that will be useful for us and that our linux box will start watching carefully. E.G.: source IP, destination IP, source port, destination port, etc.

How does it work?

The linux kernel has 3 predefined (empty) chains of rules: INPUT, FORWARD and OUTPUT. Each TCP/IP packet that enters/travels/leaves from/to a computer with iptables working, will also traverse the INPUT, FORWARD, or OUTPUT chain respectively. The tables, are just a chunk of rules, which we will control each one of the passing packets. Now you may wonder: what is a rule? Just to avoid confussion, I'll try to simplify its definition as much as possible, and then show you some examples. A rule consists of just 2 parts, a CONDITION and an ACTION. If the packet matches the condition, the ACTION is executed. Simple, really?
Take note for beyond: if a packet traversed all the chain rules without match (or we could say, the packed 'survived' the chain), iptables will check the packet on the chain's default policy

Kernel? forward? packets? chains? rules? I feel a bit dizzy...

Do not panic. To sum up:
1) the most important thing...you must have previous knowledge, about linux and TCP/IP
2) The linux kernel has built-in 3 primary chains (INPUT, FORWARD and OUTPUT)
3) With iptables, you can add/modify/delete rules (among other things)


This is a great moment to write on your prompt "man iptables", if you haven't done it yet, to have a greater idea about this great utility. Nevertheless, it's not a must to continue reading this =)
Let's suppose the following scenario:
Once upon a time, there was a local network (192.168.1.0) with 50 workstations, and of those we'll distinguish these ones:
1) intruder.iptableslinux.lan (192.168.1.15) <--- an excesively curious guy
2) neutral.iptableslinux.lan (192.168.1.18) <--- someone that doesn't matter
3) jose.iptableslinux.lan (192.168.1.20) <-- our beloved linux box
We are root on "jose.iptableslinux.lan", we're running an ftp service, and we certainly know that the owner of "intruder", enjoys investigating on other people's pc's using his network interface card.

Experimenting with icmp packets


This is the real first approach with iptables. Let's see the current state of our 3 chains, listing their contents. We'll execute this command:
[root@jose /]# /sbin/iptables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

If the command output isn't like the above stuff, means than probably you don't have installed the needed modules on the kernel, or that there's still a working firewall, or to confuse you even more: this means that a script has been executed (probably on system startup) where the firewall rules are loaded. You should check the docs of your current linux distribution.


Assuming that the command output is exactly the same as above, your situation is the following:
All packets are accepted by the firewall, or in other words, nothing at all is filtered. This indicates us 2 things:
1) the three chains (INPUT/FORWARD/OUTPUT) are empty.
2) the three chains have set "ACCEPT" action as the default policy.

So, it is normal that if we ping localhost (ourselves), we'll get an answer:


[root@jose /]# ping localhost
PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.
64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=0 ttl=255 time=0.3 ms
64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=255 time=0.1 ms
64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=2 ttl=255 time=0.1 ms
(and so forth...)

Let's add a rule, to understand how things work:


[root@jose /]# /sbin/iptables --append INPUT --protocol icmp --source 127.0.0.1 --jump DROP


What was that? Simple: Append the following rule to the INPUT chain: when an icmp type (--protocol icmp) packet which source is 127.0.0.1 (--source 127.0.0.1) and with any destination (it is not specified), we'll ignore that packet, or just...drop that packet (--jump DROP).


What happend? nothing! Really? are you sure? Then let's check again the rules on our chains (did you forget how to do that?)


[root@jose /]# /sbin/iptables -nL
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP icmp -- 127.0.0.1 0.0.0.0/0
Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination


We'll notice that on the INPUT chain, we've got a rule now, which goal is avoid pinging ourselves. You'll also notice that this rule isn't very useful itself, but it's a nice example to understand how things work, as we said before. So? what's next? Checking that things work, of course!!!
[root@jose /]# ping localhost
PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.
(and it'll stay quietly waiting an answer that'll never arrives...)

Our firewall is invisible? Of course not! In fact, we could check from "intruder" workstation, that ping has no problem at all. Intruder pings, and our machine answers.

[root@intruder /]# ping jose.iptableslinux.lan
PING jose.iptableslinux.lan (192.168.1.20) from 192.168.1.15 : 56(84) bytes of data.
64 bytes from 192.168.1.20: icmp_seq=0 ttl=255 time=2.524 msec
64 bytes from 192.168.1.20: icmp_seq=1 ttl=255 time=1.319 msec
So, using just ping, our friend from "intruder" can easily notice our presence. Pay attention, this is just the beginning.

Filtering packets from outside


Our firewall is currently filtering the local ICMP packets, and it allows the remote ones. Let's change the
game rules, or our firewall rules ;)

We'll delete the recently added rule, this can be done with the "--delete", and the rest is the EXACT transcription of the rule that we want to delete:
[root@jose /]# /sbin/iptables --delete INPUT --protocol icmp --source 127.0.0.1 --jump DROP

Note: knowing that the rule was the only one, we would get the same effect with this command (instead of repeating the rule, we put the rule number):
[root@jose /]# /sbin/iptables --delete INPUT 1

Now the stuff that matters: filter the icmp packets from network!!

[root@jose /]# /sbin/iptables -A INPUT -p icmp -s 192.168.1.15 -j DROP

Also note that writing -A is the same as --append, also -D or --delete, -p o --protocol, an so on.
Now we'll list again our INPUT chain:

[root@jose /]# /sbin/iptables -nL INPUT
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP icmp -- 192.168.1.15 0.0.0.0/0

Great! now any icmp packet which source is 192.168.1.15 will be dropped. That's why our curious friend tries to ping from his machine to ours, he'll never get a response.

[root@intruder /]# ping jose.mired.lan
PING jose.mired.lan (192.168.1.20) from 192.168.1.15 : 56(84) bytes of data.

Fantastic! Our linux box is invisible to our friend, we've done it. Our machine is safe, isn't it?
No! NO NO NO!!
Our friend has turned even more curious because our machine don't reply ping, thought that our PC was turned off, but after that he wrote:

[root@intruder /]# ftp jose.mired.lan
Connected to jose.mired.lan.
220 jose FTP server (Version wu-2.6.1-16) ready.
Name (jose:root):

Oh oh! We've forgotten our FTP server running...then in a hurry we add the following rule:

[root@jose /]# /sbin/iptables -A INPUT -p tcp -s 192.168.1.15 --dport 21 -j DROP

We list once again the INPUT chain

[root@jose /]# /sbin/iptables -nL INPUT
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP icmp -- 192.168.1.15 0.0.0.0/0
DROP tcp -- 192.168.1.15 0.0.0.0/0 tcp dpt:21

Our friend will get bored waiting any response from our ftp (but will never happen). Interesting, isn't it?

Now: let's think a bit: what happens if on most of our 50 PC operators, are "curious friends". Should we add a rule for each one of them? It would be a tedious work, and hardly efficient.
Beside that, a really important detail: even though all these rules could be applied without any trouble and are sintacticall correct, a _CONSIDERABLE_ suggestion is follow the "all that is not explicitly permited, then is prohibited" philosophy.

How can we achieve this? The first step, is set the default policy on "DROP". Any packet that doesn't match any rule will be dropped. We change our way to think, now each packet survives the chain if it matches a rule.

In case that you are accesing locally to your linux box, just ignore the following paragraph:


IMPORTANT NOTICE: for those that are accessing your box remotely (via telnet or ssh), if you set your INPUT policy as "DROP", you'll suffer a disconnection. This actually happened to me. That's why you should previously add the following rule:
[root@jose /]# /sbin/iptables -A INPUT -p tcp -s #SOURCE# --dport #PORT# -j DROP

Where #SOURCE# is the IP Address from where you're connecting to the linux box, and #PORT# should be 22 (if you connect via ssh), or 23 (if it's telnet).


Now, as we said, we'll continue setting the INPUT policy on "DROP", (with the -P option)
[root@jose /]# /sbin/iptables -P INPUT -j DROP

We'll empty all chains with the -F (Flush) Option.
[root@jose /]# /sbin/iptables -F

We list the rules of our INPUT chain
[root@jose /root]# /sbin/iptables -nL INPUT
Chain INPUT (policy DROP)
target prot opt source destination

Even though we don't have any rule, nobody will be able to access to our PC. Any packet tha arrives, is exposed to each rule on the INPUT chain (in our case, it's empty!), as no match was produced, the chain policy decides the packet destiny, and it is: "DROP". The current problem is that even though we can make outgoing connections, all responses will be dropped. To solve this, we'll add the following rule:

[root@jose /root]# /sbin/iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

What does it means? we allow any packet which connection was already established, or which connection is new, but is related to an established connection. This is what iptables man pages say (I told you to read them, didn't I?)

Again, yes...again we list our INPUT chain:

[root@jose /root]# /sbin/iptables -nL INPUT
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED

Nobody will be able to initiate a connection to our machine, unless we specify it. We have a DROP by default. Now we can start allowing connections.

Local access:
[root@jose /root]# /sbin/iptables -A INPUT -i lo -s 127.0.0.1 -j ACCEPT

-i lo : we specify the packets coming from the localhost interface.

[root@jose /root]# /sbin/iptables -nL INPUT
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 127.0.0.1 0.0.0.0/0

We allow to "neutral" ftp access (port 21)
[root@jose /root]# /sbin/iptables -A INPUT -p tcp -s 192.168.1.18 --dport 21 -j ACCEPT
[root@jose /root]# /sbin/iptables -nL INPUT
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
ACCEPT all -- 127.0.0.1 0.0.0.0/0
ACCEPT tcp -- 192.168.1.18 0.0.0.0/0 tcp dpt:21


Useful advices and final comments
=================================

As long as you start learning how to use iptables, you'll start allowing (or not) the access to your PC, network, services, matching by source or destination port, source or destination host (or network), etc. It's just a minimal part of the whole you can do with iptables.

Don't turn off your linux box. All changes to the iptables chains on-the-fly are NOT saved in any files, just on the RAM, and as you may know, when you reboot, you lose changes.

1) Store all your rules in a tidy script, with comments explaining each applied rule or each group of rules.

2) if you consider running the script automatically after booting, you should start it before starting any service, or even better: before starting up any network interface. Don't worry about rules that reference interfaces not yet started, they're still accepted by iptables.

3) Keep on reading iptables man pages. Notice that it is *NOT* an usual RTFM advice, because the iptables man pages are a really complete, excellent source of information, well and of course, the infinite documentation that you'll find on the net.

As the last words, I hope that you've enjoyed reading this doc, as much as I've enjoyed writing, and translating them. :)

I would be glad to receive feedback (no flames please!), just to know if this document has been useful, and of course, any advice, corrections, etc.
feedback at iptableslinux.com

Look for the lastest version of this document at:
http://www.iptableslinux.com

NOTE


You must notice that FTP protocol is atypical, or at least, different to other protocols (TELNET, SSH, etc). It has 2 connection modes: active and passive. The "active" mode is supported by any FTP client, opens 2 connections. Beside the one from the client to the server, the server opens an additional connection to the client (from and to any port, unless the server allows to specify some range). On the other hand, on passive mode, both connections are opened from the client and the server (ports 21 and 20 by default). You should consider this when you, being the client and behind a firewall in a "protected" network, need to connect to an FTP server outside the firewall.

© 2005 iptableslinux.com
info at iptableslinux dot com