I admin ecom website and a lot of bogus traffic comes from countries that do not offer much in commercial value. How do I just configure Apache or iptables to just refuse connections to certain countries?
You can block traffic at both Apache or iptables level. I recommend iptables to save some resources. First, you need to get list of netblocks for each country. Simply visit this page and download IP block files are provided in CIDR format. Use the following shell script:
WARNING!People from other countries may use proxy server or think of spoofing their IP address. In such case, this may not work and it will only protect your box from automated scans or spam.#!/bin/bash ### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ### ISO="af cn" ### Set PATH ### IPT=/sbin/iptables WGET=/usr/bin/wget EGREP=/bin/egrep ### No editing below ### SPAMLIST="countrydrop" ZONEROOT="/root/iptables" DLROOT="http://www.ipdeny.com/ipblocks/data/countries" cleanOldRules(){ $IPT -F $IPT -X $IPT -t nat -F $IPT -t nat -X $IPT -t mangle -F $IPT -t mangle -X $IPT -P INPUT ACCEPT $IPT -P OUTPUT ACCEPT $IPT -P FORWARD ACCEPT } # create a dir [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT # clean old rules cleanOldRules # create a new iptables list $IPT -N $SPAMLIST for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone # get fresh zone file $WGET -O $tDB $DLROOT/$c.zone # country specific log message SPAMDROPMSG="$c Country Drop" # get BADIPS=$(egrep -v "^#|^$" $tDB) for ipblock in $BADIPS do $IPT -A $SPAMLIST -s $ipblock -j LOG --log-prefix "$SPAMDROPMSG" $IPT -A $SPAMLIST -s $ipblock -j DROP done done # Drop everything $IPT -I INPUT -j $SPAMLIST $IPT -I OUTPUT -j $SPAMLIST $IPT -I FORWARD -j $SPAMLIST # call your other iptable script # /path/to/other/iptables.sh exit 0
Save above script as root user and customize ISO variable to point out country name using ISO country names. Once done install the script as follows using crontab:
@weekly /path/to/country.block.iptables.sh
To start blocking immediately type:
# /path/to/country.block.iptables.sh
And you are done with blocking the whole country from your server.
iptables geoip patch
Another, alternative to above shell script is to use geoip iptables patch. This is not standard iptables modules. You need to download patch and compile Linux kernel.
- Grab geoipt patch from the official website.
- Download and install Linux kernel and iptables source code.
- Grab and install tool called patch-o-matic (required for geoip modules).
- Finally, grab GEO IP database from MaxMind.
The details of kernel compile and iptables patching are beyond the scope of this FAQ. This is left as an exercise to readers.
Further Enhancements (ready to use scripts)
See discussion below in the comments:
- Bash shell Script (sys v style) by David Picard
- Perl Script by Timothe Litt (see also installtion script)
You should follow me on twitter here or grab rss feed to keep track of new changes.
Featured Articles:
- 30 Handy Bash Shell Aliases For Linux / Unix / Mac OS X
- Top 30 Nmap Command Examples For Sys/Network Admins
- 25 PHP Security Best Practices For Sys Admins
- 20 Linux System Monitoring Tools Every SysAdmin Should Know
- 20 Linux Server Hardening Security Tips
- Linux: 20 Iptables Examples For New SysAdmins
- Top 20 OpenSSH Server Best Security Practices
- Top 20 Nginx WebServer Best Security Practices
- 20 Examples: Make Sure Unix / Linux Configuration Files Are Free From Syntax Errors
- 15 Greatest Open Source Terminal Applications Of 2012

- My 10 UNIX Command Line Mistakes
- Top 10 Open Source Web-Based Project Management Software
- Top 5 Email Client For Linux, Mac OS X, and Windows Users
- The Novice Guide To Buying A Linux Laptop












{ 124 comments… read them below or add one }
This was a very useful post, something I’ve been looking at doing for some time. Thank you for the great information!
i am not interested in iptables itself all that much but I have to say that the script is one of best formatted ones on the internet.
The nixCraft posts themselves are of excellent quality.
Keep it up,
Happy *nixing
Nice topic, I like it and I like they way script is made. Good job.
I definitely use this in my office.
Can I block this IP’s from Squid ?
Thank you for your post.
Creating so many iptables rules creates huge memory overhead loading all these rules as well as delay and cpu overhead while scanning all these rules for each new connection – so it’s not practical.
While you can use ipset module – http://ipset.netfilter.org
to make it work fast
# I am using the following script to ban all IP except a couple of known IP’s
# Can I improve the script?
#!/bin/sh
`iptables -F INPUT`
while read server; do
`iptables -A INPUT -p tcp –dport 3306 -s $server -j ACCEPT`
done <<HERE
17.29.0.21
17.29.0.22
HERE
`iptables -A INPUT -p tcp –dport 3306 -j REJECT`
`service iptables save`
While I’m not commenting on the script per se, the motivation behind the script seems highly questionable…to broadly block users from entire countries from accessing e-commerce sites is unethical.
Myself, I’m an American engineer working in the oil fields of Nigeria who has to rely on internet access to banking, insurance, educational and technical sites just as you ALL do. If you block my access because i’m on a nigerian IP address, i’m screwed.
Thanks for making working in this place all the more difficult.
Yeah, that’s a common scenario. You’re right, we should allow unrestricted access from Nigeria and every other country well known for scamming, phishing, hacking, cyber-crime, etc. just so you can get to your sites as you please. Why on earth would anyone expect you to be personally responsible for assuring personal, individual access to your personal bank? We really made a huge mistake. I mean it’s not like you can set up a U.S. proxy server, or use an existing one, or pay even a half-wit server nerd $50 to do it for you. All of the servers in this country should acquiesce to your needs immediately without regard to our own financial and data security. I’m going to go self-flagellate for my unethical, selfish behavior (right after I delete all passwords from my server). I’m so, so, so sorry.
Herb – if you read on a bit further you’ll see that I modified the script to allow access on ports you can define. HTTP vulnerabilities are out there, but leaving that port open and blocking traffic from the named countries on all other ports seems relatively harmless. If you want to leave HTTP open specify 80,443 as the open ports in my modified script which can be downloaded from my blog
My blog url was changed during a recent reinstall of wordpress – I posted a comment on the end with the new blog link and here at my first (of many) comments
@piavlo,
Yes, ipset would be nice due to speed factor and to avoid errors.
Hi Vivek, Is there a way to log all traffic on a server or Linux router, blocked or not, maybe like a report to round off the above handy script?
@Johan,
You can setup a central logging Linux host using syslogd itself. You can send all logs from system or router to loghost. No need to write a script.
That’s fine for just logging, Vivek, but this script isn’t about mere logging.
Oh, nvm, missed Johan’s post just before, please delete.
Blocking by country may make sense in some settings, but may be offensive in others. Be careful how you define “no commercial value”, etc. I know of some people who block IP ranges like those originating in South Korea, forgetting that we have US military members stationed in that country. A common lock-out occurs on IRC.
What I am saying is that it is possible that blocking by country may not be the most direct way to fix some problems.
I’m not touching the moral question, of blocking on the basis of country of origin. I just checked a random IP from the country codes of the given web site and it appears they don’t mach:
$ whois 89.149.128.5 |grep ^country
country: US
$ grep 89.149.128.0 eu.zone
89.149.128.0/18
So the script would think that the USA is a member of the EU. Other IPs of the EU zone seem to be wrong as well.
For ubuntu & ufw:
#!/bin/bash ### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ### ISO="af cn vn" ### Set PATH ### WGET=/usr/bin/wget EGREP=/bin/egrep ### No editing below ### ZONEROOT="/root/ufwzones/" DLROOT="http://www.ipdeny.com/ipblocks/data/countries" # create a dir [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone echo "Downloading $c.zone .." # get fresh zone file $WGET -O $tDB $DLROOT/$c.zone >> /dev/null 2>&1 BADIPS=$(egrep -v "^#|^$" $tDB) for ipblock in $BADIPS do echo "Blocking IP $ipblock.." /usr/sbin/ufw deny from $ipblock done done exit 0Great post have been looking for this along time now :) thank you
Note, Ubuntu’s UFW stores the rules to file /var/lib/ufw/user.rules</. I still haven’t found a good way to clean the old rules before adding new ones (like if you want to delete china zone). Just delete ~1000 IP-addresses from there with vim, but remember to leave the end rules marker:
### RULES ###
*delete from here*
### END RULES ###
-A ufw-user-input -j RETURN
-A ufw-user-output -j RETURN
-A ufw-user-forward -j RETURN
COMMIT
@incidence
I’ve got the same problem but use a Perl 1-liner to do it:
USERFILE=”/lib/ufw/user.rules”
perl -e ‘$f=pop(@ARGV); -f($f)||die($!); open(IN,”<$f")||die($!); $s=join("",); close(IN); $s =~ s/(### RULES ###).*(### END RULES ###)/$1\n$2/s; open(OUT,”>$f”)||die($!); print OUT $s; close(OUT);’ $USERFILE
Guys, you sucks….
You think with your IPTABLES you can block people from other countries to visit your website?? ARE YOU KIDDING!!! What about online proxies? and what about thousands (may be millions) USA PCs without any protection which allow hackers and other newbies to access whatever they want???
THIS IS NOT A GOOD IDEA AT ALL!!!
If you want to protect your network, you got to implement a good firewall.
firewall builder or ipcop would be enough for you.
cheers.
hel
Stfu you tool.
Damn ..
I was wrong =(
I supposed that iptables was a firewall =(
…
OF COURSE YOU CANNOT BLOCK AT 100%, but we can do a little bit more difficult to scriptkiddies blocking SSH, FTP or whatever ¬¬
You want true security?
Cut the UTP
this post is very useful.
Keep it up !
It kills me people talking about the “morality” of blocking countries. Morality is about should you sleep with your best friends wife not blocking an IP. We never blocked anyone for eight years. During that time we had traffic from certain countries that never brought a penny into our company. Those same countries traffic brought hackers and crackers and credit card fraud scammers by the groves to our company. If a certain segment is doing nothing but costing me money and time why should I not block them? Why should I even spend the time with working firewall rules on those countries traffic? As far as getting by my block with a proxy, if you have ever fought with hackers, crackers and scammers you would know that most of them are too lazy to target one site. They are looking for the easy buck. This is not to say that one should rely on only blocking countries. But blocking a country that has always been nothing but problems is the *front line”. If you are in the online security war you know that you need to knock out the bulk of the problem first and then layers of security after that. For the Americans living outside the USA move back home or buy a subscription to a proxy service.
So, basically IPSet would store everything in it allows you to add 1 rule only in IPtables to block an entire country?
Another person commented about cpu overhead of having so many rules (I’m using over 6200 rules for just 4 countries). How efficient is iptables with lots of rules? Does it test each rule in turn (until a match and action is taken) or does it use some sort of decision tree based on the rules to quickly know which rules (or group of rules) to test and which to ignore?
If iptables does not optimize, would the cpu overhead be reduced if multiple chains were used, one for each octet of the ip? For example, group all the X.*.*.* rules into a “countrydropX” chain. And then have 254 rules (no 0 or 255) in the countrydrop chain, such as:
iptables -A countrydrop -s 1.0.0.0/8 -j countrydrop1
…
iptables -A countrydrop -s 254.0.0.0/8 -j countrydrop254
This design assumes that all the country ip’s to be checked are /8 or higher.
This design increases the complexity but should significantly reduce the cpu overhead (assuming that iptables doesn’t already do some sort of optimization).
@ David,
For lots of ip use IPSet, it offers speed and uses less CPU as compared to iptables. http://ipset.netfilter.org/
I can only use iptables. My server does not come with ipset and I cannot and will not install ipset. That’s why I’m asking if iptables efficiency would be increased by grouping rules by the first octet of the IP. I’ll assume that it will, and I will re-organize my IP rules that way. Thanks.
My personal server was aggressively probed, nmapped, and generally DoS’ed today. I use iptables normally and my server name is somewhat complicated, because it is used for personal file access. Non-the-less, some host in China took it upon themselves to determine what I was going to do with a few of my valuable hours.
I manually stopped the attack and then installed this script. Just finished nmapping the new config and all is well and good.
Thanks for the script!
IP Deny table is incomplete. For example, domains SU and TK aren’t there.
Great script. It reduces the attacks and spam here about 95%. I noticed that the attackers switched the source system to countries that are not blocked yet. I added those countries, too and they switched to another. Kind of funny, but may leads to block the whole word exept the countries that should have access. How about a 2nd script that allows to use it as a whitelist instead? It´s much faster to config iptables just to allow connects from xx, yy, zz. .. Would be a great help.
For internet radio and rights you have to pay for playing music, on a particular server you might want to think the other way around. In fact you do not want anyone outside your own country to be able to connect to your server so you avoid any conflicts in rights to be paid on the music for every country that listens. Is it possible for you to rewrite this so it will allow only ip’s from 1 specific country (i.c. nl) to attach to the streaming server so that it does not mess up processing power?
Thank you very much, easy to use and VERY effective.
Thanks,
Excellent bit of work here. Finally a reliable way to basically make some of the more annoying countries go away. Excellent work indeed!
Nice script – however I have modified the script slightly to cut down on unnecessary rule processing. Effectively, I’ve made it only deny new connections and it only searches through the rules that match the first octet in the ip range for each packet rather than all rules. I also do not wipe the entire iptable on refresh as this would eliminate custom rules I’ve added on VPN startup and foul my VPN connections. See below:
#!/bin/bash ### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ### ISO="af cn" ### Set PATH ### IPT=/sbin/iptables #IPT=/bin/echo WGET=/usr/bin/wget EGREP=/bin/egrep ### No editing below ### SPAMLIST="countrydrop" ZONEROOT="/root/iptables" DLROOT="http://www.ipdeny.com/ipblocks/data/countries" cleanOldRules(){ #$IPT -F #$IPT -X #$IPT -t nat -F #$IPT -t nat -X #$IPT -t mangle -F #$IPT -t mangle -X #$IPT -P INPUT ACCEPT #$IPT -P OUTPUT ACCEPT #$IPT -P FORWARD ACCEPT $IPT -D INPUT -j $SPAMLIST $IPT -D OUTPUT -j $SPAMLIST $IPT -D FORWARD -j $SPAMLIST $IPT -X $SPAMLIST TOPIP=`iptables -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$SPAMLIST'` for i in $TOPIP do $IPT -F ${i} $IPT -X ${i} done } # create a dir [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT # clean old rules cleanOldRules # create a new iptables list $IPT -N $SPAMLIST for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone # get fresh zone file $WGET -O $tDB $DLROOT/$c.zone # country specific log message SPAMDROPMSG="$c Country Drop" # get BADIPS=$(egrep -v "^#|^$" $tDB) for ipblock in $BADIPS do topip=`echo $ipblock | cut -f 1 -d '.'` $IPT -A $topip-$SPAMLIST -s $ipblock -j LOG --log-prefix "$SPAMDROPMSG" $IPT -A $topip-$SPAMLIST -s $ipblock -j DROP done done TOPIP=`iptables -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$SPAMLIST'` for i in $TOPIP do sip=`echo ${i} | cut -f 1 -d '-'`.0.0.0/8 $IPT -A $SPAMLIST -s ${sip} -j ${i} done # Drop everything $IPT -I INPUT -m state --state NEW -j $SPAMLIST $IPT -I OUTPUT -m state --state NEW -j $SPAMLIST $IPT -I FORWARD -m state --state NEW -j $SPAMLIST # call your other iptable script # /path/to/other/iptables.sh exit 0Sorry about the last post – please switch out the IPT environment from echo back to iptables – posted the debug version of the script ;-)
I’ve modified the script a little further as the initial load using iptables line by line took over 13 hours. A cleaner and higher performance approach will leverage iptables-restore which commits the tables once at the end of the load instead of after each additional range addition.
In addition, I’ve added a parameter for allowed ports to allow only traffic destined for certain ports from the country to be blocked – so my friends serving in Afghanistan are now able to get to my web site.
Note on performance – using iptables-restore loads the rules in less than a second – see the below script:
#!/bin/bash ### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ### ISO="af cn" ### Set PATH ### IPT=/sbin/iptables WGET=/usr/bin/wget EGREP=/bin/egrep ### No editing below ### CBLIST="countrydrop" ZONEROOT="/var/iptables" IPTCBRESTORE="/etc/sysconfig/iptables.cb" ALLOWPORTS=80,443 MAXZONEAGE=7 DLROOT="http://www.ipdeny.com/ipblocks/data/countries" cleanOldRules(){ $IPT -L $CBLIST > /dev/null 2>&1 if [ $? = 0 ] ; then $IPT -D INPUT -j $CBLIST $IPT -D OUTPUT -j $CBLIST $IPT -D FORWARD -j $CBLIST fi TOPIP=`$IPT -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$CBLIST'` for i in $TOPIP do $IPT -F ${i} $IPT -X ${i} done $IPT -X $CBLIST } updateZoneFiles() { ZONEARCH=${ZONEROOT}/arch mkdir -p ${ZONEARCH} find ${ZONEROOT} -maxdepth 1 -mindepth 1 -ctime +${MAXZONEAGE} -exec mv {} ${ZONEARCH} \; for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone if [ -f $tDB ] ; then printf "Zone file %s is new enough - no update required.\n" $tDB else # get fresh zone file if it is newer than MAXZONEAGE days $WGET -O $tDB $DLROOT/$c.zone fi done oldzones=`find ${ZONEROOT} -mindepth 1 -maxdepth 1 -type f -exec basename {} \; | cut -f 1 -d '.'` # Archive old zones no longer blocked for z in $oldzones ; do archme=${c} for c in $ISO ; do if [ $c = $z ] ; then archme="X"; fi done if [ $archme = $z ] ; then mv ${archme} ${ZONEARCH} else printf "Working from previous zone file for %s\n" ${z} fi done } createIPTLoadFile() { printf "# Generated by %s on" $0 > ${IPTCBRESTORE} printf "%s " `date` >> ${IPTCBRESTORE} printf "\n*filter\n" >> ${IPTCBRESTORE} # Create CBLIST chain printf ":$CBLIST - [0:0]\n" >> ${IPTCBRESTORE} printf "%s INPUT -j $CBLIST\n" "-I" > ${IPTCBRESTORE}.tmp printf "%s OUTPUT -j $CBLIST\n" "-I" >> ${IPTCBRESTORE}.tmp printf "%s FORWARD -j $CBLIST\n" "-I" >> ${IPTCBRESTORE}.tmp if [ "Z${ALLOWPORTS}" = "Z" ] ; then printf "Blocking all traffic from country - no ports allowed\n" else printf "%s $CBLIST -p tcp -m multiport ! --dports ${ALLOWPORTS} -j RETURN\n" "-I">> ${IPTCBRESTORE}.tmp fi for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone # country specific log message SPAMDROPMSG="$c Country Drop" # Create drop chain for identified packets CBLISTDROP=${c}-${CBLIST}-DROP printf ":${CBLISTDROP} - [0:0]\n" >> ${IPTCBRESTORE} printf "%s ${CBLISTDROP} -j LOG --log-prefix \"$SPAMDROPMSG\"\n" "-A" >> ${IPTCBRESTORE}.tmp printf "%s ${CBLISTDROP} -j DROP\n" "-A" >> ${IPTCBRESTORE}.tmp # Load IP ranges into chains correlating to first octet BADIPS=$(egrep -v "^#|^$" $tDB) for ipblock in $BADIPS do topip=`echo $ipblock | cut -f 1 -d '.'` chainExists=`grep -c :${topip}-${CBLIST} ${IPTCBRESTORE}` if [ $chainExists = 0 ] ; then printf "Creating chain for octet %s\n" ${topip} printf ":$topip-$CBLIST - [0:0]\n" >> ${IPTCBRESTORE} sip=${topip}.0.0.0/8 printf "%s $CBLIST -s ${sip} -j $topip-$CBLIST\n" "-A" >> ${IPTCBRESTORE}.tmp fi printf " Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} printf "%s $topip-$CBLIST -s $ipblock -j ${CBLISTDROP}\n" "-A" >> ${IPTCBRESTORE}.tmp done done cat ${IPTCBRESTORE}.tmp >> ${IPTCBRESTORE} && rm -f ${IPTCBRESTORE}.tmp printf "COMMIT\n# Completed on " >> ${IPTCBRESTORE} printf "%s " `date` >> ${IPTCBRESTORE} printf "\n" >> ${IPTCBRESTORE} } directLoadTables() { # Create CBLIST chain $IPT -N $CBLIST $IPT -I INPUT -j $CBLIST $IPT -I OUTPUT -j $CBLIST $IPT -I FORWARD -j $CBLIST if [ "Z${ALLOWPORTS}" = "Z" ] ; then printf "Blocking all traffic from country - no ports allowed\n" else $IPT -I $CBLIST -p tcp -m multiport ! --dports ${ALLOWPORTS} -j RETURN fi for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone # country specific log message SPAMDROPMSG="$c Country Drop" # Create drop chain for identified packets CBLISTDROP=${c}-${CBLIST}-DROP $IPT -N ${CBLISTDROP} $IPT -A ${CBLISTDROP} -j LOG --log-prefix "$SPAMDROPMSG" $IPT -A ${CBLISTDROP} -j DROP # Load IP ranges into chains correlating to first octet BADIPS=$(egrep -v "^#|^$" $tDB) for ipblock in $BADIPS do topip=`echo $ipblock | cut -f 1 -d '.'` $IPT -L $topip-$CBLIST > /dev/null 2>&1 if [ $? = 1 ] ; then printf "Creating chain for octet %s\n" ${topip} $IPT -N $topip-$CBLIST sip=${topip}.0.0.0/8 $IPT -A $CBLIST -s ${sip} -j $topip-$CBLIST fi printf " Adding rule for %s to chain for octet %s\n" ${ipblock} ${topip} $IPT -A $topip-$CBLIST -s $ipblock -j ${CBLISTDROP} done done } loadTables() { createIPTLoadFile ${IPT}-restore -n ${IPTCBRESTORE} #directLoadTables } # create a dir [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT # clean old rules cleanOldRules # update zone files as needed updateZoneFiles # create a new iptables list loadTables exit 0Forgot one point on the release notes – I don’t like to hammer people who provide a valuable resource – I also added a check to ensure the age of the zone file was greater than 7 days (configurable) before going to the ipdeny.com site to get new content. 30 days might be a better default – but 7 is better than all the time ;-)
@David,
Excellent work! I’ve updated faq and created a download link to your post. Thanks for your contribution.
Great Job! Following this topic and using the script for three months now on all servers. Unfortunately it seems it has come to a point where you can’t run a server without it. This should be part in the future of all major linux distro’s in utility’s like yast, yum to make it a little bit more accesible to everyone. Once again, great job!
After a little monitoring and testing with the port exclusions, I realized the –dports options were inverted – the ‘!’ operator needs to be removed from those rules.
Noticed the China block also includes the private class C subnet of 192.168.0.0/16 – modified the script again slightly to skip filtering the private subnet – as below. Note that the subnet can be changed to filter against a smaller subnet and exclude subnets you don’t serve:
Would this script alter the existing ip-tables settings?
The version I modified alters the existing iptables settings by introducing and maintaining additional chains. It will not remove any customized settings you have added to iptables if you ensure the variable CBLIST does not overlap the namespace for existing chains.
I have devised a similar method but with a slight variation. It combines the idea of vivek, david and myself.
Check it out here
Hi Nilesh -
Your changes will work, of course, but the issues here are in kernel performance in processing the chain.
It’s best to consider the path through the table each packet might take and to reduce the number of rules that might be checked. In the case of your script, every declared subnet will be checked, while in the optimized version I posted earlier, we are reducing the number of rules checked at runtime for each incoming packet by anywhere from 80-98% due to the hierarchical nature of the constructed chains.
Yeah; best is to use a custom compiled kernel along with ipset. :)
Custom compilation of the kernel is not required – the rules should be organized such that the minimum number of rules need be checked. Hierarchical structuring of the rule configuration is sufficient for these purposes. Neither is ipset required to provide optimal firewall performance.
Can you please post the script for ubuntu ? I tried this script but it gave error that /etc/sysconfig/iptables.cb not found.
This script will really be helpful, I am getting too much hacking attempts from china.
Hi M -
First – try running the script via sudo as this may be a permissions issue for access to the /etc/sysconfig directory.
You may need to change the ZONEROOT, IPTCBRESTORE, IPTCBDEVICE, and ALLOWSUBNET parameters at the top of the script posted on this thread to fit your system and network configuration. IPTCBRESTORE defines the filename where the generated rules will be saved and restored from and by default is set to “/etc/sysconfig/iptables.cb” – you might try changing the /etc/sysconfig part to a directory that exists in Ubuntu if Ubunutu does not have a /etc/sysconfig directory.
Thanks for responding David. Sorry for a noob question but does iptables.cb file created by you or is it standard iptables config file in your linux distro. If it is a file created by you, then does the location of file matter ? i.e. can I just put in /etc/network (or any other folder).
iptables.cb is created by this script and saved to the /etc/sysconfig directory to provide better visibility as this is normally where iptables rules are saved. You can save the rules anywhere in your filesystem – if Ubuntu normally exports iptables rules to a different location, I would suggest defining this file to be located there as well
Thanks, it worked. However, it looks like the ipdeny site is missing several big segments. I had several ips in my logs which as per this http://remote.12dt.com/lookup.php site belong to china. Can I suggest a further improvement ? How about having it take an additional custom file with ip blocks and block that too ? That would allow other scripts to add to this file and call your script to block certain ips.
Here are some IP’s
117.41.168.235, 119.147.116.157, 119.147.116.158
Hi David,
I am having an error loading the script… I have flushed iptables tried and tried again but keep getting this error:
iptables-restore v1.3.0: error creating chain ‘mk-countrydrop-DROP’:File exists
Error occurred at line: 251
any ideas?
Thanks,
Chris
Chris -
Looking at the script I can think of two reasons this might occur:
1. The table was not properly cleared and the drop rule already exists. If you flushed the table properly we can rule this out.
2. You have listed ‘mk’ twice as a blocked country.
If neither is the cause, I’m stumped ;-)
Ahhhhaaaa! Double entry! Got it! Works like a charm!
Thanks David! Kudos to you sir!
-Chris
Thank you so much to all the people who contributed to this article, especially Vivek and Dave!!!! You guys were a big help as I been having this problem with my home server and not familiar with configuring iptables. So thanks again…
I had a problem the other day trying to download the firmware for my Samsung BD player – turns out the outbound request was going to Korea, and I had Korea categorically blocked.
So I added the following line to the script allowing responses to outbound requests from my network just before the first ‘if’ statement in the createIPLoadFile function and can now access Korean sites for my BD updates.
printf “%s $CBLIST -m state –state RELATED,ESTABLISHED -j ACCEPT\n” “-I”>> ${IPTCBRESTORE}.tmp
Fantastic! Thanks Vivek and David P.
I’ve been using fail2ban for years and it works great. I’ve noticed, however, that when fail2ban blocks an IP from say China, often over the next hour or so, more from the same country will be blocked. It appears that the probes or attacks are coordinated. So, if they figure out my fail2ban thresholds, they could sustain an attack for quite some time. This script will be a perfect addition!
David, when I add the following line per your updated post (fixing the smartquotes when pasted of course)
printf “%s $CBLIST -m state –state RELATED,ESTABLISHED -j ACCEPT\n” “-I”>> ${IPTCBRESTORE}.tmp
I get the below error. Removing that line allows the script to function properly. Any ideas? Using Centos 4.8 32bit.
Adding rule for 223.255.0.0/17 to chain for octet 223
Bad argument `–state’
Error occurred at line: 54
Try `iptables-restore -h’ or ‘iptables-restore –help’ for more information.
Country block instituted for: cn
Thanks again! Most excellent!
Mike – the point changes on each line can get confusing and line numbers can change. I’ve started a blog entry of my own where the updated script is maintained and can be copied and pasted directly. Please refer to http://psind.com/blog/2010/07/31/targeted-ip-blocking-align-web-services-to-your-target-markets/ for the full script and my thinking around the changes.
Very very very nice effort. Can not thank you enough :)
Cool script, I’m using it for a couple of countries that brute force my ssh server all day. Iptables -L struggles to list all of the rules that’s for sure and it made me curious.
In terms of network overhead, what costs more, denying all of the non US IP blocks or allowing ONLY US ip blocks?
Performance depends on how you have the tables structured within iptables. I suspect that US IP blocks are distributed across more of the first octet which would cause more rules to be checked. I’m not sure that all the US networks are listed either, which would mean that well wishers and potential customers might be denied out of hand based on the missing US sub-nets if you inverted the rule to be allow instead of deny.
The script arranges the tables hierarchically – so we have an iptable defined for each octet that is blocked. This reduces the number of rules checked during runtime significantly – possibly by as much as 99% but more realistically somewhere around 75-85%.
Nilesh had mentioned using ipset a little further back in this thread. ipset may improve performance, but it also may not be part of your distribution and could require custom compilation and installation.
Under modest loads of 10-20 requests per second I would suspect that you are not likely to observe much of a difference from a CPU utilization perspective. If your traffic is higher than this you might consider using ipset or possibly getting a more dedicated solution.
Aaron, instead of filtering a IP range on iptables, you might give a try on ‘fail2ban’ to protect services like ssh against bruteforce attacks.
http://www.fail2ban.org/wiki/index.php/Main_Page
Bremm – good suggestion, I would suggest using both approaches as security measures are intended to delay a compromise – or to make the attempt so unappealing that the attacker gives up and searches for an easier target ;-)
I’ve done further work on this script, turning it into a script that can be run as a service at startup as well as a cron job. It also does some reporting. This version was developed under fedora core, but should easily port to other distributions. For full documentation, run with –help (note that the help text varies with the configuration.
Run chkconfig (or equiv) to get the service installed.
This is a poor mechanism for distributing large scripts – I have provided it to ipdeny.com as a .tar file; you may be able to pick it up from their tools area sometime soon.
Cron job:
51 3 * * Wed /etc/init.d/BlockCountries start -updateSample /etc/syconfig/BlockCountries – modify as required
/etc/init.d/BlockCountries
Thanks for sharing the script with us.
Bugger the morals, I’m just sick of all the spam. That’s why I ban the entire Russian Federation IP space.
That’s why I block US entirely too.
Hi Vivek
I have ran this script on one of my VPS server but its not running successfully. Can you list the modules required to run this iptables script successfully?
Regds
Sushant Chawla
Sr. Systems Engineer (Linux)
If you’re talking about the perl version, the list of modules is at the top of the script:
# The following are either part of base perl, or available on CPAN
use File::Basename;
use File::Path;
use IO::Uncompress::Gunzip;
use Locale::Country;
use LWP::Simple;
use NetAddr::IP;
use Net::Domain;
use Parse::Syslog;
use POSIX;
use Text::ParseWords;
This has been tested on perl 5.8.8 and 5.10.1 under fedora. CPAN is http://search.cpan.org, normally accessed via the cpan command.
Depending on your distribution, you may need to adjust some of the paths listed in the “local configuration” section.
Don’t forget to install the config file – especially if you setup to run at startup.
For SYSV-init under fedora, put the script under /etc/init.d, and run chkconfig (or system-config-services) to get it run at startup/shutdown. And install the cron job as noted above.
Check your permissions – and if you are running under selinux, look for audit errors.
Note that you can run the script from a terminal if you like.
iptables -nvL should show the filter chains – the output is typically large, so pipe it thru less or send it to a file.
For more specific assistance, you’ll have to provide the specific error(s) that you are experiencing and details of your configuration. “Not running successfully” is not much to go on.
The scripted version runs on most standard linux installs – the reason why I espouse scripting in init files. It needs wget, egrep, and iptables, which can be installed through your distribution specific software installer. I’ll post an init compatible version of the script version to this thread within a few weeks.
Hello, I wan to block all and permit just Argenitna.
How can i change de script to bloclk all and permit one or two countrys?
Thanks!
>>How can i change de script to bloclk all and permit one or two countrys?
Getting that right would be a non-trivial change, since the syntax/architecture is designed around blocking. You would still want to allow some ports/ips (notably your internal addresses), but would need more syntax to over-ride (block) the “allowed” IPs when you find errors in the database. You need to consider the logging/reporting functions.
It could be done, but it’s not as simple as it appears.
Computes are cheap. You can do this instead:
Put your configuration in /etc/sysconfig/BlockCountries.template, but omit the country codes.
In your cron job, run a script something like this:
# Untested - cp /etc/sysconfig/BlockCountries.template /etc/sysconfig/BlockCountries.new # # Allow Argentina and Spain # Note that there are significant spaces in all of the regexps /etc/init.d/BlockCountries list | sed -e'/^[^ ]/d' -e's/ - .*/ /' | grep -vP ' (ar|es) ' >>/etc/sysconfig/BlockCountries.new # Make sure we got a new list - if server fails, do not run with an empty blocking list. if ! diff -q /etc/sysconfig/BlockCountries.template /etc/sysconfig/BlockCountries.new ; then echo "Failed to generate new blocked countries list, using previous configuration" else mv /etc/sysconfig/BlockCountries.new /etc/sysconfig/BlockCountries fi /etc/init.d/BlockCountries start -updateThis automatically generates a configuration file that will block all countires that IPDNEY knows about, except the ones in the regexp. (If there’s a country that IPDENY doesn’t know about, it will not be blocked, which may or may not matter to you.) It is not very efficient for the computer, but it is for you. The generated filter should be reasonably efficient because many of the IP ranges will be coallesced.
You should run the cron script by hand before running BlockCountries for the first time.
If you want a solution that directly implements your request, you’ll need to re-engineer and maintain the script. And share it with everyone else…
I’d try the simple approach before undertaking a re-engineering effort.
Changing the script to block all traffic by default and allow a country or two through is not so difficult. Change the default input rule on the internet interface to deny and change the ‘drop’ actions to ‘return’. The problem with this approach is that any ipranges for the allowed country that are missing in the ipdeny database will not be allowed either (I mention this in an earlier discussion around this point).
tlhackque’s approach above addresses the missing iprange problem, but incurs an enormous processing penalty on allowed packets. Unless you have a fairly beefy firewall machine dedicated only to filtering, you should not follow that approach.
I won’t get into a religous war – scripts are fine. I write lots of them.
But you are almost certainly over-stating the drawbacks of my note.
1) Perl is univerally available – about as available as bash. Yes, you need to fetch modules – which are just as available as commands.
2) Perl does processing that you can’t do in a script,. In this case, IP subnet colsolidation (merging).
3) As I noted, the subnet merging will reduce the number of rules actually generated – the huge number of ranges will help because of more chances to merge blocks. The “enormous processing penalty” and “beefy machine” requirements are likely less than you think. But as I noted, it’s worth running the experiment. If measured data shows that for your environment, your time is worth less than the cost of computes, by all means invest in doing the engineering. Inverting the sense of the rules would be more machine-efficient. But do the whole job. And that mean adding a mode, valdidating it, and handling the corner cases.
Note that changing the INPUT policy to deny and swapping drop for return would only work if these are the ONLY firewall rules on the INPUT chain. That’s certainly not the case on any machine that I run. Country blocking is merely one (rather heavy-handed) filter in a series of rules. So if you go down that path, you have to consider what may come after the country blocking.
4) The engineering is not rocket science. I have no need for “permit except” functionality – and doing it right is non-trivial – so I’m not implementing it. I hope that anyone who does undertakes the whole job – and that it’s actually worthwhile based on measured data.
Have fun!
Didn’t see mention of the merge in the release notes when the perl version was posted – nice touch and certainly a good argument for going with the Perl version vs the shell version if you’re blocking a lot of countries.
I don’t follow point 3 – if this is about cost accounting on development effort to make the changes to the posted Perl script, then it’s certainly worthwhile to invest the effort if you’re risking loss of access under high traffic conditions. These types of tools introduce business risk that should be considered when deploying them as mentioned earlier in these threads.
At this point I think that comment 4 highlights the need for people to post back comments on how system performance is impacted at various traffic levels for different country blocks in the configuration so we can have some hard data on where the problems might exist with any of the versions posted in this thread – so non-speculative comments with concrete measurements on performance are certainly welcome.
David,
See the help text for documentation:
“This version of the script merges all the IP address blocks; this saves over 1,000
rules for the default banned address list. It’s also somewhat faster than a shell
script, and contains a more complete and polished user and system interface.”
It turns out that even if you’re only blocking a single country, the distributed rulesets benefit from compaction. For example, my current ru.zone file shrinks ~12%; cn.zone shrinks by~45%. I provided the IPDENY folks with a simple perl script that does a better job (and statistics) – but they haven’t gotten around to implementing it.
You could incorporate the compression script into your shell script – if you do, note that you do NOT want to compress one file at a time; you want to merge adjacent blocks from different countries. But I think that the perl script is a more complete solution.
compressiplist
pipe data to stdin, or list files on command line. output to stdout
if you use -v, you’ll get statistics on stderr
#!/usr/bin/perl use strict; use warnings; use NetAddr::IP; my $stats; if( $ARGV[0] eq '-v' ) { shift; $stats = 1; } my @addresses = (); while( ) { chomp; s/\s*#.*$//; next if( !length ); push @addresses, NetAddr::IP->new( $_ ); } exit unless( @addresses ); my $inn = (scalar @addresses); print STDERR ( $inn, " addresses input, " ) if( $stats ); @addresses = NetAddr::IP::Compact(@addresses); if( $stats ) { print STDERR ( (scalar @addresses), " addresses output" ); printf STDERR (" Savings %f.2%%\n", 100*(1-(@addresses/$inn))); } print join( "\n", @addresses ), "\n"; exit;I didn’t do a feature-by-feature comparison. But here’s a swag (it’s been a while, so this may not be complete):
I do everything that your script does, except I don’t archive old zone files. If I ever need an old config, I have backups. I also don’t deail with the FORWARD or OUTPUT chains; I don’t think your code works because it’s looking at the source address and would need a second ruleset that looks at the destinations. I didn’t need these, so I didn’t fix these, although there are hooks.
I also:
o Never remove the rules when updating; start builds a parallel rule set, installs it & then removes the old set. This eliminates a window that you have during update.
o Never block any RFC1918 subnets – routers should never let these in from the internet, and this prevents a bad configuration or zone file from locking you out.
o Don’t output debugging messages unless enabled – prevents cron from sending mail
o zone files are uploaded with a mirror function – this ensures that files are downloaded intact (you don’t check wget status)
o If you enable logging, stats are available on what has been blocked – see “intercepts”. Note that this works out per-country statistics despite the address compaction.
o You can block by country name as well as ISO code
o Most configuration is in a separate file
o Host and protocol names are accepted as well as numerics. Hostnames with multiple addresses are handled.
o allowed ports can exceed 15, and both TCP and UDP are supported
o script can be directly used as a SYSV init script – including success/failure that’s console sensitive (matches the redhat style)
o verbose status (status -v) will provide configuration summary
o list will provide the available country list
o help will attempt to be helpful.
o Code is more careful about checking for and handling errors.
The compaction happens in several places, but see the comment:
# Compact the blocks into the minimal covering set
You’ll also notice that the rules are sorted to drop the largest blocks first.
If you add -v to start, you’ll get some statistics.
I’m currently blocking 13 countries, and get this:
9 exceptions generated 5 rules.
9373 blocked address ranges generated 7184 rules, using 96 sub-chains. Savings: 2189 rules (23.35%). Minimum chain length: 1, Maximum: 976
That maximum chain length for my configuration has grown – perhaps to the point where another level of chain forking is in order. But note that the more countries one blocks, the more likely merges will happen. So until someone runs the experiment, we won’t know what happens with the inverted (“everything but”) block list. (It’s too bad that iptables/netfilter doesn’t provide a hash mechanism…)
As noted in the credits, a number of the ideas in my implementation came from your work. I’m glad that what you created met your needs and that it was shared to provide a basis for my re-interpretation.
I think that if you try the perl version, you’ll find that the address consolidation will reduce the number of rules that you have to deal with. For that reason, aside from the inverted rule discussion, it should perform better. But as always, real data would be interesting.
I don’t run a high-traffic site, so my analysis has been static.
The perl version works for me. If it helps others, or if others find it worthwhile to evolve it further, that’s great. If not, the shell script still works.
Er, typo in posting the compression script
printf STDERR (" Savings %f.2%%\n", 100*(1-(@addresses/
should be
printf STDERR (" Savings %.2f%%\n", 100*(1-(@addresses/
This effects display, not the statistics I quoted.
Guys, I think you are wonderful, honest.
We´ve been trying all day with David´s bash script (great, man !). We do have a lot of traffic on our servers, and I confess we are being attacked by a ZeuS-like net-bot army. Not much sleeping here, and I still trying to block all countries (but Argentina) with the script never getting to finish adding all countries iptables rules. Later I would allow a few specific IPs from different countries. As I cannot figured to completely finish the script out, and my Perl knowledge is zero, I came back to you folks. Any chance to any of you to consolidate IP ranges for my need with the perl script (all countries except ar, and subnet 192.168.1.0 of course? No doubt will share all performance, stats, problems to anyone interested. Again thank you very much.
I hate to see anyone suffering a DDOS attack.
Here is a new version of the perl script. It has received only very limited testing, but I hope it will help your defense.
You don’t need programming knowledge of Perl to install/use this script, but you do need to know how to install CPAN modules. Some distributions provide many of the modules under yum, apt-get, etc. If yours doesn’t – or doesn’t have one that this needs, use cpan. There should be a cpan command installed with Perl. Just say cpan to the shell, then install modulename (like IO::Uncompress::Gunzip) and it should just work. ‘quit’ exits cpan. To find which modules are missing, run the script (e.g. /etc/init.d/BlockCountries help). If you get an error like “Can’t locate IO/Uncompress/Gunzip.pm in @INC….BEGIN failed–compilation aborted”, you need “IO::Uncompress::Gunzip” – you changed ‘/’ to “::” and drop the “.pm” to get the module name. Repeat for each missing module until you see the help display.
Don’t blindly install all the modules in the list, because if your distribution mechanism supplied a module, you want the distribution to handle updates.
See my post #65 for additional instructions and the sample configuration file.
Good luck!
Changes:
-permitonly inverts the country list – that is, listed countries are permitted, all others are denied.
-limit will limit the logging rate (see help for details) There is a default; use
-nolimit to get the old behavior
-dip will allow you to deny specific hosts/networks
The blocking chain will be inserted into the INPUT-HOOK chain if it exists, otherwise INPUT. (This allows more flexibility in when country blocking happens, particularly when multiple iptables-based tools all think they want to be ‘first’.)
I don’t know why the previous code post was htmll-formatted – I put it it in “code” tags, but it seems to have been reformatted anyway. View source shows HTML paragraphs and breaks. No wonder people have had trouble.
It would be helpful if this board had a way to simply attach a file to a post.
Meantime, you can find the unmangled source at http://pastebin.com/guudutHH
Further testing found a statistics display nit when -dip is used with start -v.
Fixed version is http://pastebin.com/Q3jjE22h
There is no functional impact – it only impacts the statistics display.
Feel free to report any other divots, bugs – and successes!
The bash script was never intended to block more than a few countries – you’re better off using the Perl script from tlhackque to reduce the number of rules added to the firewall, but even then I suspect you’ll have a lot of rules in the table.
The bash script shouldn’t take so long to load. The latest version of the bash script can be downloaded at http://www.psind.com/products/iptables-cblock.tgz – the shell script posted initially by Vivek above takes a very long time to load, which is why I posted the optimized bash script some time ago.
I’ve modified the bash script to function as an init script and have made it available at http://www.psind.com/products/iptables-cblock-init.tgz . tlhackque has noted a few unfinished or unaddressed features in the script and addressed them in his Perl version above. Some of these have been addressed as part of the init enablement in this version of the shell script, while others may be addressed in future versions.
I don’t believe either version defends against outbound communication by trojans, which was part of the reasoning behind including the block against the FORWARD and OUTPUT chain – although the rules there are unfinished. The other reason for having the FORWARD and OUTPUT rules is to block traffic forwarded from another interface on a gateway machine, although the rules currently do not address this risk completely.
Subscribe to this thread to receive an update when the outbound traffic restrictions are added to the script as an optional feature.
Because it was raining…
BlockCountries V1.3 is available at http://pastebin.com/cstR7Bve
CAUTION: If you are running an earlier version, you must stop blocking with that version, and restart with the new one. (And vice-versa should you want to go back.)
This is because the generated rules have a new, incompatible format, preventing either version from cleaning up the other’s rulesets.
This version adds the -blockout flag to start and stop. If you use this flag, I strongly recomend that you put it in the config file, not on a command line.
This will generate rules for blocking output & forwarding – with all the usual exceptions.
It will roughly double your actual rule count and memory requirements. (-v statistics won’t reflect this.) The output rulesets are identical to the input rules, except that they look at destination ports and addresses instead of source ports and addresses.
As David notes, it may be useful if you are infested with trojans or other spyware – but you really, really want to avoid that!
I would expect malware to evade this fairly easily.
In any case, if you want it, it’s there.
Feedback welcome. But please don’t wish for me to have more bad weather.
as I have been following this for some time and me myself been asking before for a reverse script, thus allowing only one country, it should be taken into account that certain important IP’s (Google and other search engines) should have access to your server because otherwise all sites can and wil not be indexed, which will get rid of about 70-90% of your visitors….I was asking this before for a shoutcast server. Because of rights payments only countries where broadcasting rights are paid should be able to reach your shoutcast servers, because otherwise you are worldwide accountable by every country for users who can listen
The perl version supports the ‘allow just one (or a few) countries’ with -permitonly.
This is a sharp tool; as you point out, if you mis-use it, you will cut yourself.
It’s up to you to provide overrides for services from other countries that you care about. -aip will allow you to allow services by host or aubnet.
Perl version 1.4 is available at http://pastebin.com/v1qeQED7. (Would you believe, it’s snowing?)
Improvements to -blockout as follows:
1) Output rules match on the destination port (as do input).
2) Output port overrides are now distinct from input port overrides. This is because you may want to allow, for example, http service from a banned country while blocking mail. But a trojan is likely to talk http outbound. So you don’t want to allow connections TO http in banned countries. Of course, if you also want to visit a website in an otherwise-banned country – your’re out of luck. But that just illustrates how difficult the trojan problem is. As I wrote earlier, you really want to avoid being infested in the first place. A better solution is non-country-specific deep packet inspection – for which there are other, more sophisticated engines.
-atporto and -auporto correspond to -atport and -auport for output rules.
Enjoy.
For those who are not familiar with installing perl scripts, I have provided an installation aid (bcinstall) at http://pastebin.com/5B03KbBH
This script will determine if perl is installed on your system and will see whether all the required library modules are installed.
Simply download the script, make it executable, and run it. (No parameters are required.)
If it says “All needed perl modules are installed”, block countries should run (assuming that iptables is installed.)
If it says that you need to install perl, it should be packaged for your distribution (apt-get, yum, etc.) If you can’t find a current perl version for your distribution (which would be very, very surprising) – you can find it at http://www.perl.org.
If it says that you need to install a perl module, here is how to do this:
Some distributions provide many of the modules under yum, apt-get, etc. If yours doesn’t – or doesn’t have one that this needs, use cpan. There should be a cpan command installed with Perl. Just say cpan to the shell, then install modulename (like IO::Uncompress::Gunzip) and it should just work. ‘quit’ exits cpan.
Install the module, then run bcinstall again. Repeat until bcinstall reports that you have all the needed modules.
I hope that this helps.
@tlhackque / David,
The faq has been updated with direct links. Thanks for sharing your code!
Am I the only one that have noticed that the OUTPUT rule is not needed at all?
If you can’t receive from that country, you will never send an answer to that country.
But if you still need it (don’t know why), the rules are inverted: must be -d (destiny) instead of -s (source).
Great post, David.
Not exactly. These rules apply when a connection is initiated. Once a connection is established, data can flow in both directions. The INPUT rules block incoming connections. The OUTPUT rules block outgoing connections. And the FORWARD rules block packets forwarded from one interface to another.
The INPUT rules suffice for most circumstances.
As David pointed out, if machines on your internal network are infected – for example with a keystroke logger, they will try to make an outbound connection to deliver their stolen data to the criminals. Such infections can come from many sources – USB keys, websites in an unblocked country, “free” software downloads – and more. Or, one may even have humans who try to transfer data to countries in violation of export or business rules. (Accidentally, of course.)
The OUTPUT rules limit the countries to which malware can connect. We can argue about how effective this is (see my previous comments), but it can have some effect. If someone believes that it’s worthwhile to implement this blocking, it is technically possible.
The perl code optionally generates the necessary output (and forward) rules. And yes, it is smart enough (as I have mentioned) to apply destination filtering for outbound rules and source filtering for input rules.
As he notes, the current version of David’s code doesn’t do this – output and forward rules are “unfinished”.
As always, when choosing a tool it is important to evaluate the capabilities and limitations of each alternative.
David’s script is easier to install and provides an archiving function for previously-used zone blocking files. The perl script produces more efficient rules, supports output blocking and has a variety of other capabilities as noted in prevoius posts.
Whether to block at all, and what technology to use if you do, is a choice that each system administrator, in conjunction with business management, should make carefully.
Hi PacoSS – to answer your question on the OUTPUT block, we allow responses on established sessions so that BluRay players on those home networks can establish connections out to Samsung in Korea to get the flash updates and continue playing those wonderful BluRay discs as well as suporting requests for other updates / drivers to web sites hosted in blocked countries.
An OUTPUT block eliminates requests on unspecified ports which may be used by trojans to communicate key logging results out on IRC chat or some other network protocol. Admitted, they could use port 80 or 443 (which you should leave unblocked to get those hardware drivers), but use of a proxy HTTP server for all outbound requests can give you better control of traffic out on port 80 and 443 and provide a different mechanism to block outbound traffic from trojans, especially if you use an authenticated proxy server. I block port 80 and 443 on my FORWARD chain for traffic destined outside of my local network, forcing all users to go through the proxy server.
But the iptables parameter -s appy to the source of the packet.
So the OUTPUT chain must use -d if you want to block any packet to get to the desired blacklisted country.
The same in the forward rule if the server is working as a firewall.
Kind regards.
You are correct that input rules must use -s and output rules -d.
There are two implementations of country blocking discussed here.
I’ll let David speak for himself on the shell script version.
The Perl script version 1.4+ provides output blocking. It uses -d for the output and forward rules and -s for the input rules. If you want this function, try it and see. See posts 87 & 88 for where to get it – the current source is NOT in this thread due to formatting issues.
As PacoSS has noted, the OUTPUT rules in the script version I provide are not yet properly formed to block on the destination – I still need to create a separate chain for those. As an inbound FORWARD rule is highly unlikely on most intranets using the private IP space, the FORWARD rule should also likely use the -d option for the blocked addresses. It’s on the roadmap to make the changes, but I’m not making firewall changes while I’m out of the office so probably will not get to it until this weekend or next weekend ;-)
I also want to take a little more time to think through how a FORWARD rule might be required in a non-redundant fashion as attack vectors are generally not addressed in both a simple and complete fashion.
Thx for this great project!
I have some problems with EU geoip zones.
When i do update trought perl script i receive this error.
“Unrecognized country/country code: eu”
I have tried to update locale::country module but i have the last version!
How can i fix it?
EU is continent. You need to set country names in EU such as UK, DE (Germany), ES (Spain) etc. See http://www.iso.org/iso/country_codes.htm
OK but in more geoip site i see this ip class http://www.ipdeny.com/ipblocks/data/countries/eu.zone
what is?
Thx for your fast reply!
I’ve not tested this but I guess you need to make some modification to source code. The original code is designed for country only blocking and not for continent. I hope you’ve modified $BLOCKURL or configfile as documented by Timothe.
In case anyone is interested, I have written the IPset version of this article. It was requested by a visitor on my site.
The article can be found here.
I’m sorry if this appears as SPAM to someone. @Vivek, please remove this comment if you consider this as SPAM.
I’ve finally got my blog back up and running which outlines some of the business implications of using this script and also have the script available on my site again for downloading from here for those who still prefer the shell script.
Hi Guys
Thank you very much for this usefull (albeit long!) blog.
Just to let you know: As the last IP ranges have now been handed out there surely is no need to schedule this as a CRON job? A single run of the perl/sh script should be sufficient to generate the iptables stuff and allow it to be copied to internet facing servers.
Personally I plan to single out all US ips and route them through my US proxy, so that I’m able to stream both domestic (danish, europe) tv and Hulu/Netflix etc.. It’ll be fun to inject these settings into the router.
It’s a linux based DD-WRT firmware so it *should* work (with a bit of re-engineering – personally I’m a powershell guy not perl, but we’ll see)…
I have installed the perl script BlockCountries, have it running as a service and see the /root/blockips directory populated with zones for countries I have added in /etc/sysconfig/BlockCountries but do not see any added rules in iptables. Is there something I need to do to get BlockCountries to add rules to iptables ?
Never mind iptables -L is showing the new chains. Thanks for the awesome script!!
Is there any way to add some custom ranges that are not one country specific so they are handled by the script? Can I makes something like a custom zone in /root/blockips?
Nice scripts,
Another question, How do I block all country and only allow my country as allowed range of Ip’s.. adding to much list of country will not be a good practice right?
Hi guys. Thanks for the scripts. Quite a long read. Be nice having a summary of features, usage somewhere?
My understanding is if using proper chaining in ip tables having a large number of rules isn’t really an issue. Seems ever so slightly slower than ip set?
(Only benchmark I could find. Last couple charts on page 13. http://people.netfilter.org/kadlec/nftest.pdf)
Also found this script by BoneKracker.
http://forums.gentoo.org/viewtopic-t-863121-start-0.html
Any downside to using this over the two scripts found here other than needing ip sec installed – (which doesn’t seem like an issue).
Guessing BoneKrackers script is first choice then tlhackque’s perl script then David’s ip tables (chain) bash script…
Also nobody seems to be discussing the accuracy of IP Deny. What’s the quality of data like – no mention of it even on their site? How does it compare to MaxMind’s data?
line 53: cn Country Drop: command not found
iptables v1.3.5: Unknown arg `–log-prefix’
i dont know, i see more and more this message, pls help me
line 53: done
Hi guys,
Is it possible to make a whitelist-Script out of the Script?
I know this post is a bit old and someone mentioned this before:
I haven’t analyzed the script yet, but is it hard to change it?
Thanks.
I’ve mentioned that a whitelist script is not necessarily the best idea as there could be an issue with the IP mappings where some groups are overly broad. In addition, a whitelist would need to include the private networks like 10.0.0.0/8 and 192.168.0.0/16. In addition, there are services like DLNA (video and audio streaming within the home) that use broadcast addresses for discovery that would likely be blocked unintentionally with a white list approach.
If your dead set on trying a white list, set the default policy on the interface to DENY and change all instances of DENY or DROP in the script to ACCEPT – that would theoretically create the white list. If you want to accept traffic from inside your own network or allow broadcast discovery of devices through DLNA, etc… you will have a lot more work to do.
did you ever get this sorted ? I only just read it – if not I have an automatic updating whitelist script that works off a MySQL database, some bash, and some PHP. It runs the script via crontab and produces a script that configures iptables to block everything and then lets the Countries that are selected on the database through. It also concatenates the ranges to minimize RAM usage. If you want a copy let me know.
Thanks for sharing. I just installed the script for my server. Hopefully the those f*kin bots and scanners from china are no longer able to connect. Is it correct they are looking for vulnerable config within php and apache?
cheers
Thanks for this guys! Man I was tired of all the hack attempts. I am using fail2ban, and it’s working pretty good, but this helps clean up the rest of it. Thanks again!
Hi, very useful, my compliments.
I just addedd a counter during loop and a final echo with total rules created.
Thank you it’s a very good script.
Hi guys,
Nice scripts.I was going to use apache level restrictions but this works very well indeed. The only this is i am block a large number of countries but not necessarily for security purposes. Mostly this the what the project requires. But i don’t want potential customers (we will be opening it up to international later in the year) to get a ‘No connectivity’ message.
Ideally i’d like to redirect (forward) all those blocked incoming IP’s to another IP (which will have a single static page saying “thanks for your interest. at this time we are..etc..”). This second IP is on a different machine.
I’ve had a look around but i’m not sure if IPtables can do this for me. Most scripts i’ve seen can forward ALL port 80 packets to another IP but i only want the blocked IP’s forwarded.
Any suggestions would be welcomed.
cheers
Alan – this can easily be done with iptables – but the rules must be applied on the nat table in the PREROUTING chain. Instead of jumping to the DROP rule simply create another chain – perhaps DROPXHTTP – and replace the jump to drop with the new chain. The DROPXHTTP chain should redirect if the packet is on port 80 and jump to the DROP chain otherwise.
Thanks David,
Managed to get that working after i figured out to enable my IP_forwarding in sysctl.conf.
IMO this script is more than bad idea for 2 reasons: You
1) delete all previous rules in IPtables instead of leaving them there. I don’t see any reason why they should be deleted especially when you’re not gonna put them back after adding the addresses.
2) append all those addresses to the main table instead of making separate table for either all target countries or per country and then calling that table somewhere amongst the rules. If it’s true that alot of rules in single table is performance sink, then table per country or some other multitable solution.
Jouni -
You must be looking at a different script – the script I posted in the follow-up does not delete all the previously created rules. Also appending everything to the main table can adversely impact performance – we create the table groupings to reduce the number of rules that need to be checked as each packet comes in – I’m pretty sure all this was covered in the topic thread above if not in my blog posting on my site.
Yeah, I looked at the script on the top of this article.