How to block network traffic by country on Linux

As a system admin who maintains production Linux servers, there are circumstances where you need to selectively block or allow network traffic based on geographic locations. For example, you are experiencing denial-of-service attacks mostly originating from IP addresses registered with a particular country. In other cases, you want to block SSH logins from unknown foreign countries for security reasons. Or your company has a distribution right to online videos, which allows it to legally stream to particular countries only. Or you need to prevent any local host from uploading documents to any non-US remote cloud storage due to geo-restriction company policies.

All these scenarios require an ability to set up a firewall which does country-based traffic filtering. There are a couple of ways to do that. For one, you can use TCP wrappers to set up conditional blocking for individual applications (e.g., SSH, NFS, httpd). The downside is that the application you want to protect must be built with TCP wrappers support. Besides, TCP wrappers are not universally available across different platforms (e.g., Arch Linux dropped its support). An alternative approach is to set up ipset with country-based GeoIP information and apply it to iptables rules. The latter approach is more promising as the iptables-based filtering is application-agnostic and easy to set up.

In this tutorial, I am going to present another iptables-based GeoIP filtering which is implemented with xtables-addons. For those unfamiliar with it, xtables-addons is a suite of extensions for netfilter/iptables. Included in xtables-addons is a module called xt_geoip which extends the netfilter/iptables to filter, NAT or mangle packets based on source/destination countries. For you to use xt_geoip, you don't need to recompile the kernel or iptables, but only need to build xtables-addons as modules, using the current kernel build environment (/lib/modules/`uname -r`/build). Reboot is not required either. As soon as you build and install xtables-addons, xt_geoip is immediately usable with iptables.

As for the comparison between xt_geoip and ipset, the official source mentions that xt_geoip is superior to ipset in terms of memory foot print. But in terms of matching speed, hash-based ipset might have an edge.

In the rest of the tutorial, I am going to show how to use iptables/xt_geoip to block network traffic based on its source/destination countries.

Install Xtables-addons on Linux

Here is how you can compile and install xtables-addons on various Linux platforms.

To build xtables-addons, you need to install a couple of dependent packages first.

Install Dependencies on Debian, Ubuntu or Linux Mint

$ sudo apt-get install iptables-dev xtables-addons-common libtext-csv-xs-perl pkg-config

Install Dependencies on CentOS, RHEL or Fedora

CentOS/RHEL 6 requires EPEL repository being set up first (for perl-Text-CSV_XS).

$ sudo yum install gcc-c++ make automake kernel-devel-`uname -r` wget unzip iptables-devel perl-Text-CSV_XS

Compile and Install Xtables-addons

Download the latest xtables-addons source code from the official site, and build/install it as follows.

$ wget http://downloads.sourceforge.net/project/xtables-addons/Xtables-addons/xtables-addons-2.10.tar.xz
$ tar xf xtables-addons-2.10.tar.xz
$ cd xtables-addons-2.10
$ ./configure
$ make
$ sudo make install

Note that for Red Hat based systems (CentOS, RHEL, Fedora) which have SELinux enabled by default, it is necessary to adjust SELinux policy as follows. Otherwise, SELinux will prevent iptables from loading xt_geoip module.

$ sudo chcon -vR --user=system_u /lib/modules/$(uname -r)/extra/*.ko
$ sudo chcon -vR --type=lib_t /lib64/xtables/*.so

Install GeoIP Database for Xtables-addons

The next step is to install GeoIP database which will be used by xt_geoip for IP-to-country mapping. Conveniently, the xtables-addons source package comes with two helper scripts for downloading GeoIP database from MaxMind and converting it into a binary form recognized by xt_geoip. These scripts are found in geoip folder inside the source package. Follow the instructions below to build and install GeoIP database on your system.

$ cd geoip
$ ./xt_geoip_dl
$ ./xt_geoip_build GeoIPCountryWhois.csv
$ sudo mkdir -p /usr/share/xt_geoip
$ sudo cp -r {BE,LE} /usr/share/xt_geoip

According to MaxMind, their GeoIP database is 99.8% accurate on a country-level, and the database is updated every month. To keep the locally installed GeoIP database up-to-date, you want to set up a monthly cron job to refresh the local GeoIP database as often.

Block Network Traffic Originating from or Destined to a Country

Once xt_geoip module and GeoIP database are installed, you can immediately use the geoip match options in iptables command.

$ sudo iptables -m geoip --src-cc country[,country...] --dst-cc country[,country...]

Countries you want to block are specified using two-letter ISO3166 code (e.g., US (United States), CN (China), IN (India), FR (France)).

For example, if you want to block incoming traffic from Yemen (YE) and Zambia (ZM), the following iptables command will do.

$ sudo iptables -I INPUT -m geoip --src-cc YE,ZM -j DROP

If you want to block outgoing traffic destined to China (CN), run the following command.

$ sudo iptables -A OUTPUT -m geoip --dst-cc CN -j DROP

The matching condition can also be "negated" by prepending "!" to "--src-cc" or "--dst-cc". For example:

If you want to block all incoming non-US traffic on your server, run this:

$ sudo iptables -I INPUT -m geoip ! --src-cc US -j DROP

For Firewall-cmd Users

Some distros such as CentOS/RHEL 7 or Fedora have replaced iptables with firewalld as the default firewall service. On such systems, you can use firewall-cmd to block traffic using xt_geoip similarly. The above three examples can be rewritten with firewall-cmd as follows.

$ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m geoip --src-cc YE,ZM -j DROP
$ sudo firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -m geoip --dst-cc CN -j DROP
$ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m geoip ! --src-cc US -j DROP

Conclusion

In this tutorial, I presented iptables/xt_geoip which is an easy way to filter network packets based on their source/destination countries. This can be a useful arsenal to deploy in your firewall system if needed. As a final word of caution, I should mention that GeoIP-based traffic filtering is not a foolproof way to ban certain countries on your server. GeoIP database is by nature inaccurate/incomplete, and source/destination geography can easily be spoofed using VPN, Tor or any compromised relay hosts. Geography-based filtering can even block legitimate traffic that should not be banned. Understand this limitation before you decide to deploy it in your production environment.


Subscribe to Xmodulo

Do you want to receive Linux FAQs, detailed tutorials and tips published at Xmodulo? Enter your email address below, and we will deliver our Linux posts straight to your email box, for free. Delivery powered by Google Feedburner.


Support Xmodulo

Did you find this tutorial helpful? Then please be generous and support Xmodulo!

The following two tabs change content below.
Dan Nanni is the founder and also a regular contributor of Xmodulo.com. He is a Linux/FOSS enthusiast who loves to get his hands dirty with his Linux box. He likes to procrastinate when he is supposed to be busy and productive. When he is otherwise free, he likes to watch movies and shop for the coolest gadgets.

21 thoughts on “How to block network traffic by country on Linux

  1. This is awesome! Do you think this approach could improve security of an sftp file server? I don't expect connections from abroad, can this reduce cracking attempts from all around the world?

    • Sorry for the extremism. It is an interesting method, however I think you should deny by default, and accept those you need. Opposite logic.

      • That works only if you know exactly who are those that you need. Which will be true only in very limited instances.

    • It would marginally reduce the risk. Keep in mind that some connections and attacks are "proxied" via compromised hosts and will not appear from abroad. You can make a set of rules to only allow specific IPs. I would recommend to avoid using FTP altogether unless you absolutely have to.

  2. Hello Danny,

    It is another good tutorial as I expected. Some other point of view:

    I use both variants ipset and geoip module for iptables for several years. Both have advantages and disadvantages at the same time. If I must select only one, I will go with ipset (it is not so nice to compile any kernel module). But in the end both of them will add some iptables rules. So any new connection (at least) must be checked by iptables, and it is not a smart option (in my opinion).

    Another good option is to configure the routing table with null route, because:

    1. You do not need any kernel module
    2. You do not need to read the routing table for any new connection (routing table has a cache)
    3. It uses less resources compared with iptables

    It is not so complicated to make a script that take geoip database and convert it to null routes.

    • @Murgulet, thanks for your comment and thought.
      Null routes are indeed a good option. But I guess this is something you can deploy at the upstream routers, not on end servers themselves, as blackhole routes on end servers will not block incoming traffic, but only outgoing traffic. If you don't have access to upstream routers, you will have to resort to iptables on end servers.

      • Sorry for my late reply :)
        Yes your observation is true. But in many situations the null routes are sufficient because the source IPs do not have any connection. For example it is true for the case mentioned by you: "legally stream to particular countries only." So any Linux admin will use what is good for him.

        • Yes, but if someone sends you lots of unsolicited packets as part of DoS attack, null routes cannot protect your network stack from them, while iptables can drop them before the packets enter the stack.

          • It is not entirely true. Only if you have a low level DoS, iptables can protect you. But with a high level DoS attack, the end result is the same (with iptables or with null routes). Any other "good" clients cannot connect to your server/service. Also on some medium level DoS attacks, iptables will consume a lot of resources of the server/service, and in this case it is possible that this in fact has an impact on your server. At the same level, null routes will be better because the critical resource consumption will be lower compared to iptables.

            But if you are a pro-active admin and you really know your landscape/server, you can use what is best for you. For example you could mix both methods: If we have a low DoS, we will use iptables-geoip. If we have a medium DoS, use null routes. This can be set up with monit for example:

            if cpu_counter > X 
            if packets_numbers/time_unit > Y then
             RUN null_route_script.sh 
            else
             RUN iptables_geoip_script.sh
            end if
            end if
            

            Like I said in my old tutorial about monit, it is a very good tool if you want to use it! To be more general, with monit you can get the state of your server (cpu/load/memory/network and so on), and on any combination of resource levels, you can use what is the best protection for that moment (iptables geoip/ipset/TARPIT, or any other tool). As a conclusion, any tool is good in some situation, but not in all. So it is better to use the best tool in the proper situation and monit can make it possible for us!
            Thx. monit ;)

  3. In a way I think this is horrible, specially if you relay on the free GeoIP list which has a number of IP-ranges listed for the wrong country and this kind of rules will also make your network slower, then better to use only ssh keys to login.

  4. Well. How accurate is Maxmind? We have to inform them at least every month and mostly they don't changes anything. The only reason I've come up with is that they want paying customer and not users who control their own prefixes. Maxmind is sort of useless. The simple reason is that there is no geographical IP.

    • While it's true that there is no notion of geography in IP addresses, there are many practical heuristics being used to infer their physical locations. For example, most ISPs incorporate various geographic hints (e.g., major nearby cities, airport codes) in DNS names of the IP addresses assigned to their customers for troubleshooting purposes. There are also delay measurement technique (inferring host locations from nearby network nodes) or web crawling (for physical addresses) based inference, etc. GeoIP is not rocket science, bu some of these heuristics can generate pretty accurate GeoIP databases at least on coarser-granularity.

      According to the MaxMind's official statement:

      "In our recent tests, the databases were 99.8% accurate on a country level, 90% accurate on a state level in the US, and 81% accurate for cities in the US within a 50 kilometer radius."

    • Yes I can also confirm what @Gotxi wrote! ipblocks is better compared to geoip. Not a single false positive in almost a year for China. It is not the case for geoip (China only geoip).

  5. Neat and this is a lot cleaner then adding in all the ranges. I will suggest you add an allow rule for the country’s you want to access and deny the rest. Your ACL's might be shorter plus it won't tax the Linux box as much.

  6. Cool. Just understand that this is only blocking the lowest common denominator of security threats. That may be enough... until it isn't. Do proper security too, please.

  7. Oops. You need a compiler present to do this. I'd rather not install gcc on a bastion host (duh). Sure, I can remove the binaries with a list of --ignore-depends, but that's going to cause patching problems down the road.

    Also what's not mentioned here: what countries should you block? A few are well known, but I wonder if there isn't a good aggregate blacklist. Otherwise, you'll be piecing together a long list!

  8. Very helpful.
    Saved me a lot of time trying to use tcpwrappers.
    I used your instructions directly with xtables and selinux configuration but also reused a tcpwrappers hack that I found somewhere else to finish off.

    ./add-ip.sh

    #!/bin/bash
    ## CENTOS7 as root do.
    yum install gcc-c++ make automake kernel-devel-`uname -r` wget unzip iptables-devel perl-Text-CSV_XS GeoIP
    wget http://downloads.sourceforge.net/project/xtables-addons/Xtables-addons/xtables-addons-2.10.tar.xz
    tar -xf xtables-addons-2.10.tar.xf
    cd xtables-addons-2.10
    ./configure
    make
    make install
    chcon -vR --user=system_u /lib/modules/$(uname -r/extra*.ko
    chcon -vR --type=lib_t /lib64/xtables/*.so
    cd geoip
    ./xt_geoip_dl
    ./xt_geoip_build GeoIPCountryWhois.csv
    mkdir -p /usr/share/xt_geoip
    cp -r {BE,LE} /usr/share/xt_geoip
    

    Run this script with the offending IP address of a country you want to be done with...

    ALLOWED="AU US UK NZ DE"
    if [ $# -ne 1 ]; then
      echo "Usage:  `basename $0` " 1>&2
      exit 0 # return true in case of config issue
    fi
    
    if [[ "`echo $1 | grep ':'`" != "" ]] ; then
      COUNTRY=`/usr/bin/geoiplookup6 "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
    else
      COUNTRY=`/usr/bin/geoiplookup "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
    fi
    
    [[ $COUNTRY = "IP Address not found" || $ALLOWED =~ $COUNTRY ]] && BLOCK="FALSE" || BLOCK="TRUE"
    if [ $BLOCK == "TRUE" ]
    then
      firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m geoip --src-cc $COUNTRY -j DROP
      firewall-cmd --reload
      echo "$COUNTRY blocked ($1)"
      logger -p authpriv.notice "$COUNTRY blocked ($1)"
      firewall-cmd --direct --get-all-rules
      exit 0
    else
      echo "$COUNTRY";
      exit 1
    fi
    

    Thanks again.

  9. First of all, thank you for this article.
    I've followed all the steps, but got this error. Do you know why geoip is not found?

    $ firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -m geoip --dst-cc CN -j DROP
    $ Error: COMMAND_FAILED: '/sbin/iptables -t filter -I OUTPUT_direct 1 -m geoip --dst-cc CN -j DROP' failed: iptables v1.4.21: Couldn't load match `geoip':No such file or directory

    Thank you,
    Dave

  10. One thing I have found is that under CentOS 7 I have to stick with kernel 3.10.0-229.20.1.el7.x86_64.
    xtables-addons won't compile with a newer CentOS kernel.

    but, if you had no compile errors I would step through the article steps again, as root (or sudo), and restart the firewall (systemctl restart firewalld).

    I did rewrite my add-ip.sh script, which gives a bit more 'informed' control. See below:

    #!/bin/bash
    ## CENTOS7 as root do.
    yum install gcc-c++ make automake kernel-devel-`uname -r` wget unzip iptables-devel perl-Text-CSV_XS GeoIP
    wget http://downloads.sourceforge.net/project/xtables-addons/Xtables-addons/xtables-addons-2.10.tar.xz
    tar -xf xtables-addons-2.10.tar.xf
    cd xtables-addons-2.10
    ./configure
    make
    make install
    chcon -vR --user=system_u /lib/modules/$(uname -r/extra*.ko
    chcon -vR --type=lib_t /lib64/xtables/*.so
    cd geoip
    ./xt_geoip_dl
    ./xt_geoip_build GeoIPCountryWhois.csv
    mkdir -p /usr/share/xt_geoip
    cp -r {BE,LE} /usr/share/xt_geoip
    

    # run this script with the offending IP address of a country you want to be done with...

    ALLOWED="AU US UK NZ DE JP"
    if [ $# -ne 1 ]; then
      echo "Usage:  `basename $0` " 1>&2
      exit 0 # return true in case of config issue
    fi
    
    echo --sendmail log--
    cat /var/log/maillog | grep -e $1
    echo --apache log--
    cat /var/log/httpd/*_log | grep -e $1
    
    echo 'block? (y or n)'
    read confirm
    
    if [ $confirm == 'n' ]
    then
      echo NOT BLOCKING
      exit 0
    fi
    
    if [[ "`echo $1 | grep ':'`" != "" ]] ; then
      COUNTRY=`/usr/bin/geoiplookup6 "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
    else
      COUNTRY=`/usr/bin/geoiplookup "$1" | awk -F ": " '{ print $2 }' | awk -F "," '{ print $1 }' | head -n 1`
    fi
    
    [[ $COUNTRY = "IP Address not found" || $ALLOWED =~ $COUNTRY ]] && BLOCK="FALSE" || BLOCK="TRUE"
    if [ $BLOCK == "TRUE" ]
    then
      firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -m geoip --src-cc $COUNTRY -j DROP
      firewall-cmd --reload
      echo "$COUNTRY blocked ($1)"
      logger -p authpriv.notice "$COUNTRY blocked ($1)"
      firewall-cmd --direct --get-all-rules
      exit 0
    else
      echo $COUNTRY
      firewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address=$1 reject"
      firewall-cmd --reload
      echo "$1 added to rich rules"
      firewall-cmd --list-all
      exit 1
    fi
    

    Good luck!

  11. /etc/cron.weekly/geoip_update.sh script

    #!/bin/bash
    
    GEOIPDIR=/root/xtables-addons-2.10/geoip
    GEOIPDEST=/usr/share/xt_geoip
    
    cd $GEOIPDIR
    ./xt_geoip_dl
    ./xt_geoip_build GeoIPCountryWhois.csv
    rm -rf $GEOIPDEST/BE
    rm -rf $GEOIPDEST/LE
    cp -r {BE,LE} $GEOIPDEST
    /bin/firewall-cmd --reload
    /bin/firewall-cmd --direct --get-all-rules
    /bin/geoipupdate
    

Leave a comment

Your email address will not be published. Required fields are marked *