Issue
I found an interesting concept which is to block a user¹ when they attempt to open too many TCP connections within one minute.
This comes down to a couple of rules:
iptables -A INPUT -p tcp \
-m recent --update --name synflood --seconds 60 --hitcount 100 \
-m tcp --syn -j add_to_denylist
iptables -A INPUT -p tcp \
-m recent --set --name synflood -m tcp --syn
The second rule says to add an entry for the source IP in a table named synflood if the user is attempting to connect using the TCP protocol.
The first rule checks whether the same IP address is again attempting a connection. If so, it registers the new attempt. If 100 attempts happen within one minute, then the rule becomes TRUE and the action is taken. My action is to add the user's IP to my deny list. This means they are completely blocked, whatever they try to do, and that for a rather long time.
Now, I run several websites on my server and what I was wondering is: how can I make sure that I don't block legitimate users? Is 100 connections within 1 minute possible over HTTP[S] by legitimate users?
I know that browsers are expected to be limited to 2 connections (HTTP/1.1) although most browsers allow up to 6 connections. So I would think that 100/min. is a more than realistic limit. I am also wondering about things such as spiders (GoogleBot in particular). Would such do that many new connection attempts per minute? I only host 19 websites.
¹ user -- most certainly a robot controlled by a hacker.
Research
After many tests and discovering that iptables extensions can be repeated and the order in which they appear is very important, I found out how to enter those two rules properly. We still need two rules, but quite a bit different.
Solution
Clearing Old Entries
To properly clear old entries, we need to avoid as many checks as possible. Further, the --rcheck may return true or false. This cannot be included in the same rule as the other checks. It should appear just before the other rule to make sure that the number of entries decreases.
Note: the clear is done using the --reap option.
Especially, you cannot test the --hitcount or the clear would not happen since we especially want to clear when the count is under 100 so as not to reach that limit.
New TCP Packets
The SYN flood rule has to match only if the TCP SYN flag is set. That has to be tested first.
Increase Hit Counter
We cannot use --update because that command does not create the synflood list entry. So instead I use --set.
The good thing is that the --set also counts as one additional hit if the list already exists. It does not reset the list. In other words, the --set nearly works like the --update except that it has the ability to create the list.
The fact is that, in my rules in the question above, I assumed that I wanted to increment the counter only if necessary. i.e. only if the 60 seconds hadn't past. This is done with the Check Limit below.
Check Limit
Now the --set command always returns true. It also does not allow for testing a TTL or a hit cont, so we need another -m recent rule.
This uses an --rcheck command. We do not want to use the --update since we already got the --set incrementing the hit count and it just happened so really there is no such need at all in this case.
The Final Rules
Here are the resulting rules:
iptables -A INPUT \
-m recent --rcheck --name synflood --seconds 60 --reap
iptables -A INPUT -p tcp \ # chain / protocol
-m tcp --syn \ # user is trying to connect with TCP?
-m recent --set --name synflood \ # create list or +1 hit
-m recent --rcheck --seconds 60 --hitcount 100 \
-j add_to_denylist # action if --rcheck true
As mentioned above, the order in which all the rules and the -m ... extensions appear is very important. The order in which the rules are checked is top to bottom (left to right if you write your rules on one long line).
So if we read this list we get:
-
Rule 1
-
Is this an INPUT packet?
-
Are there entries older than 60s in synflood? If so delete them.
-
Rule 2
-
Is this an INPUT packet?
-
Is this a TCP packet?
-
Is this a new TCP packet?
-
If there is no recent list named synflood, create it
-
Add this packet to the synflood list
-
Check whether there was 100 or more in the last 60 seconds?
-
If the --rcheck is true, call add_to_denylist.
The --set always returns true (or false if you use the ! operator). All the other lines in Rule 2 break the process if false is returned.