Updated : Sep 26, 2016 in Linux

Blocking all traffic from individual countries using IPSet and IPTables

Introduction:
As I looked at the syslog I found too often that SSH login attacks are coming either from China or from Russia. Having enough of that, and anyway not expecting any traffic from any of these countries, although I use fail2ban still I decided to block any traffic coming from these 2 countries. Fail2ban is a great tool for blocking unsuccessful attempts to login. BUT it seems that many attemps(thousands) per day are still going through. I can only assume that the attacker is using parallel sending of attempts and till Fail2ban(based on auth logs) reacts many have gone through.
To do a full blocking of IPs with preloaded lists of IP ranges, I started by using iptables with one rule per IP range, BUT the loading of the rules was taking too long and more importantly, loading all IP ranges, as pure iptables rules, made my server unstable, meaning … it crashed!!! It is said that more than max. 25,000 rules in the iptables, especially larger than 27,000, rules may put the kernel in an unstable state.
To remedy to that I use ipset with iptables. ipset is especially conceived to deal with large IP Range lists(fast access hash tables) which can contain up to 65536 entries.

NOTE: The script below is only valid for Debian based Linux distributions. For other distributions you will need to adapt the script accordingly.

Principle of this Countries Blocking protection:
The IP ranges in CIDR format are picked up from the web site http://www.ipdeny.com/ipblocks/data/countries/ and entered in the IPSet’s individual lists which are named by country codes and then referenced by iptables to define the TARGET (what to do with the IPs that match:DROP)
NOTE: In this example the script sets iptables rules target to DROP instead of REJECT to avoid high traffic of TCP/IP stack rejection responses. DROP doesn’t respond anything.

IMPORTANT NOTE: This script below should be run AFTER you loaded your regular firewall rules. It INSERTS the new iptables rules in such a way that any incoming packets from those defined countries will be blocked BEFORE any further processing in your firewall.

Steps:
#!/bin/bash
# Description: Uses IPSET and IPTABLES to block full countries from accessing the server for all ports and protocols
# Syntax: countries_firewall.sh countrycode [countrycode] ......
# Use the standard locale country codes to get the proper IP list. eg.
# countries_firewall.sh cn ru ro
# Will create tables that block all requests from China, Russia and Romania
# Changes: 13.11.2016 Initial creation of script
# Note: To get a sorted list of the inserted IPSet IPs for example China list(cn) run the command:
# ipset list cn | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4
# #############################################################################
# Defining some defaults
iptables="/sbin/iptables"
tempdir="/tmp"
sourceURL="http://www.ipdeny.com/ipblocks/data/countries/"
#
# Verifying that the program 'ipset' is installed
if ! (dpkg -l | grep '^ii ipset' &>/dev/null) ; then
echo "ERROR: 'ipset' package is not installed and required."
echo "Please install it with the command 'apt-get install ipset' and start this script again"
exit 1
fi
[ -e /sbin/ipset ] && ipset="/sbin/ipset" || ipset="/usr/sbin/ipset"
#
# Verifying the number of arguments
if [ $# -lt 1 ]; then
echo "ERROR: wrong number of arguments. Must be at least one."
echo "countries_firewall.sh countrycode [countrycode] ......"
echo "Use the standard locale country codes to get the proper IP list. eg."
echo "countries_firewall.sh cn ru ro"
exit 2
fi
#
# Now load the rules for blocking each given countries and insert them into IPSet tables
for country ; do
# Read each line of the list and create the IPSet rules
# Making sure only the valid country codes and lists are loaded
if wget -q -P $tempdir ${sourceURL}${country}.zone ; then
# Destroy the IPSet list if it exists
$ipset flush $country &>/dev/null
# Create the IPSet list name
echo "Creating and filling the IPSet country list: $country"
$ipset create $country hash:net &>/dev/null
(for IP in $(cat $tempdir/${country}.zone) ; do
# Create the IPSet rule from each IP in the list
echo -n "$ipset add $country $IP --exist - "
$ipset add $country $IP -exist && echo "OK" || echo "FAILED"
done) > $tempdir/IPSet-rules.${country}.txt
# Destroy the already existing rule if it exists and insert the new one
$iptables -D INPUT -p tcp -m set --match-set $country src -j DROP &>/dev/null
$iptables -I INPUT -p tcp -m set --match-set $country src -j DROP
# Delete the temporary downloaded counties IP lists
rm $tempdir/${country}.zone
else
echo "Argument $country is invalid or not available as country IP list. Skipping"
fi
done
# Display the result of the iptables rules in INPUT chain
echo "======================================"
echo "IPSet lists registered in iptables:"
$iptables -L INPUT -n -v | grep 'match-set'
# Dispaly the number of IP ranges entered in the IPset lists
echo "--------------------------------------"
for country ; do
echo "Number of ip ranges entered in IPset list '$country' : $($ipset list $country | wc -l)"
done
echo "======================================"
#
#eof

Loggin of the script operation:

As you can see in the script, the adding of IP ranges in the IPSet tables is logged in: $tempdir/IPSet-rules.${country}.txt which is overwritten every time the script is run for the same country.

Starting the script

In order to make sure this firewall add-on starts properly on each boot, but after the user defined firewall, I start it via cron @reboot job after a delay of about 40 sec.(which can be adjusted to you needs) to let other services start, including the firewall service. I’m aware that this method of starting the script is not very elegant but it fits pretty much all variations of Linux distributions whether Sysinit-V or Systemd based. Example of root cron job:
@reboot /bin/sleep 40 ; /bin/bash -c ". /root/.bashrc ; /root/bin/countries_firewall.sh cn ru"

Sending traffic reports per email

In order to send traffic reports per email every day I collect every day the traffic data from iptables, format it and send it by email using the following bash script. This script will then be run every day by cron (put in /etc/cron.daily/) and the traffic counter will be reset.
#!/bin/bash
# Purpose: Sends the blocked traffic report per email and resets the counter
# Syntax: iptables_report
# Dependencies: Systems tools: iptables, awk, column, whois, sendmail
# Changes: 13.11.2016 First implementation of script
#----------------------------------------------------------
HOST=$(cat /etc/hostname | tr 'a-z' 'A-Z')
email="me@myserver.com"
reportsender="cron@$HOST"
subject="BLOCKED Packets report on $(hostname | tr 'a-z' 'A-Z')"
tempdir="/tmp"
file1="iptables_report1.txt"
file2="iptables_report2.txt"
#
#------------ Build the header of the mail to send ------------
echo "From: $reportsender" > $tempdir/$file1
echo "To: $email" >> $tempdir/$file1
echo "Subject: $subject" >> $tempdir/$file1
echo "MIME-Version: 1.0" >> $tempdir/$file1
echo 'Content-Type: text/html; charset="ISO-8859-15"' >> $tempdir/$file1
echo "" >> $tempdir/$file1
echo "<br />" >> $tempdir/$file1
echo -e "<font size=3 FACE='Courier'><pre>" >> $tempdir/$file1
# Formatted message starts here
# Add the country at the end of each line
# Load the header and data to the temporary file 2
echo -e "Packets Bytes Source \n======= ========= ======" >$tempdir/$file2
/sbin/iptables -L -n -v | /bin/grep -v '^ 0' | /bin/grep 'match-set' | /usr/bin/awk '{print $1" "$2" "$11}' >> $tempdir/$file2
#
# Format temp file2 into temp file1
cat $tempdir/$file2 | column -t >> $tempdir/$file1
#
#
# Add the last HTML preformatting End
echo -e "</pre>" >> $tempdir/$file1
echo "" >> $tempdir/$file1
#
#----------------- Send the prepared email ---------------------------
# now format the report and send it by email
cat $tempdir/$file1 | /usr/sbin/sendmail -t
rm $tempdir/$file1 $tempdir/$file2
#
# Reset the iptables counters
/sbin/iptables -Z
#
# eof

Creating the cron job for regular daily reports
Save that file in eg. /etc/cron.daily/iptables_report
Make it runable:
chmod 755 /etc/cron.daily/iptables_report
Example of report email:
Packets Bytes Source
======= ========= ======
188 7852 ru
19295 1150K cn

Manual status:
To get manual status of the iptables rules for the INPUT chain run the command:
iptables -L INPUT -v -n
To get a sorted listing of an IPSet IP list run the command(eg. for Russia):
ipset list ru | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4

3 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: