Unexpected DDOS: Blocking China with ipset and iptables
Set up iptables
If you already have iptables set up and in use, skip this section and go straight to the ipset section.
Create a file where we can declare some rules to use:
sudo nano /etc/iptables.firewall.rules
Inside there you'll want to paste the following:
*filter
# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 -j REJECT
# Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow all outbound traffic - you can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT
# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
# Allow SSH connections
#
# The -dport number should be the same port number you set in sshd_config
#
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT
# Allow ping
-A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
# Drop all other inbound - default deny unless explicitly allowed policy
-A INPUT -j DROP
-A FORWARD -j DROP
COMMIT
Save that. Next, we need to apply those rules – this is just a text file, and we need to instruct iptables to actually use it.
sudo iptables-restore < /etc/iptables.firewall.rules
That should have loaded the rules and applied them; you can check by
iptables -L
The output of that command ought to look like
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
REJECT all -- anywhere 127.0.0.0/8 reject-with icmp-port-unreachable
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:http
ACCEPT tcp -- anywhere anywhere tcp dpt:https
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh
ACCEPT icmp -- anywhere anywhere
LOG all -- anywhere anywhere limit: avg 5/min burst 5 LOG level debug prefix "iptables denied: "
DROP all -- anywhere anywhere
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DROP all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
Great, it's working! But if you reboot the server it won't be. So lets fix that by creating a file which will run at boot.
sudo nano /etc/network/if-pre-up.d/firewall
Inside that file paste:
#!/bin/sh
/sbin/iptables-restore < /etc/iptables.firewall.rules
Save it. Now we must make sure it's allowed to execute:
sudo chmod +x /etc/network/if-pre-up.d/firewall
Done. The firewall is now running with those rules applied and those rules will be re-applied every time the server reboots. But it's not blocking China yet; it's only blocking anything not on port 80 or 443 (http and https).
Using ipset to block China
You can't manually add a few thousand IP addresses to your iptables, and even doing it automatically is a bad idea because it can cause a lot of CPU load (or so I've read). Instead we can use ipset which is designed for this sort of thing. ipset handles big lists of ip addresses; you just create a list and then tell iptables to use that list in a rule.
Note; I assume that the entirety of the following is done as root. Adjust accordingly if your system is based on sudo.
apt-get install ipset
Next, I wrote a small Bash script to do all the work, which you should be able to understand from the comments in it. Create a file:
nano /etc/block-china.sh
Here's what you want to paste into it:
# Create the ipset list
ipset -N china hash:net
# remove any old list that might exist from previous runs of this script
rm cn.zone
# Pull the latest IP set for China
wget -P . http://www.ipdeny.com/ipblocks/data/countries/cn.zone
# Add each IP address from the downloaded list into the ipset 'china'
for i in $(cat /etc/cn.zone ); do ipset -A china $i; done
# Restore iptables
/sbin/iptables-restore < /etc/iptables.firewall.rules
Save the file. Make it executable:
chmod +x /etc/block-china.sh
This hasn't done anything yet, but it will in a minute when we run the script. First, we need to add a rule into iptables that refers to this new ipset list the script above defines:
nano /etc/iptables.firewall.rules
Add the following line:
-A INPUT -p tcp -m set --match-set china src -j DROP
Save the file. To be clear, my full iptables.firewall.rules now looks like this:
*filter
# Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 -j REJECT
# Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Block anything from China
# These rules are pulled from ipset's china list
# The source file is at /etc/cn.zone (which in turn is generated by a shell script at /etc/block-china.sh )
-A INPUT -p tcp -m set --match-set china src -j DROP
# Allow all outbound traffic - you can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT
# Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT
# Allow SSH connections
#
# The -dport number should be the same port number you set in sshd_config
#
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT
# Allow ping
-A INPUT -p icmp -j ACCEPT
# Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
# Drop all other inbound - default deny unless explicitly allowed policy
-A INPUT -j DROP
-A FORWARD -j DROP
COMMIT
Right now, nothing has changed with the server because no new rules have been applied; to do so, run the block-china.sh script:
/etc/block-china.sh
This should show some output as it pulls a fresh list of Chinese based IPs and then, after a few seconds or so, it will complete and drop you back to a command prompt.
To test if it worked, run:
iptables -L
You should now see a new rule blocking China – the output ought to look like this:
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
REJECT all -- anywhere loopback/8 reject-with icmp-port-unreachable
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
DROP tcp -- anywhere anywhere match-set china src
ACCEPT tcp -- anywhere anywhere tcp dpt:http
ACCEPT tcp -- anywhere anywhere tcp dpt:https
ACCEPT tcp -- anywhere anywhere state NEW tcp dpt:ssh
ACCEPT icmp -- anywhere anywhere
LOG all -- anywhere anywhere limit: avg 5/min burst 5 LOG level debug prefix "iptables denied: "
DROP all -- anywhere anywhere
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DROP all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
Almost done! This works, and will continue to work on re-boots. But, IP addresses change and that list will grow stale over time. If you want to pull and apply an updated list of IPs you can just run the block-china.sh script again.
We can also set the machine to do that automatically via a cron job:
crontab -e
Add a line such as this:
* 5 * * * /etc/block-china.sh
This will run /etc/block-china.sh at 5am every day. The user running the script will need to be root or have root privileges
Subscribe to my newsletter
Read articles from Kilo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by