Advanced Features of netfilter/iptables
Originally published in the LinuxGazette.net, November 2004, Issue 108.Introduction
It is commonly known that netfilter/iptables is the firewall of the Linux operating system. What is not commonly known is that iptables has many hidden gems that can allow you do things with your firewall that you might never have even imagined. In this article I am going to introduce many of these features with some practical uses. If you are not au fait with the basics of iptables then you should read my previous article in the Gazette, "Firewalling with netfilter/iptables".The following features are discussed:
- Specifying multiple ports in one rule
- Load balancing
- Restricting the number of connections
- Maintaining a list of recent connections to match against
- Matching against a string in a packet's data payload
- Time-based rules
- Setting transfer quotas
- Packet matching based on TTL values
All of the features discussed in this article are extensions to
the packet matching modules of iptables. I used only two of these
extensions in the previous article: the --state
module
which allowed us to filter packets based on whether they were
NEW
, ESTABLISHED
, RELATED
or
INVALID
connections; and the multiport
extension, of which I will go into more detail on in this
article.
Some of the modules introduced in this article (marked with an asterisk) have not made their way into the default Linux kernel yet but a netfilter utility called "patch-o-matic" can be used to add them to your own kernel and this will be discussed at the end of the article.
1. Specifying Multiple Ports with multiport
The multiport
module allows one to specify a number of
different ports in one rule. This allows for fewer rules and easier
maintenance of iptables configuration files. For example, if we
wanted to allow global access to the SMTP, HTTP, HTTPS and SSH
ports on our server we would normally use something like the
following:
-A INPUT -i eth0 -p tcp -m state --state NEW --dport ssh -j ACCEPT -A INPUT -i eth0 -p tcp -m state --state NEW --dport smtp -j ACCEPT -A INPUT -i eth0 -p tcp -m state --state NEW --dport http -j ACCEPT -A INPUT -i eth0 -p tcp -m state --state NEW --dport https -j ACCEPTUsing the
multiport
matching module, we can now write:
-A INPUT -i eth0 -p tcp -m state --state NEW -m multiport \ --dports ssh,smtp,http,https -j ACCEPTIt must be used in conjunction with either
-p tcp
or
-p udp
and only up to 15 ports may be specified. The
supported options are:
--sports port[,port,port...]
- matches source port(s)
--dports port[,port,port...]
- matches destination port(s)
--ports port[,port,port...]
- matches both source and destination port(s)
mport
* is another similar extension that
also allows you to specify port ranges, e.g. --dport
22,80,6000:6100
.
2. Load Balancing with random
* or
nth
*
Both the random
and nth
extensions can be
used for load balancing. If, for example, you wished to balance
incoming web traffic between four mirrored web servers then you
could add either of the following rule sets to your
nat
table:
-A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m nth \ --counter 0 --every 4 --packet 0 -j DNAT --to-destination 192.168.0.5:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m nth \ --counter 0 --every 4 --packet 1 -j DNAT --to-destination 192.168.0.6:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m nth \ --counter 0 --every 4 --packet 2 -j DNAT --to-destination 192.168.0.7:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m nth \ --counter 0 --every 4 --packet 3 -j DNAT --to-destination 192.168.0.8:80or:
-A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m random \ --average 25 -j DNAT --to-destination 192.168.0.5:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m random \ --average 33 -j DNAT --to-destination 192.168.0.6:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m random \ --average 50 -j DNAT --to-destination 192.168.0.7:80 -A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW \ -j DNAT --to-destination 192.168.0.8:80The
nth
matching extension allows you to match the nth
packet received by the rule. There are up to 16 (0...15) counters
for matching the nth packets. The above four (nth
)
rules use counter 0 to count every 4th packet. Once the 4th packet
is received, the counter is reset to zero. The first rule matches
the 1st packet (--packet 0
) of every four counted, the
second rule matches the 2nd packet (--packet 0
), and
so on.
The random
matching extension allows you to match
packets based on a given probability. The first rule from the set
of random
rules above matches 25% (--average
25
) of the TCP connections to port 80 and redirects these to
the first mirrored web server. Of the 75% of connections not
matching on the first rule, 25% will match the second and a further
25% will match the third. The remaining 25% will be caught by the
fourth rule.
Another use of the random
extension would be to
simulate a faulty network connection to evaluate the performance of
networking hardware/software, etc.
3. Restricting the Number of Connections with
limit
and iplimit
*
The limit
matching extension can be used to limit the
number of times a rule matches in a given time period while the
iplimit
extension can restrict the number of parallel
TCP connections from a particular host or network. These extensions
can be used for a variety of purposes:
- to protect against DOS (denial of service) attacks such as preventing a flood of HTTP requests to your web server while ensuring all your customers have unlimited access;
- to prevent a brute-force attack to guess passwords;
- to limit Internet usage by staff during working hours;
- and many many more.
-A FORWARD -m state --state NEW -p tcp -m multiport --dport http,https \ -o eth0 -i eth1 -m limit --limit 50/hour --limit-burst 5 -j ACCEPTThis rule assumes that we are acting as a proxy server where the external connection is via
eth0
and eth1
connects to our office network. The rule limits all of our internal
computers to only 50 new HTTP or HTTPS connections per hour and the
use of --limit-burst
prevents any one employee from
using up all 50 in one go. Packets can be matched
/day
, /hour
, /minute
or
/sec
.
The --limit-burst
parameter can be quite confusing
at first. In the above example, it will ensure that if all
employees are trying to access the Internet throughout the hour
then only 5 connections are made every 5 minutes. If 30 minutes
pass with no connections and then there is a sudden rush for the
remaining 30 minutes, only 5 connections will be permitted every
2.5 minutes. I once heard it explained as follows:
For every
limit
rule, there's a "bucket" containing "tokens". Whenever the rule matches, a token is removed and when the token count reaches zero, the rule doesn't match anymore.
--limit
is the bucket refill rate.
--limit-burst
is the bucket size (number of tokens that it can hold).
The iplimit
extension allows us to restrict the
number of parallel TCP connections from a particular host or
network. If, for example, we wanted to limit the number of HTTP
connections made by any single IP address to 5 we could use:
-A INPUT -p tcp -m state --state NEW --dport http -m iplimit \ --iplimit-above 5 -j DROP
4. Maintaining a List of recent
Connections to
Match Against
By using the recent
extension one can dynamically
create a list of IP addresses that match a rule and then match
against these IPs in different ways later. One possible use would
be to create a "temporary" bad-guy list by detecting possible port
scans and to then DROP
all other connections from the
same source for a given period of time
Port 139 is one of the most dangerous ports for Microsoft
Windows® users as it is through this port that the Windows file
and print sharing service runs. This also makes this port one of
the first scanned by many port scanners or potential hackers and a
target for many of the worms around today. We can use the
recent
matching extension to temporarily block any IP
from connecting with our machine that scans this port as
follows:
-A FORWARD -m recent --name portscan --rcheck --seconds 300 -j DROP -A FORWARD -p tcp -i eth0 --dport 139 -m recent --name portscan \ --set -j DROPNow anyone trying to connect to port 139 on our firewall will have all of their packets dropped until 300 seconds has passed. The supported options include:
--name name
- The name of the list to store the IP in or check it against. If no name is given then
DEFAULT
will be used--set
- This will add the source address of the packet to the list. If the source address is already in the list, this will update the existing entry.
--rcheck
- This will check if the source address of the packet is currently in the list.
--update
- This will check if the source address of the packet is currently in the list. If it is then that entry will be updated and the rule will return true.
--remove
- This will check if the source address of the packet is currently in the list and if so that address will be removed from the list and the rule will return true.
--seconds seconds
- This option must be used in conjunction with one of
--rcheck
or--update
. When used, this will narrow the match to only happen when the address is in the list and was seen within the last given number of seconds.--hitcount hits
- This option must be used in conjunction with one of
--rcheck
or--update
. When used, this will narrow the match to only happen when the address is in the list and packets had been received greater than or equal to the given value. This option may be used along with `seconds' to create an even narrower match requiring a certain number of hits within a specific time frame.
5. Matching Against a string
* in a
Packet's Data Payload
The string
extension allows one to match a string
anywhere in a packet's data payload. Although this extension does
have many valid uses, I would strongly advise caution. Let's say,
for example, that our Linux firewall is protecting an internal
network with some computers running Microsoft Windows® and we
would like to block all executable files. We might try something
like:
-A FORWARD -m string --string '.com' -j DROP -A FORWARD -m string --string '.exe' -j DROPThis has a number of problems:
- if the '
.com
' or '.exe
' is split across two packets it will not be matched - if any packet being transmitted contains either of the stings it will be dropped; this includes any packets from a web page containing those strings, from an e-mail transmission, etc
6. Time-based Rules with time
*
We can match rules based on the time of day and the day of the week
using the time
module. This could be used to limit
staff web usage to lunch-times, to take each of a set of mirrored
web servers out of action for automated backups or system
maintenance, etc. The following example allows web access during
lunch hour:
-A FORWARD -p tcp -m multiport --dport http,https -o eth0 -i eth1 -m time \ --timestart 12:30 --timestop 13:30 --days Mon,Tue,Wed,Thu,Fri -j ACCEPTClearly the start and stop times are 24-hour with the format
HH:MM
. The day is a comma-separated list that is case
sensitive and made up of Mon
, Tue
,
Wed
, Thu
, Fri
,
Sat
and/or Sun
.
7. Setting transfer quotas with
quota
*
Setting transfer quotas can be very useful in many situations. As
an example, a lot of broadband users will have download quotas set
for them by their ISP and many may charge extra for every megabyte
transferred in excess of this quota. You can use iptables to
monitor your usage and cut you off when you reach your quota (say
2GB) with a rule similar to the following:
-A INPUT -p tcp -m quota --quota 2147483648 -j ACCEPT -A INPUT -j DROPYou can then view your usage with the following command:
$ iptables -v -L
You would also need to reset the quota every month manually (by
restarting iptables) or with a cron job. Clearly your computer
would need to be 'always-on' for this example to be of any use, but
there are also any other situations where the quota
extension would be useful.
8. Packet Matching Based on TTL Values
The TTL (Time-To-Live) value of a packet is an 8-bit number that is decremented by one each time the packet is processed by an intermediate host between its source and destination. The default value is operating system dependant and usually ranges from 32 to 128. Its purpose includes ensuring that no packet stays in the network for an unreasonable length of time, gets stuck in an endless loop because of bad routing tables, etc. Once the TTL value of a packet reaches 0 it is discarded and a message is sent to its source which can decide whether or not to resend it.As an interesting aside: this is actually how the
traceroute
command works. It sends a packet to the
destination with a TTL of 1 first and gets a reply from the first
intermediate host. It then sends a packet with a TTL of 2 and
receives a reply from the second intermediate host and so on until
it reaches its destination.
The usefulness of packet matching based on TTL value depends on your imagination. One possible use is to identify "man-in-the-middle" attacks. If you regularly connect from home to work you could monitor your TTL values and establish a reasonable maximum value at the receiving end. You can the use this to deny any packets that arrive with a higher TTL value as it may indicate a possible "man-in-the-middle" attack; someone intercepting your packets, reading/storing them and resending them onto the destination. There are of course "man-in-the-middle" methods that wouldn't alter the TTL value but, as always, security is never absolute, only incremental. TTL matching could also be used for network debugging or to find hosts with bad default TTL values.
As a simple example, let's reject all packets from a specific IP with a TTL of less than 40:
-A INPUT -s 1.2.3.4 -m ttl --ttl-lt 40 -j REJECTYou can also check for TTL values that are less than (
--ttl-gt
) or equal to (--ttl-eq
) a
particular value.
9. Patching Your Kernel with Patch-O-Matic (POM)
Some of the newer features introduced in this article are not considered stable enough by the netfilter development team for inclusion in the current Linux kernel. To use these you will need to patch your kernel using a utility called patch-o-matic. This is not for the faint of heart and I am not going to provide step-by-step instructions here. I will simply cover patch-o-matic and provide references to more information.Patch-o-matic can be downloaded from the netfilter homepage,
http://www.netfilter.org/.
You will also need the source code for your kernel (if you are
using a kernel supplied with your distribution, install the
kernel-source
package or install a new kernel by
downloading the latest kernel source code from http://www.kernel.org/) and the source
code for iptables which you can also download from the netfilter
homepage. Once you have these, unpack them and execute the
runme
script from patch-o-matic as follows:
$ KERNEL_DIR=<path to the kernel source code>
IPTABLES_DIR=<path to the iptables source code> ./runme
extra
The script describes each new extension and asks whether or not to patch the kernel for it. Once that is finished you will need to recompile the kernel, the netfilter kernel modules and the iptables binaries. This is outside the scope of this article but you will find useful information on the following sites:
- Netfilter Extensions HOWTO
- Kernel
Rebuild Guide
(this was part of the The Linux Documentation Project but was removed for review - this is the latest version from that review process but it is still in development)
10. Change Log
- April 24, 2005
- Fixed math error in section 2 using the random module. Thanks to Rich Price, John mcDonald and Emmanuel Araman for pointing this out.
Copyright © 2004, Barry O'Donovan. Released under the Open Publication license.