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.
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 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:
- 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.
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).
--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:
- 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 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
$ 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, 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= IPTABLES_DIR= ./runme extra
$ KERNEL_DIR=
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:
Iptables Restricting Access By Time Of The Day
Recently I was asked to control access to couple of services based upon day and time. For example ftp server should be only available from Monday to Friday between 9 AM to 6 PM only. It is true that many services and daemons have in built facility for day and time based access control. For example xinetd offers data and time based access control. Iptables also allows such control via time patch/module. It matches if the packet arrival time/date is within a given range. This is very handy when you want a service to be available only at certain times of day or even certain days.
General syntax:
iptables RULE -m time --timestart TIME --timestop TIME --days DAYS -j ACTIONWhere,
- --timestart TIME : Time start value . Format is 00:00-23:59 (24 hours format)
- --timestop TIME : Time stop value.
- --days DAYS : Match only if today is one of the given days. (format: Mon,Tue,Wed,Thu,Fri,Sat,Sun ; default everyday)
Suppose you would like to allow incoming ssh access only available from Monday to Friday between 9 AM to 6. Then you need to use iptables as follows:
Input rule:
iptables -A INPUT -p tcp -s 0/0 --sport 513:65535 -d 202.54.1.20 --dport 22 -m state --state NEW,ESTABLISHED -m time --timestart 09:00 --timestop 18:00 --days Mon,Tue,Wed,Thu,Fri -j ACCEPTOutput rule:
iptables -A OUTPUT -p tcp -s 202.54.1.20 --sport 22 -d 0/0 --dport 513:65535 -m state --state ESTABLISHED -m time --timestart 09:00 --timestop 18:00 --days Mon,Tue,Wed,Thu,Fri -j ACCEPTReferences:
- Please note time module is not part of standard kernel, you need to download and apply patch from Patch-O-Matic
- Read iptables man page for more information.