...making Linux just a little more fun!

<-- prev | next -->

Advanced Features of netfilter/iptables

By Barry O'Donovan

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:

  1. Specifying multiple ports in one rule
  2. Load balancing
  3. Restricting the number of connections
  4. Maintaining a list of recent connections to match against
  5. Matching against a string in a packet's data payload
  6. Time-based rules
  7. Setting transfer quotas
  8. 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 ACCEPT
Using 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 ACCEPT
It 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:80
or:
-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 25 \
    -j DNAT --to-destination 192.168.0.6:80
-A PREROUTING -i eth0 -p tcp --dport 80 -m state --state NEW -m random --average 25 \
    -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:80
The 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: Let's take the case where we want to limit the Internet usage of our employees during working hours. We could use a rule like:
-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 ACCEPT
This 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 DROP
Now 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 DROP
This has a number of problems:

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 ACCEPT
Clearly 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 DROP
You 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 REJECT
You can also check for TTL values that are less than (--ttl-gt) or equal to (--ttl-eq) a particular value.

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, https://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 https://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:

 


[BIO] Barry O'Donovan graduated from the National University of Ireland, Galway with a B.Sc. (Hons) in computer science and mathematics. He is currently completing a Ph.D. in computer science with the Information Hiding Laboratory, University College Dublin, Ireland in the area of audio watermarking.

Barry has been using Linux since 1997 and his current flavor of choice is Fedora Core. He is a member of the Irish Linux Users Group. Whenever he's not doing his Ph.D. he can usually be found supporting his finances by doing some work for Open Hosting, in the pub with friends or running in the local park.

Copyright © 2004, Barry O'Donovan. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 108 of Linux Gazette, November 2004

<-- prev | next -->
Tux