Limit the rate of inbound TCP connections using iptables
Tested on |
Debian (Lenny, Squeeze) |
Ubuntu (Lucid, Precise, Trusty) |
Objective
To limit the rate of inbound TCP connections from any given IP address using iptables
Background
Some types of network attack involve making connection attempts a far higher rate than would be likely to occur in normal use. Reasons for an attacker to do this include:
- performing a denial of service attack,
- scanning for open network ports, or
- performing a brute force or (more likely) dictionary-based search for valid usernames and passwords.
By imposing a limit on the rate of new connections it may be possible to greatly reduce the effectiveness of such attacks without adversely affecting legitimate users.
Limitations of this technique are that it will not protect against a large-scale distributed attack, and it will not limit the number of separate requests that might be channelled through a given TCP connection if the overlying protocol allows this (as, for example, HTTP does). It is not readily applicable to UDP or ICMP due to the way in which the connection tracking system handles these protocols.
Scenario
Suppose that you are running a server which needs to accept SSH connections from the public Internet using password authentication. From its logs you can see that the server receives many connection attempts from third parties attempting to search for a valid username and password. You have already made sure that all of the accounts on the server have strong passwords, but in order to further reduce the risk of successful attack you wish to limit the rate at which inbound SSH connections can be established from any given IP address.
You do not expect any legitimate use to need to make more than 5 connections in any 60 second period.
Method
Rate limiting can be implemented using a netfilter extension module called recent
, which allows a list of recently-seen IP addresses to be constructed and used for matching. The requirement described above could be met by inserting the following pair of rules into the INPUT
chain:
iptables -I INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --name ssh \ --update --seconds 60 --hitcount 6 -j REJECT iptables -I INPUT 2 -p tcp --dport 22 -m state --state NEW -m recent --name ssh --set
The -p tcp
and --dport 22
options restrict the operation of these rules to TCP port 22, which is the port normally used for SSH connections. They could be omitted, in which case rate limiting would apply to all connections; however it would probably not be a good idea to limit protocols such as HTTP using the same parameters as SSH because the normal connection rate is likely to be very different.
The -m state
and --state NEW
options restrict the operation of these rules to new connections only. Without this every packet would count towards the rate limit, making it difficult or impossible to distinguish between legitimate and illegitimate use.
The -m recent
option specifies that the options following it refer to the recent
module. The --name
option gives a name to the list of IP addresses. This is optional, but allows for the possibility of there being more than one list.
The --set
option causes the recent
module to note the source address of the current packet, updating the list as appropriate. The --update
option causes the source address of the current packet to be checked against the list. The addtion of --seconds 60
and --hitcount 6
causes this rule to match only if there have been four or more matching packets within the last 60 seconds. If there have then -j REJECT
causes the current packet to be rejected.
REJECT
has been used in this example in preference to DROP
because it is less likely to cause confusion if the rule is triggered inadvertently. However, if the intent is to protect against denial of service attacks then DROP
is probably a more appropriate course of action.
The rules were placed at the start of the chain in the example above to ensure that the limiting occurs regardless of what other rules might be present, however this probably not a good choice in practice. The following is a more realistic example of how rate limiting might be integrated into a firewall ruleset:
iptables -P INPUT DROP iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -s 127.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name ssh \ --update --seconds 60 --hitcount 6 -j REJECT iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --name ssh --set iptables -A INPUT -p tcp --dport 22 -j ACCEPT iptables -A INPUT -p tcp --dport 80 -j ACCEPT
If you place limits on several different types of traffic then it is generally best to maintain a separate list for each. The reason for this is that a legitimate user is more likely to generate multiple types of traffic than an attacker. If the figures were aggregated it would then be necessary to use a higher threshold to protect against false positives.
Troubleshooting
Address lists created by the recent
module can be inspected by viewing the correspondingly-named files in the directory /proc/net/xt_recent
. Here is an example of one of these files:
src=192.168.0.1 ttl: 64 last_seen: 4336435992 oldest_pkt: 6 4333277121, 4333277811, 4334553350, 4336024328, 4336317192, 4336435992 src=192.168.0.2 ttl: 64 last_seen: 4333275775 oldest_pkt: 4 4332549872, 4333275266, 4333275539, 4333275775
Each line lists the information recorded about one IP address. This includes the timestamp when the address was most recently observed, followed by a list of recorded timestamps.
Interpreting these timestamps is non-trivial. They are measured in jiffies, but of the type used internally by the kernel as opposed to those normally exposed to userspace. The current time in kernel jiffies can be obtained from /proc/timer_list
:
grep ^jiffies /proc/timer_list
and the number of jiffies per second can be found by measuring how quickly this value changes:
grep ^jiffies /proc/timer_list ; sleep 1 ; grep ^jiffies /proc/timer_list
For older versions of the kernel (prior to 2.6.28) replace /proc/net/xt_recent
with /proc/net/ipt_recent
. Note that the presence of a packet in the list does not necessarily mean that it is recent enough to count towards the hitcount.
See also
Further reading
- IPTables/Netfilter Recent Module (official website)