A lot of people have been asking about how to protect a Linux server against denial of service (DoS) attacks lately. The vast majority of these attacks involve one individual using a scripted program to execute an attack on a single server target. The goal of using iptables here is to handle networking traffic before it reaches srcds where it could cause undesired latency for players. Also, keep in mind that these iptables rules will do nothing in the face of a large-scale sustained DDoS attacks. With that in mind, effectively iptables rules will mitigate script kiddies' DoS, small-scale DDoS, and even larger pulsed DDoS attacks.
Before you begin changing the iptables rules, unless you have physical access to the server you should consider scheduling a periodic iptables rules flush in case a change causes the system to lock everyone out. To do this, make a shell script with the proper permissions and put the following inside it:
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
Then configure it to run at a scheduled time using crontab or similar program. Remember to unschedule this once you've finished setting up the firewall.
The first measure of protection is to develop a white-list of IP addresses that have your permission to access rcon; otherwise, it's best to completely hide rcon from the outside world.
iptables -A INPUT -p tcp --destination-port 27015 -j LOG --log-prefix "SRCDS-RCON " -m limit --limit 1/m --limit-burst 1
iptables -A INPUT -p tcp --destination-port 27015 -j DROP
Many of the programs available in the nether-regions of the Internet spam queries to the server. These programs have a few commonalities such as the length of their packets. One popular iptables rule blocks anything with length 28. To suppress these attacks, we'll block any packets with a length between 0 and 32. You won't see any valid game packets below 32 bytes.
iptables -A INPUT -p udp --destination-port 27015 -m length --length 0:32 -j LOG --log-prefix "SRCDS-XSQUERY " --log-ip-options -m limit --limit 1/m --limit-burst 1
iptables -A INPUT -p udp --destination-port 27015 -m length --length 0:32 -j DROP
Similarly, how the game responds to fragmented packets is defined by a few net_ cvars. Check the values of your cvars and configure your firewall rules accordingly. This is the calculation I used to determine the maximum acceptable packet size:
Maximum Size = (`net_maxroutable`) + (`net_splitrate`) * (`net_maxfragments`)
which gives 2520 bytes under the default configuration.
iptables -A INPUT -p udp --destination-port 27015 -m length --length 2521:65535 -j LOG --log-prefix "SRCDS-XLFRAG " --log-ip-options -m limit --limit 1/m --limit-burst 1
iptables -A INPUT -p udp --destination-port 27015 -m length --length 2521:65535 -j DROP
Another rule to consider is a limit on how many UDP IP packets we receive over a set period of time. This is difficult to implement but the general idea is to add all of the cmdrate values for all connected clients and specify a burst that has some leeway, say 120%. For example, let's say there are currently 10 connected clients each with a cmdrate of 33 packets per second, so 10*33 = 330 per second with 330*0.2 = 66 burst. So your iptables rule would include something like,
-m hashlimit --hashlimit-mode dstport,dstip --hashlimit-name StopFlood --hashlimit 330/s --hashlimit-burst 66
Implementing this kind of dynamic firewall rule is left as an exercise for the reader.
After this we'll accept all 'established' UDP connections. Since UDP is a stateless protocol, 'established' is a bit of a misnomer. In this sense, established means that there has been communication in both directions from the same source port to the same destination port and a timeout value has not been reached.
iptables -A INPUT -p udp -m state --state ESTABLISH -j ACCEPT
Handling 'new' or unsolicited UDP connections such as requests to join the game server or miscellaneous queries will be rate-limitted. A hash-limit is used to throttle connection attempts that become excessive. This is so sensitive that hitting 'Refresh' in the server browser window too often will trigger these rules. There are several different options for how to configure the hash-limits so I'll briefly discuss two different scenarios.
1) You run multiple game servers on different ports but same IP
For this you'd want to make the hash-limit come from the source IP and go to the destination port (srcip,dstport).
iptables -A INPUT -p udp -m state --state NEW -m hashlimit --hashlimit-mode srcip,dstport --hashlimit-name StopDoS --hashlimit 1/s --hashlimit-burst 3 -j ACCEPT
2) You run a single game server on a single IP
For this it's easier just to specify the source IP for the hash (srcip).
iptables -A INPUT -p udp -m state --state NEW -m hashlimit --hashlimit-mode srcip --hashlimit-name StopDoS --hashlimit 1/s --hashlimit-burst 3 -j ACCEPT
Finally, for all packets that weren't matched to an acceptance rule above, we'll drop them here.
iptables -A INPUT -p udp -j LOG --log-prefix "UDP-SPAM " --log-ip-options -m limit --limit 1/m --limit-burst 1
iptables -A INPUT -p udp -j DROP
Some iptables rule sets include exemptions for Valve's master servers. Unfortunately, due to the nature of UDP, there is little stopping an attacker from spoofing these master server addresses. It's better to allow the current clients a playable experience than worrying about appearing unavailable for some brief period of time.
There are other iptables firewalls posted that use packet inspection to deny a few of these scripted DoS attacks. Generally, if it's easily detected by a simpler iptables rule, then use that instead. Also, there are quite a few SourceMM or SM extensions that attempt to deal with this issue as well: DAF, Query Cache, et. al. It's best to avoid letting srcds or any related plugins or extensions deal with these problems. But, other than resource consumption, there's no harm in using both approaches.
If you're default policy on the INPUT chain is to accept traffic, consider changing it to drop. If you do so, remember to make allowances for already established tcp connections.
If your net_splitrate is 0 and you won't fragment packets, consider disabling fragmentation completely for srcds traffic.
If you receive attacks from shady foreign countries such as Burma or North Korea, consider black-listing these entire countries.