Introductory blurb
I've elected to do three separate guides for iptables that will cover three topics. These will be as follows:
- Basic protection, granting access to services based on IP and/or MAC address, and Debian integration
- Protection against port scanning, and blacklisting addresses that "misbehave"
- Port knocking
First things first
Make sure you have the "iptables" package installed. Use your favourate package manager to do it, I won't tell you how.
Big fat notes
- This guide only deals with filtering. It will not deal with routing or mangling. If you don't know what these are, don't worry
I intend this guide to be for people who just want to protect their desktop/laptop from nasty people. - When building a firewall, you should know exactly what you want to do with it from the outset. Firewalls take thought and planning. By all means, keep reading if you havn't a clue, and you might get some ideas, but it isn't the kind of thing where you just throw in commands and play around.
- All of the commands need to be ran with root privilages. Amend them as you need to (be it prefixing sudo, or whatever). I just use a root console myself, but hey, I'm old fashioned.
- If you set up your firewall badly, things will probably stop working. IP tables doesn't save the tables itself, so if you reboot, it will clear everything. This could save you. However, if you are doing this on a remote machine, that could be easier said than done

- If this guide scares you, then a GUI firewall builder might be better for you. There are many out there that are only a search away. I personally like kmyfirewall.
- Read this guide before you do it. In fact, read any guide before you do it. It's just good sense.
IP tables basics
There are three types of packets you will normally encounter when filtering:
- INPUT - these are inbound packets. Things like people trying to access yours. This includes you trying to access your own services. Remember that last bit, it is important.
- FORWARD - These are routing packets. As said earlier, I won't deal with those, thus we will just block them.
- OUTPUT - These are packets that you send.
- ACCEPT - allow the packet to do it's business.
- REJECT - send the packet back to source with a little note saying that it isn't allowed in the system.
- DROP - ignore the packet.
Now, lets get down to how IP tables actually works. Each type of packet (INPUT, FORWARD and OUTPUT) are processed separately, but in the same manner. IP tables has a "table" of rules for each of the three packet types. For each table, there is what is termed the "Default Policy", which is either ACCEPT, REJECT or DROP. It literally starts at the top of the table, and works down until something matches the packet. If none of the rules in the table match the packet, then the default policy is applied. Make sense? Probably not, but hopefully it will shortly. On top of the three tables that IP tables gives you by default, you can make custom tables that you can use. This lets you group related rules together, and make it a bit tidier. They arn't needed, but it's nice to keep things clean.
Now, lets look at how rules are constructed. There are three parts to a rule: the table, the match criteria, and the target. The table is simply what table the rule gets inserted into (INPUT, FORWARD, OUTPUT or a custom table). The match criteria describe the situations that you want the rule to apply to (which are things like ports, addresses, and so on), but we'll cover these later on. The target is a little more interesting. The IP tables matches a rule, it sends the packet to a particular "target". These targets can be other tables, for additional processing, or it can be one of ACCEPT, REJECT and DROP. This is probably clear as mud just now, but once you see some rules, you'll be able to see how it comes together.
But what does it look like?
If you run "iptables" from a terminal, you'll get a lovely message saying "no command specified". Not what we're really after if we want to see the current table. We can do this using the "-L" option (L for list). This is what a blank table looks like:
- Code: Select all
marbles:~# iptables -L
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
Bringing it all together
So, we've covered how tables are processed and how rules are made in a very hand-waving "take my word for it" kind of way. Now comes the fun bit.
I work by this philosophy: nobody should be in my computer except me, and I don't want to be restricted in my surfing. This should imply to you that we want to keep as much of the incoming traffic out of the computer while letting everything leave the computer, which should start the cogs turning. Obvious candidates for the default policies are ACCEPT for OUTPUT, and either REJECT or DROP for INPUT. See where I got this from? If not, then I'd recommend a GUI firewall builder. Seriously.
Whether you use REJECT or DROP is purely a matter of preference. REJECT tells the source that a computer is there and that their packet has been blocked, but with DROP the source computer doesn't have a clue what has happened (but can probably guess). I personally favour DROP, because I like to be "invisible"
So we've dealt with the INPUT and OUTPUT default policies, but what about FORWARD? As this guide ignores routing, we want the computer to follow suit, so we'll have the FORWARD policy as DROP.
To set our default policies, we use the "-P" option (P for policy) on each table, like so:
- Code: Select all
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
- Code: Select all
Chain INPUT (policy DROP)
target prot opt source destination
Chain FORWARD (policy DROP)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Some basic input exceptions
So now we have locked down our computer. What about everything that had connections before? Things like current SSH connections (which might include the one you are currently accessing the computer with
IP tables lets us check the "state" of connections, i.e. whether it is a new connection, an existing connection, related to an existing connection and so on. What we want to do is allow all existing connections, and any connections that relate to the existing connections. Confused? Good
- Code: Select all
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
- "-A INPUT" sets the table. It reads as "Append to the INPUT table"
- "-m state --state RELATED,ESTABLISHED" is our matching criteria. It literally reads as "Use the state module, and match related or established connections"
- "-j ACCEPT" is our target. It literally reads as "If the matching criteria fits, jump to ACCEPT". This is basically, if we match, accept the packet.
- Code: Select all
marbles:~# iptables -L
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT 0 -- anywhere anywhere state RELATED,ESTABLISHED
Chain FORWARD (policy DROP)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
The next rule I always use is to allow any local incoming packets. Ever wondered what your loopback device ("lo" in ifconfig) is for? It is used when you access a service locally, such as SSH into yourself, view your own web server, access your local IMAP server, etc. So, if we want to be able to get to our own services (which you usually want to do), we need to add a rule that "trusts" the loopback device. Here goes:
- Code: Select all
iptables -A INPUT -i lo -j ACCEPT
Now, if we look at our table, you'll see the most useless line ever:
- Code: Select all
arbles:~# iptables -L
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT 0 -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT 0 -- anywhere anywhere
Chain FORWARD (policy DROP)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
- Code: Select all
marbles:~# iptables -vL
Chain INPUT (policy DROP 91 packets, 8249 bytes)
pkts bytes target prot opt in out source destination
59 5901 ACCEPT 0 -- any any anywhere anywhere state RELATED,ESTABLISHED
11 660 ACCEPT 0 -- lo any anywhere anywhere
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 131 packets, 9383 bytes)
pkts bytes target prot opt in out source destination
With this table, you now have a pretty secure system. You can send out what you like, but nobody else will get in. Nice
Allowing access
There are three matching criteria that I think are useful when allowing packets in: port (which is set by the "--dport" option), source IP address (which is set by the "-s" option), and source MAC address (which is a little more complicated). You can use any combination of the three to build your firewall, and the order doesn't matter because they all need to match. For matching ports and such like, you need to also match the protocol using the "-p" option. This will be either "-p tcp" or "-p udp". Lets do some examples.
I want all computers to be able to access my webserver, which runs on TCP port 80. Hopefully now, you'd be able to give making this rule a bash yourself, because it is pretty simple. Here is the rule:
- Code: Select all
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
- Code: Select all
marbles:~# iptables -L INPUT
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT 0 -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT 0 -- anywhere anywhere
ACCEPT tcp -- anywhere anywhere tcp dpt:www
Now, I want only my laptop to be able to access SSH. My laptop always has IP address 192.168.0.3, and lets say it's MAC address is 00:11:22:33:44:55. An appropriate rule would be this:
- Code: Select all
iptables -A INPUT -p tcp -m mac --mac-source 00:11:22:33:44:55 --dport 22 -s 192.168.0.3 -j ACCEPT
- Code: Select all
mac
--mac-source [!] address
Match source MAC address. It must be of the form XX:XX:XX:XX:XX:XX. Note that this only makes sense for
packets coming from an Ethernet device and entering the PREROUTING, FORWARD or INPUT chains.
And as always, it makes our table look like this:
- Code: Select all
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT 0 -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT 0 -- anywhere anywhere
ACCEPT tcp -- anywhere anywhere tcp dpt:www
ACCEPT tcp -- 192.168.0.3 anywhere MAC 00:11:22:33:44:55 tcp dpt:ssh
Just for completeness, if you only want to allow access by IP address, and not bother with MAC filtering, you would use:
- Code: Select all
iptables -A INPUT -p tcp --dport 22 -s 192.168.0.3 -j ACCEPT
What about allowing access from a particular range of IP addresses? Perfectly reasonable thing to ask, and it took me ages to figure out. Say you want to allow port 80 to addresses 192.168.0.2 through to 192.168.0.8. From what I have above, that would be 7 rules. But what if you want to do everyone from 192.168.0.2 to 192.168.0.200? "Eek" is an appropriate word here, so we use the "iprange" module like so:
- Code: Select all
iptables -A INPUT -p tcp -m iprange --src-range 192.168.0.2-192.168.0.200 --dport 80 -j ACCEPT
Something that isn't just tcp
Bittorrent is a prime example of how what I've covered so far doesn't work, because I've only done TCP so far, and Bittorrent uses TCP and UDP. For arguments sake, by Bittorrent client is configured to use ports 10000 to 10020. Don't ask why, I can't even remember why I picked them. I digress.. Anyway, here is the line we would use for the TCP part:
- Code: Select all
iptables -A INPUT -p tcp --dport 10000:10020 -j ACCEPT
- Code: Select all
iptables -A INPUT -p udp --dport 10000:10020 -j ACCEPT
But that's not all! What about flood pings?
Having covered TCP and UDP, we've missed one out, our old friend ICMP! For those of you that don't know what ICMP is used for, the best description I've ever come up with is "It's used for pings and stuff". I personally don't like pings. Once your network is working, the only thing they are useful for is flood pinging each other for fun (or for evil purposes). So lets block them completely!
- Code: Select all
iptables -A INPUT -p icmp -j DROP
- Code: Select all
iptables -A INPUT -p icmp -m limit --limit 1/second --limit-burst 5 -j ACCEPT
iptables -A INPUT -p icmp -j DROP
- Code: Select all
limit
This module matches at a limited rate using a token bucket filter. A rule using this extension will match until
this limit is reached (unless the ‘!’ flag is used). It can be used in combination with the LOG target to give
limited logging, for example.
--limit rate
Maximum average matching rate: specified as a number, with an optional ‘/second’, ‘/minute’, ‘/hour’, or
‘/day’ suffix; the default is 3/hour.
--limit-burst number
Maximum initial number of packets to match: this number gets recharged by one every time the limit speci-
fied above is not reached, up to this number; the default is 5.
You can of course mix this with anything else described above. I, for example, allow any pings (be them flood or not) from my laptop by matching IP address and MAC address, and every other computer on the network is only allowed to ping me once every second. This is just combining what we already know and getting the order right like this:
- Code: Select all
iptables -A INPUT -p icmp -m mac --mac-source 00:11:22:33:44:55 -s toaster -j ACCEPT
iptables -A INPUT -p icmp -m limit --limit 1/second --limit-burst 1 -j ACCEPT
iptables -A INPUT -p icmp -j DROP
Saving and loading rules
We use the two commands "iptables-save" and "iptables-restore" to do this. They don't work quite as I ever expected, but are still very handy. If I run "iptables-save" just now using the last example (SSH from my laptop), I get this:
- Code: Select all
marbles:~# iptables-save
# Generated by iptables-save v1.3.6 on Mon Jun 18 11:05:39 2007
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [15605:1128558]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -s 192.168.0.3 -p tcp -m mac --mac-source 00:11:22:33:44:55 -m tcp --dport 22 -j ACCEPT
COMMIT
# Completed on Mon Jun 18 11:05:39 2007
- Code: Select all
iptables-save > mysuperrules
- Code: Select all
iptables-restore < mysuperrules
Debian Integration
Debian doesn't have any "proper" way of setting iptables rules, but it's quite easy to make it do it for us. First, I like to make a place to keep my rules (I have a few rules for different scenarios, such as laptop in the house, laptop roaming, that kind of thing). You're choice, I use /etc/iptables/ to save them. So lets say our rules are saved in the file /etc/iptables/myrules. Just now, my /etc/network/interfaces file looks like this:
- Code: Select all
auto lo eth0
iface lo inet loopback
iface eth0 inet dhcp
- Code: Select all
auto lo eth0
iface lo inet loopback
iface eth0 inet dhcp
pre-up /sbin/iptables-restore < /etc/iptables/myrules
Conclusion
Well, thats basically it for now. We have covered locking down the computer and then granting access by IP address and MAC address, TCP, UDP and ICMP filtering, filtering by consecutive and non-consecutive ports, limiting packet rates, packet states, and getting it all to work with Debian nicely. I'd say experiment and read the man page now, just so you get a feel of just how powerful this program really is. Remember, if you mess it up, iptables resets when you restart your machine, so it isn't the end of the world.
Next time: portscan protection and blacklisting
Cheers,
Chris