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 # Purpose: Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code. # # See url for more info - http://www.cyberciti.biz/faq/?p=3402 # Author: nixCraft <www.cyberciti.biz> under GPL v.2.0+ # ------------------------------------------------------------------------------- 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
- An updated version of older Perl script written in bash by Timothe Litt.
- Perl Script by Timothe Litt.(outdated and not recommended)




180 comment
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
V
P
N
?
@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:
Forgot 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:
#!/bin/bash # Originally from https://www.cyberciti.biz/faq/block-entier-country-using-iptables ### Block all traffic from AFGHANISTAN (af) and CHINA (CN). Use ISO code ### ISO="af cn kr" ### 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" IPTCBDEVICE=eth0 ALLOWPORTS=80,443 ALLOWSUBNET=192.168.0.0/255.255.0.0 MAXZONEAGE=7 DLROOT="http://www.ipdeny.com/ipblocks/data/countries" cleanOldRules(){ $IPT -L $CBLIST > /dev/null 2>&1 if [ $? = 0 ] ; then $IPT -D INPUT ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST $IPT -D OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST $IPT -D FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST fi $IPT -F $CBLIST $IPT -X $CBLIST for i in `$IPT -L -n | grep Chain | cut -f 2 -d ' ' | grep '\-$CBLIST'` do $IPT -F ${i} $IPT -X ${i} done } 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 ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST\n" "-I" > ${IPTCBRESTORE}.tmp printf "%s OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST\n" "-I" >> ${IPTCBRESTORE}.tmp printf "%s FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -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 if [ "Z${ALLOWSUBNET}" = "Z" ] ; then printf "Blocking all traffic from country - no subnets excluded\n" else printf "%s $CBLIST -s ${ALLOWSUBNET} -j RETURN\n" "-I">> ${IPTCBRESTORE}.tmp fi for c in $ISO do # local zone file tDB=$ZONEROOT/$c.zone # country specific log message SPAMDROPMSG="iptables: ${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 ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -j $CBLIST $IPT -I OUTPUT ${IPTCBDEVICE:+-o }${IPTCBDEVICE} -j $CBLIST $IPT -I FORWARD ${IPTCBDEVICE:+-i }${IPTCBDEVICE} -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 if [ "Z${ALLOWSUBNET}" = "Z" ] ; then printf "Blocking all traffic from country - no subnets allowed\n" else $IPT -I $CBLIST -s ${ALLOWSUBNET} -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 printf "Country block instituted for: %s\n" "$ISO" } # 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 0Would 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
#!/usr/bin/perl # # BlockCountries Block IP traffic from specified countries # # chkconfig: 2345 10 92 # description: Blocks IP traffic from IP addresses assigned to specific countries # use strict; use warnings; # Copyright (c) 2010 Timothe Litt, litt__at__acm_dot_org # All rights reserved. # # This software is licensed under the terms of the Perl # Artistic License (see http://dev.perl.org/licenses/artistic.html). # # This is free software - it works for me, and it may (or may not) # work for you. No warranty or support is provided. # # Consider carefully whether you want to use this software # and the full consequences to your site and/or business. # # This is written as a technical means to assist in implementing # your policy. The author expressly disclaims any responsibility # for the consequences of using this software. ### Block all traffic from specified countries. ### # # See Usage() for documentation # # List of country codes - specify yours in the config file my @DEFAULT_ISO = qw /cn kr kp kz ru/; # Local configuration my $IPT = '/sbin/iptables'; my $IPTR = '/sbin/iptables-restore'; my $GREP = '/bin/grep'; my $CFGFILE = '/etc/sysconfig/BlockCountries'; my $ZONEDIR = '/root/blockips'; my $ZONETBL = "$ZONEDIR/tables.ipt"; my $BLOCKURL = 'http://www.ipdeny.com/ipblocks/data/countries'; my $LOGPFX = '[Blocked CC]: '; my $LOG = '/var/log/messages*'; # Note: This is a wildcard to handle log rotation. .gz files will decompressed on the fly and processed. my $LOGPGM = 'kernel'; # ### End of configuration # 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; umask 0137; my $prog = basename $0; # new, old my @IPCHAINS = ( 'BLOCKCC0', 'BLOCKCC1' ); @IPCHAINS = reverse( @IPCHAINS ) if( system( "$IPT -n -L $IPCHAINS[0] >/dev/null 2>&1" ) == 0 ); my $IPNEWCHAIN = $IPCHAINS[0]; my $IPOLDCHAIN = $IPCHAINS[1]; if( -e $CFGFILE ) { open( my $fh, '<', $CFGFILE ) or die( "Can't open $CFGFILE:$!" ); while( ) { s/\s*#.*$//; s/^\s+//; s/\s+$//; next unless length; push @ARGV, parse_line( '\s+', 0, $_ ); } close $fh; } my $cmd = shift; # Collect all arguments here, even though they are mostly for start # This allows detailed status my( $debug, $verbose, @iso, %iso, $update, $log, $days, $host, @atports, @auports, @aips ); while( (my $arg = shift) ) { if( $arg =~ /^-/ ) { if( $arg eq '-update' ) { $update = 1; next; } if( $arg eq '-log' ) { $log = 1; next; } if( $arg eq '-nolog' ) { $log = 0; next; } if( $arg eq '-d' ) { $debug = 1; next; } if( $arg eq '-v' ) { $verbose = 1; next; } if( $arg eq '-days' && $ARGV[0] && $ARGV[0] =~ /^\d+$/ ){ $days = shift; next; } if( $arg eq '-host' && $ARGV[0] ){ $host = shift; next; } if( $arg eq '-atport' && $ARGV[0] ){ $arg = shift; if( $arg =~ /^(?:\d+)$/ ) { push @atports, $arg; next; } my $val = getservbyname( $arg, 'tcp' ); if( defined $val ) { push @atports, $val; next; } print "Invalid port $arg\n"; exit 1; } if( $arg eq '-auport' && $ARGV[0] ){ $arg = shift; if( $arg =~ /^(?:\d+)$/ ) { push @auports, $arg; next; } my $val = getservbyname( $arg, 'udp' ); if( defined $val ) { push @auports, $val; next; } print "Invalid port $arg\n"; exit 1; } if( $arg eq '-aip' && $ARGV[0] ) { if( $ARGV[0] =~ /^\d{1,3}(?:\.\d{1,3}){0,3}(?:\/(?:\d+|(?:\d{1,3}(?:\.\d{1,3}){3})))?$/ ){ push @aips, NetAddr::IP->new( shift ); next; } my @h = gethostbyname( $ARGV[0] ); unless( @h) { print "Unknown host $ARGV[0]\n"; exit 1; } unless( $h[3] == 4 && $#h >= 4 ) { print "$ARGV[0] : not an IPV4 address\n"; exit 1; } for my $a (@h[4..$#h]) { push @aips, NetAddr::IP->new( sprintf( "%vd", $a ) ); } shift; next; } if( $arg eq '-h' || $arg eq '--help' ) { Usage(); exit 0; } print "Unknown switch $arg"; print ' ', $ARGV[0] if( defined $ARGV[0] ); print "\n"; exit 1; } if( defined code2country( $arg ) ) { $iso{lc $arg} = 1; } else { my $cc = country2code( $arg ); if( defined $cc ) { $iso{lc $cc} = 1; } else { print "Unrecognized country/country code: $arg\n"; exit 1; } } } @iso = sort keys %iso; @iso = @DEFAULT_ISO unless( @iso ); sub Usage { print << "HELP"; IP filter manager for country filters Usage: $prog command args status [-v] Display filter status list List available country names/codes Contacts server for list. intercepts [-host name] [-days n] List today's intercepts by host (from $LOG) stop Stop filtering restart args Synonym for start (reloads with no open window) condrestart args Restarts only if already running start args Starts filter Start uses tables of IP blocks assigned to country codes that are stored in $ZONEDIR, which will be created if necessary. The data is obtained from $BLOCKURL when needed, or when start -update is specified. iptables filters are generated and installed by start. The filters are optimized and generally will not look identical to the input data. However they will match the same address (no more and no fewer.) Arguments for start-class commands are: -update Get latest data for active country codes. Otherwise, only gets data if no local file exists for a CC. -log Install a logging rule to log rejected packets. -nolog Don't install a logging rule (default) -atport n Allow connections to TCP port n even from banned addresses. May specify any number of times. May use a service name. -auport n Allow connections to UDP port n even from banned addresses. May specify any number of times. May use a service name. -aip ip(/mask) Allow connection from an otherwise banned IP address. For a block, specify a netlength or mask. A hostname may also be specified. -d Output random debugging messages -v Output extended status/statistics CC ISO Country code or name to ban (as many as you like) Default list: HELP for my $cc (sort @DEFAULT_ISO ) { print " $cc - ", code2country($cc), "\n"; } print <source/dev/null`); # Delete each one # First, delete the rule in the main chain that reads '-s 1st octet, goto subchain' # Then empty and delete the subchain for my $schain (@schains) { $schain =~ m/-(\d+)$/; system( "$IPT -D $chain -s $1.0.0.0/8 -g $schain" ); system( "$IPT -F $schain" ); system( "$IPT -X $schain" ); } } sub delchainref { my $main = shift; # e.g. INPUT my $chain = shift; # e.g. BLOCKchain my @crefs = map { (m/^$chain\s/ ? ( $chain, ) : ()) } split( /\n/, `$IPT -n -L $main 2>/dev/null`); for my $cref (@crefs) { # should be only one system( "$IPT -D $main -j $cref" ); } } # Sort function for IP addresses for installation into filter chains # Th whole chain must be processed if we miss, so there's nothing we can do. # But on a hit, we can improve the expected time somewhat by checking the # largest blocks first. This corresponds to the smallest mask length. # It is possible to do better if the traffic pattern is known, but there # isn't a good way (short of active feedback) to determine it. # In any case, we reduce the search length by hashing on the first # octet of the address, so this is a secondary effect. sub ipcmp { my $x = $a->masklen $b->masklen; return $x if( $x ); return $a $b; } sub start { print "Starting blocked countries IP filter: " unless( $ENV{CRONJOB} ); File::Path::make_path( $ZONEDIR, { mode => 0771 } ) unless( -d $ZONEDIR ); # Delete any lingering references / parts of new chain delchainref( 'INPUT', $IPNEWCHAIN ); # Perhaps someday if I want to spend core on -d rules # delchainref( 'OUTPUT', $IPNEWCHAIN ); # delchainref( 'FORWARD', $IPNEWCHAIN ); delsubchains( $IPNEWCHAIN ); system( "$IPT -F $IPNEWCHAIN >/dev/null 2>&1" ); system( "$IPT -X $IPNEWCHAIN >/dev/null 2>&1" ); # (Log & ) drop chain system( "$IPT -F $IPNEWCHAIN-DLOG >/dev/null 2>&1" ); system( "$IPT -X $IPNEWCHAIN-DLOG >/dev/null 2>&1" ); return failure unless( system( "$IPT -N $IPNEWCHAIN-DLOG" ) == 0 ); if( $log ) { # Note that we can not provide a per-country log prefix due to compaction. # However, the intercepts report will map IPs back to their (alleged) country of origin # To determine what countries are causing intercepts, the logs must be post-processed to # lookup each IP. return failure unless( system( "$IPT -A $IPNEWCHAIN-DLOG -j LOG --log-prefix \"$LOGPFX\"" ) == 0 ); } return failure unless( system( "$IPT -A $IPNEWCHAIN-DLOG -j DROP" ) == 0 ); return failure unless( system( "$IPT -N $IPNEWCHAIN" ) == 0 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -m state --state RELATED,ESTABLISHED -j RETURN" ) == 0 ); # List any allowed ports - first since they have a netmask of 0 # Allowed TCP ports - no more than 15 per rule (limit of multiport) my $exceptions = @aips + @auports + @atports; my $xrules = 0; while( @atports ) { my $n = @atports; $n = 15 if( $n > 15 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -p tcp -m multiport --dports " . join( ',', @atports[0..$n-1] ) . ' -j RETURN' ) == 0 ); splice( @atports, 0, $n ); $xrules++; } # Allowed UDP ports while( @auports ) { my $n = @auports; $n = 15 if( $n > 15 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -p udp -m multiport --dports " . join( ',', @auports[0..$n-1] ) . ' -j RETURN' ) == 0 ); splice( @auports, 0, $n ); $xrules++; } # Allowed IPs (with optional masklen/netmask); largest size first # # Local subnets - external firewalls prevent them from showing up here, but # a bogus zone file could do damage. unshift @aips, NetAddr::IP->new( '192.168.0.0/16' ), NetAddr::IP->new( '172.16.0.0/12' ), NetAddr::IP->new( '10.0.0.0/8' ); $exceptions += 3; @aips = sort ipcmp NetAddr::IP::Compact( @aips ); $xrules += @aips; while( @aips ) { return failure unless( system( "$IPT -A $IPNEWCHAIN -s " . shift( @aips ) . ' -j RETURN' ) == 0 ); } if( $verbose ) { # Exception statistics print( "\n", "$exceptions exceptions generated $xrules rules." ); } # Make sure we have a zone file for each country code # Fetch a new one if -update or we don't have one # If we fetch, only transfer the file if it's different from (usu. newer than) our copy. my @files; for my $c (@iso) { my $db = "$ZONEDIR/$c.zone"; my $cn = code2country( $c ); $cn = " ($cn)" if( defined $cn ); # Fetch if updating or have no data if( $update || ! -f $db || -z $db ) { my $rc = mirror( "$BLOCKURL/$c.zone", $db ); if( is_success( $rc ) ) { print "\nUpdated IP zone data for $c$cn", if( $debug || $update || $ENV{CRONJOB} ); # Shouldn't ever get an empty file, but may as well check unless( -f $db && -s $db ) { print "\nUpdated zone data for $c$cn is empty!"; unlink $db; return failure; } } else { if( $rc == RC_NOT_MODIFIED ) { # Can only happen if file exists print "\nNo new IP data available for $c$cn " if( $debug || ($update && !$ENV{CRONJOB}) ); } else { print "\nUnable to fetch IP zone data for $c$cn: $rc - ", status_message($rc), " "; } unless( -f $db && -s $db ) { # No data - don't replace current filter print "\nNo IP zone data available for $c$cn "; if( $debug ) { next; } return failure; } # Failed, but have old file, continue since other zones may be updated } } push @files, $db; } return failure unless( @files ); # Parse the zone files and create a list of IP blocks my @addresses = (); for my $if (@files) { open( my $ifh, '<', $if ) or die( "Can't open $if: $!" ); while( ) { s/\s*#.*$//; next unless( length ); push @addresses, NetAddr::IP->new( $_ ); } close $ifh; } return failure unless( @addresses ); # Compact the blocks into the minimal covering set my $inaddrs = @addresses; @addresses = sort ipcmp NetAddr::IP::Compact(@addresses); # Generate an iptables-restore file with the new rules my %subchains; open( my $fh, '>', $ZONETBL ) or die( "Can't open $ZONETBL: $!" ); print $fh "# Generated by $prog on ", (scalar localtime), "\n", "*filter\n", # table ":INPUT ACCEPT [0:0]\n"; # built-in chain, policy, counters foreach my $ipblock (@addresses) { $ipblock =~ /^(\d+)\./; unless( $subchains{$1} ) { print $fh ":$IPNEWCHAIN-$1 - [0:0]\n", # subchain, no policy, zero counters "-A $IPNEWCHAIN -s $1.0.0.0/8 -g $IPNEWCHAIN-$1\n"; # Chain - branch on 1st octet to subchain } $subchains{$1}++; print $fh "-A $IPNEWCHAIN-$1 -s $ipblock -j $IPNEWCHAIN-DLOG\n"; # Subchain, branch on match to log & drop } print $fh "COMMIT\n", "# Completed on ", (scalar localtime), "\n"; close $fh; if( $verbose ) { # Provide some statistics, mostly for debugging. my( $minlen, $maxlen ); $minlen = $maxlen = $subchains{(keys %subchains)[0]}; for my $s (values %subchains) { $minlen = $s if( $s $maxlen ); } print( "\n", "$inaddrs blocked address ranges generated ", (scalar @addresses), " rules, using ", (scalar keys %subchains), " sub-chains. Savings: ", ($inaddrs - scalar @addresses), " rules (", sprintf( "%.2f", 100*(1- ((scalar @addresses))/$inaddrs)), "%). Minimum chain length: $minlen", ", Maximum: $maxlen\n" ); } # Install new ruleset # -- Mass-install new Chain, subchains & rules return failure unless( system( "$IPTR -n $ZONETBL" ) == 0 ); # -- Link INPUT to the new chain return failure unless( system( "$IPT -I INPUT -j $IPNEWCHAIN" ) == 0 ); # return failure unless( system( "$IPT -I OUTPUT -j $IPNEWCHAIN" ) == 0 ); # return failure unless( system( "$IPT -I FORWARD -j $IPNEWCHAIN" ) == 0 ); # Remove old rules delchainref( 'INPUT', $IPOLDCHAIN ); # delchainref( 'OUTPUT', $IPOLDCHAIN ); # delchainref( 'FORWARD', $IPOLDCHAIN ); delsubchains( $IPOLDCHAIN ); system( "$IPT -F $IPOLDCHAIN >/dev/null 2>&1" ); system( "$IPT -X $IPOLDCHAIN >/dev/null 2>&1" ); system( "$IPT -F $IPOLDCHAIN-DLOG >/dev/null 2>&1" ); system( "$IPT -X $IPOLDCHAIN-DLOG >/dev/null 2>&1" ); $IPOLDCHAIN = $IPNEWCHAIN; return success if( running ); return failure; } sub stop { return 1 if( !running ); print "Removing blocked countries IP filter"; delchainref( 'INPUT', $IPOLDCHAIN ); delsubchains( $IPOLDCHAIN ); system( "$IPT -F $IPOLDCHAIN" ); system( "$IPT -X $IPOLDCHAIN" ); system( "$IPT -F $IPOLDCHAIN-DLOG" ); system( "$IPT -X $IPOLDCHAIN-DLOG" ); if( !running ) { success; return 1; } failure; return 0; } sub restart { # Don't stop since start will keep the current table alive until # the new one is active. return start(); } # List intercepted IPs for today # This can be run in a cron job just before midnight to get a list of # IPs to report. Or, you can use -days n to get the last n days worth # of intercepts # Only works if logging is on sub intercepts { my( $fh, %ips ); $days ||= 1; my $start = time() - ( $days * 24*60*60 ); $host ||= Net::Domain::hostname(); foreach my $logfile (glob $LOG) { my $lh = IO::Uncompress::Gunzip->new( $logfile, MultiStream => 1, Transparent => 1 ); unless( $lh ) { print "Skipping system log file: $IO::Uncompress::Gunzip::GunzipError\n"; next; } my $sl = Parse::Syslog->new( $lh, arrayref => 1 ); # Record # intercepts for each ip => protocol => port while( my $l = $sl->next ) { next if( $l->[0] [2] eq $LOGPGM && $l->[1] =~ /$host/i; if( $l->[4] =~ /^\Q$LOGPFX\E.*?\bSRC=([0-9.]+).*?\bPROTO=(ICMP)\b.*?\bTYPE=(\d+)/ ) { $ips{$1}{lc $2}{$3}++; } elsif( $l->[4] =~ /^\Q$LOGPFX\E.*?\bSRC=([0-9.]+).*?\bPROTO=(\w+).*?\bDPT=(\d+)/ ) { $ips{$1}{lc $2}{$3}++; } } close $lh; } return 0 unless %ips; # List each intercepted IP, its country, the protocols, ports and number of packets for each print "Intercepts by host IP:\n"; my( %ccip, %ccn ); foreach (glob "$ZONEDIR/*.zone") { /$ZONEDIR\/(.*).zone/; my $cc = $1; next unless( defined code2country( $cc ) ); # Skip undocumented zone files open( my $ifh, '<', $_ ) or next; while( ) { s/\s*#.*$//; next unless( length ); push @{$ccip{$cc}}, NetAddr::IP->new( $_ ); } close $ifh; } for my $ip (sort map {NetAddr::IP->new($_)} keys %ips) { my $ccn; CCSEARCH: foreach my $cc (keys %ccip) { foreach my $cip (@{$ccip{$cc}}) { if( $cip->contains($ip) ) { print "$cc: "; $ccn = $cc; last CCSEARCH; } } } unless( $ccn ) { # Possible if we've stopped blocking a country but have old log entries $ccn = '??'; print '??: '; } print $ip->addr; my @plist = sort keys %{$ips{$ip->addr}}; # Protocol for my $p (@plist) { my @rlist = sort keys %{$ips{$ip->addr}{$p}}; # Ports print ' ', join( ' ', map { my $n = $ips{$ip->addr}{$p}{$_}; $ccn{$ccn} += $n; "$p-$_($n)" } @rlist ); } print "\n"; } print "Intercepts by country:\n"; for my $cc (sort {$ccn{$b} $ccn{$a} } keys %ccn) { my $cn = code2country($cc); if( defined $cn ) { $cn = "$cc ($cn)"; } else { $cn = $cc; } printf "%10u %s\n", $ccn{$cc}, $cn; } return 1; } if( $cmd eq 'start' ) { exit !start(); } if( $cmd eq 'stop' ) { exit !stop(); } if( $cmd eq 'restart' ) { exit !restart(); } if( $cmd eq 'condrestart' ) { exit !(running && restart()); } if( $cmd eq 'status' ) { exit !status(); } if( $cmd eq 'list' ) { exit !list(); } if( $cmd eq 'intercepts' ) { exit !intercepts(); } if( $cmd eq 'help' ) { Usage(); exit; } print "Usage: $prog (start|stop|restart|condrestart|status|list|intercepts|help)\n"; exit 1;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’.)
#!/usr/bin/perl # # BlockCountries Block IP traffic from specified countries # # chkconfig: 2345 10 92 # description: Blocks IP traffic from IP addresses assigned to specific countries # use strict; use warnings; # Version 1.2 # # Copyright (c) 2010 Timothe Litt, litt__at__acm_dot_org # All rights reserved. # # This software is licensed under the terms of the Perl # Artistic License (see http://dev.perl.org/licenses/artistic.html). # # This is free software - it works for me, and it may (or may not) # work for you. No warranty or support is provided. # # Consider carefully whether you want to use this software # and the full consequences to your site and/or business. # # This is written as a technical means to assist in implementing # your policy. The author expressly disclaims any responsibility # for the consequences of using this software. ### Block all traffic from specified countries. ### # # See Usage() for documentation # # List of country codes - specify yours in the config file my @DEFAULT_ISO = qw /cn kr kp kz ru/; # Local configuration my $IPT = '/sbin/iptables'; my $IPTR = '/sbin/iptables-restore'; my $GREP = '/bin/grep'; my $CFGFILE = '/etc/sysconfig/BlockCountries'; my $ZONEDIR = '/root/blockips'; my $ZONETBL = "$ZONEDIR/tables.ipt"; my $BLOCKURL = 'http://www.ipdeny.com/ipblocks/data/countries'; my $LOGPFX = '[Blocked CC]: '; my $LOG = '/var/log/messages*'; # Note: This is a wildcard to handle log rotation. .gz files will decompressed on the fly and processed. my $LOGPGM = 'kernel'; my $IHOOK = 'INPUT-HOOK'; # Note: if this table is not found, INPUT will be used #my $OHOOK = 'OUTPUT-HOOK'; # Note: if this table is not found, OUTPUT will be used #my $FHOOK = 'FORWARD-HOOK'; # Note: if this table is not found, FORWARD will be used # ### End of configuration # 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; # Changelog # 1.0 Initial development # 1.1 Add support for hook tables # 1.2 By unpopular demand, add -permitonly # Add logging rate limit # Add -dip (deny IP) umask 0137; my $prog = basename $0; # new, old my @IPCHAINS = ( 'BLOCKCC0', 'BLOCKCC1' ); @IPCHAINS = reverse( @IPCHAINS ) if( system( "$IPT -n -L $IPCHAINS[0] >/dev/null 2>&1" ) == 0 ); my $IPNEWCHAIN = $IPCHAINS[0]; my $IPOLDCHAIN = $IPCHAINS[1]; $IHOOK = 'INPUT' unless( system( "$IPT -n -L $IHOOK >/dev/null 2>&1" ) == 0 ); #$OHOOK = 'OUTPUT' unless( system( "$IPT -n -L $OHOOK >/dev/null 2>&1" ) == 0 ); #$FHOOK = 'FORWARD' unless( system( "$IPT -n -L $FHOOK >/dev/null 2>&1" ) == 0 ); if( -e $CFGFILE ) { open( my $fh, '<', $CFGFILE ) or die( "Can't open $CFGFILE:$!" ); while( ) { s/\s*#.*$//; s/^\s+//; s/\s+$//; next unless length; push @ARGV, parse_line( '\s+', 0, $_ ); } close $fh; } my $cmd = shift; # Collect all arguments here, even though they are mostly for start # This allows detailed status my( $debug, $verbose, @iso, %iso, $update, $log, $days, $host, $permitonly, @loglimits, @atports, @auports, @aips, @dips ); @loglimits = ( '1/minute', 10 ); while( (my $arg = shift) ) { if( $arg =~ /^-/ ) { if( $arg eq '-update' ) { $update = 1; next; } if( $arg eq '-log' ) { $log = 1; next; } if( $arg eq '-limit' && $ARGV[0] ) { unless( $ARGV[0] =~ m#(\d+/(?:second|minute|hour|day))(?::(\d+))?# ) { print "Syntax error in -limit\n"; exit 1; } shift; $loglimits[0] = $1; $loglimits[1] = $2 if( defined $2 ); next; } if( $arg eq '-nolimit' ) { @loglimits = (); next; } if( $arg eq '-nolog' ) { $log = 0; next; } if( $arg eq '-permitonly' ) { $permitonly = 1; next; } if( $arg eq '-d' ) { $debug = 1; next; } if( $arg eq '-v' ) { $verbose = 1; next; } if( $arg eq '-days' && $ARGV[0] && $ARGV[0] =~ /^\d+$/ ){ $days = shift; next; } if( $arg eq '-host' && $ARGV[0] ){ $host = shift; next; } if( $arg eq '-atport' && $ARGV[0] ){ $arg = shift; if( $arg =~ /^(?:\d+)$/ ) { push @atports, $arg; next; } my $val = getservbyname( $arg, 'tcp' ); if( defined $val ) { push @atports, $val; next; } print "Invalid port $arg\n"; exit 1; } if( $arg eq '-auport' && $ARGV[0] ){ $arg = shift; if( $arg =~ /^(?:\d+)$/ ) { push @auports, $arg; next; } my $val = getservbyname( $arg, 'udp' ); if( defined $val ) { push @auports, $val; next; } print "Invalid port $arg\n"; exit 1; } if( $arg eq '-aip' && $ARGV[0] ) { if( $ARGV[0] =~ /^\d{1,3}(?:\.\d{1,3}){0,3}(?:\/(?:\d+|(?:\d{1,3}(?:\.\d{1,3}){3})))?$/ ){ push @aips, NetAddr::IP->new( shift ); next; } my @h = gethostbyname( $ARGV[0] ); unless( @h) { print "Unknown host $ARGV[0]\n"; exit 1; } unless( $h[3] == 4 && $#h >= 4 ) { print "$ARGV[0] : not an IPV4 address\n"; exit 1; } for my $a (@h[4..$#h]) { push @aips, NetAddr::IP->new( sprintf( "%vd", $a ) ); } shift; next; } if( $arg eq '-dip' && $ARGV[0] ) { if( $ARGV[0] =~ /^\d{1,3}(?:\.\d{1,3}){0,3}(?:\/(?:\d+|(?:\d{1,3}(?:\.\d{1,3}){3})))?$/ ){ push @dips, NetAddr::IP->new( shift ); next; } my @h = gethostbyname( $ARGV[0] ); unless( @h) { print "Unknown host $ARGV[0]\n"; exit 1; } unless( $h[3] == 4 && $#h >= 4 ) { print "$ARGV[0] : not an IPV4 address\n"; exit 1; } for my $a (@h[4..$#h]) { push @dips, NetAddr::IP->new( sprintf( "%vd", $a ) ); } shift; next; } if( $arg eq '-h' || $arg eq '--help' ) { Usage(); exit 0; } print "Unknown switch $arg"; print ' ', $ARGV[0] if( defined $ARGV[0] ); print "\n"; exit 1; } if( defined code2country( $arg ) ) { $iso{lc $arg} = 1; } else { my $cc = country2code( $arg ); if( defined $cc ) { $iso{lc $cc} = 1; } else { print "Unrecognized country/country code: $arg\n"; exit 1; } } } @iso = sort keys %iso; @iso = @DEFAULT_ISO unless( @iso ); sub Usage { print << "HELP"; IP filter manager for country filters Usage: $prog command args status [-v] Display filter status -v provides configuration from config file and command file - NOT iptables. list List available country names/codes Contacts server for list. intercepts [-host name] [-days n] List today's intercepts by host (from $LOG) Requires -log stop Stop filtering restart args Synonym for start (reloads with no open window) condrestart args Restarts only if already running start args Starts filter Start uses tables of IP blocks assigned to country codes that are stored in $ZONEDIR, which will be created if necessary. The data is obtained from $BLOCKURL when needed, or when start -update is specified. iptables filters are generated and installed by start. The filters are optimized and generally will not look identical to the input data. However they will match the same address (no more and no fewer.) Arguments for start-class commands are: -update Get latest data for active country codes. Otherwise, only gets data if no local file exists for a CC. -log Install a logging rule to log rejected packets. -nolog Don't install a logging rule (default) -nolimit Do not limit logging (can generate huge log files if under attack; not advised) -limit spec Limit logging, default = $loglimits[0]:$loglimits[1] (see man iptables "limit") -atport n Allow connections to TCP port n even from banned addresses. May specify any number of times. May use a service name. -auport n Allow connections to UDP port n even from banned addresses. May specify any number of times. May use a service name. -aip ip(/mask) Allow connection from an otherwise banned IP address. For a block, specify a netlength or mask. A hostname may also be specified. -dip ip(/mask) Deny connections from an otherwise allowed IP address. Same syntax as -aip -permitonly Listed countries will be permited, all others denied -d Output random debugging messages -v Output extended status/statistics CC ISO Country code or name to ban (as many as you like) Default list: HELP for my $cc (sort @DEFAULT_ISO ) { print " $cc - ", code2country($cc), "\n"; } print <source/dev/null`); # Delete each one # First, delete the rule in the main chain that reads '-s 1st octet, goto subchain' # Then empty and delete the subchain for my $schain (@schains) { $schain =~ m/-(\d+)$/; system( "$IPT -D $chain -s $1.0.0.0/8 -g $schain" ); system( "$IPT -F $schain" ); system( "$IPT -X $schain" ); } } sub delchainref { my $main = shift; # e.g. INPUT my $chain = shift; # e.g. BLOCKchain my @crefs = map { (m/^$chain\s/ ? ( $chain, ) : ()) } split( /\n/, `$IPT -n -L $main 2>/dev/null`); for my $cref (@crefs) { # should be only one system( "$IPT -D $main -j $cref" ); } } # Sort function for IP addresses for installation into filter chains # The whole chain must be processed if we miss, so there's nothing we can do. # But on a hit, we can improve the expected time somewhat by checking the # largest blocks first. This corresponds to the smallest mask length. # It is possible to do better if the traffic pattern is known, but there # isn't a good way (short of active feedback) to determine it. # In any case, we reduce the search length by hashing on the first # octet of the address, so this is a secondary effect. sub ipcmp { my $x = $a->masklen $b->masklen; return $x if( $x ); return $a $b; } sub start { print "Starting blocked countries IP filter: " unless( $ENV{CRONJOB} ); File::Path::make_path( $ZONEDIR, { mode => 0771 } ) unless( -d $ZONEDIR ); # Delete any lingering references / parts of new chain delchainref( $IHOOK, $IPNEWCHAIN ); # Perhaps someday if I want to spend core on -d rules # delchainref( $OHOOK, $IPNEWCHAIN ); # delchainref( $FHOOK, $IPNEWCHAIN ); delsubchains( $IPNEWCHAIN ); system( "$IPT -F $IPNEWCHAIN >/dev/null 2>&1" ); system( "$IPT -X $IPNEWCHAIN >/dev/null 2>&1" ); # (Log & ) drop chain system( "$IPT -F $IPNEWCHAIN-DLOG >/dev/null 2>&1" ); system( "$IPT -X $IPNEWCHAIN-DLOG >/dev/null 2>&1" ); return failure unless( system( "$IPT -N $IPNEWCHAIN-DLOG" ) == 0 ); if( $log ) { # Note that we can not provide a per-country log prefix due to compaction. # However, the intercepts report will map IPs back to their (alleged) country of origin # To determine what countries are causing intercepts, the logs must be post-processed to # lookup each IP. my $limits = ""; $limits = "-m limit --limit $loglimits[0] --limit-burst $loglimits[1] " if( @loglimits ); return failure unless( system( "$IPT -A $IPNEWCHAIN-DLOG $limits-j LOG --log-prefix \"$LOGPFX\"" ) == 0 ); } return failure unless( system( "$IPT -A $IPNEWCHAIN-DLOG -j DROP" ) == 0 ); return failure unless( system( "$IPT -N $IPNEWCHAIN" ) == 0 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -m state --state RELATED,ESTABLISHED -j RETURN" ) == 0 ); # List any allowed ports - first since they have a netmask of 0 # Allowed TCP ports - no more than 15 per rule (limit of multiport) my $exceptions = @aips + @auports + @atports; my $xrules = 0; while( @atports ) { my $n = @atports; $n = 15 if( $n > 15 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -p tcp -m multiport --dports " . join( ',', @atports[0..$n-1] ) . ' -j RETURN' ) == 0 ); splice( @atports, 0, $n ); $xrules++; } # Allowed UDP ports while( @auports ) { my $n = @auports; $n = 15 if( $n > 15 ); return failure unless( system( "$IPT -A $IPNEWCHAIN -p udp -m multiport --dports " . join( ',', @auports[0..$n-1] ) . ' -j RETURN' ) == 0 ); splice( @auports, 0, $n ); $xrules++; } # Allowed IPs (with optional masklen/netmask); largest size first # # Local subnets - external firewalls prevent them from showing up here, but # a bogus zone file could do damage. unshift @aips, NetAddr::IP->new( '192.168.0.0/16' ), NetAddr::IP->new( '172.16.0.0/12' ), NetAddr::IP->new( '10.0.0.0/8' ); $exceptions += 3; @aips = sort ipcmp NetAddr::IP::Compact( @aips ); $xrules += @aips; while( @aips ) { return failure unless( system( "$IPT -A $IPNEWCHAIN -s " . shift( @aips ) . ' -j RETURN' ) == 0 ); } # Explicitly blocked IPs @dips = sort ipcmp NetAddr::IP::Compact( @dips ); $xrules += @dips; while( @dips ) { return failure unless( system( "$IPT -A $IPNEWCHAIN -s " . shift( @dips ) . " -j $IPNEWCHAIN-DLOG" ) == 0 ); } if( $verbose ) { # Exception statistics print( "\n", "$exceptions exceptions generated $xrules rules." ); } # Make sure we have a zone file for each country code # Fetch a new one if -update or we don't have one # If we fetch, only transfer the file if it's different from (usu. newer than) our copy. my @files; for my $c (@iso) { my $db = "$ZONEDIR/$c.zone"; my $cn = code2country( $c ); $cn = " ($cn)" if( defined $cn ); # Fetch if updating or have no data if( $update || ! -f $db || -z $db ) { my $rc = mirror( "$BLOCKURL/$c.zone", $db ); if( is_success( $rc ) ) { print "\nUpdated IP zone data for $c$cn", if( $debug || $update || $ENV{CRONJOB} ); # Shouldn't ever get an empty file, but may as well check unless( -f $db && -s $db ) { print "\nUpdated zone data for $c$cn is empty!"; unlink $db; return failure; } } else { if( $rc == RC_NOT_MODIFIED ) { # Can only happen if file exists print "\nNo new IP data available for $c$cn " if( $debug || ($update && !$ENV{CRONJOB}) ); } else { print "\nUnable to fetch IP zone data for $c$cn: $rc - ", status_message($rc), " "; } unless( -f $db && -s $db ) { # No data - don't replace current filter print "\nNo IP zone data available for $c$cn "; if( $debug ) { next; } return failure; } # Failed, but have old file, continue since other zones may be updated } } push @files, $db; } return failure unless( @files ); # Parse the zone files and create a list of IP blocks my @addresses = (); for my $if (@files) { open( my $ifh, '<', $if ) or die( "Can't open $if: $!" ); while( ) { s/\s*#.*$//; next unless( length ); push @addresses, NetAddr::IP->new( $_ ); } close $ifh; } return failure unless( @addresses ); # Compact the blocks into the minimal covering set my $inaddrs = @addresses; @addresses = sort ipcmp NetAddr::IP::Compact(@addresses); # Generate an iptables-restore file with the new rules my %subchains; open( my $fh, '>', $ZONETBL ) or die( "Can't open $ZONETBL: $!" ); print $fh "# Generated by $prog on ", (scalar localtime), "\n", "*filter\n"; # table if( $IHOOK eq 'INPUT' ) { print $fh ":INPUT ACCEPT [0:0]\n"; # built-in chain, policy, counters } # Note: Do not include input hook table declaration as this will clear it # It is guaranteed to exist because we checked earlier foreach my $ipblock (@addresses) { $ipblock =~ /^(\d+)\./; unless( $subchains{$1} ) { print $fh ":$IPNEWCHAIN-$1 - [0:0]\n", # subchain, no policy, zero counters "-A $IPNEWCHAIN -s $1.0.0.0/8 -g $IPNEWCHAIN-$1\n"; # Chain - branch on 1st octet to subchain } $subchains{$1}++; if( $permitonly ) { print $fh "-A $IPNEWCHAIN-$1 -s $ipblock -j RETURN\n"; # Subchain, accept } else { print $fh "-A $IPNEWCHAIN-$1 -s $ipblock -j $IPNEWCHAIN-DLOG\n"; # Subchain, branch on match to log & drop } } if( $permitonly ) { foreach my $subchain (keys %subchains) { print $fh "-A $IPNEWCHAIN-$subchain -j $IPNEWCHAIN-DLOG\n"; } } print $fh "COMMIT\n", "# Completed on ", (scalar localtime), "\n"; close $fh; if( $verbose ) { # Provide some statistics, mostly for debugging. my( $minlen, $maxlen ); $minlen = $maxlen = $subchains{(keys %subchains)[0]}; for my $s (values %subchains) { $minlen = $s if( $s $maxlen ); } print( "\n", $inaddrs . ($permitonly? ' permitted' : ' blocked') . " address ranges generated ", (scalar @addresses), " rules, using ", (scalar keys %subchains), " sub-chains. Savings: ", ($inaddrs - scalar @addresses), " rules (", sprintf( "%.2f", 100*(1- ((scalar @addresses))/$inaddrs)), "%). Minimum chain length: $minlen", ", Maximum: $maxlen\n" ); } # Install new ruleset # -- Mass-install new Chain, subchains & rules return failure unless( system( "$IPTR -n $ZONETBL" ) == 0 ); # -- Link INPUT to the new chain return failure unless( system( "$IPT -I $IHOOK -j $IPNEWCHAIN" ) == 0 ); # return failure unless( system( "$IPT -I $OHOOK -j $IPNEWCHAIN" ) == 0 ); # return failure unless( system( "$IPT -I $FHOOK -j $IPNEWCHAIN" ) == 0 ); # Remove old rules delchainref( $IHOOK, $IPOLDCHAIN ); # delchainref( $OHOOK, $IPOLDCHAIN ); # delchainref( $FHOOK, $IPOLDCHAIN ); delsubchains( $IPOLDCHAIN ); system( "$IPT -F $IPOLDCHAIN >/dev/null 2>&1" ); system( "$IPT -X $IPOLDCHAIN >/dev/null 2>&1" ); system( "$IPT -F $IPOLDCHAIN-DLOG >/dev/null 2>&1" ); system( "$IPT -X $IPOLDCHAIN-DLOG >/dev/null 2>&1" ); $IPOLDCHAIN = $IPNEWCHAIN; return success if( running ); return failure; } sub stop { return 1 if( !running ); print "Removing blocked countries IP filter"; delchainref( $IHOOK, $IPOLDCHAIN ); delsubchains( $IPOLDCHAIN ); system( "$IPT -F $IPOLDCHAIN" ); system( "$IPT -X $IPOLDCHAIN" ); system( "$IPT -F $IPOLDCHAIN-DLOG" ); system( "$IPT -X $IPOLDCHAIN-DLOG" ); if( !running ) { success; return 1; } failure; return 0; } sub restart { # Don't stop since start will keep the current table alive until # the new one is active. return start(); } # List intercepted IPs for today # This can be run in a cron job just before midnight to get a list of # IPs to report. Or, you can use -days n to get the last n days worth # of intercepts # Only works if logging is on sub intercepts { my( $fh, %ips ); $days ||= 1; my $start = time() - ( $days * 24*60*60 ); $host ||= Net::Domain::hostname(); foreach my $logfile (glob $LOG) { my $lh = IO::Uncompress::Gunzip->new( $logfile, MultiStream => 1, Transparent => 1 ); unless( $lh ) { print "Skipping system log file: $IO::Uncompress::Gunzip::GunzipError\n"; next; } my $sl = Parse::Syslog->new( $lh, arrayref => 1 ); # Record # intercepts for each ip => protocol => port while( my $l = $sl->next ) { next if( $l->[0] [2] eq $LOGPGM && $l->[1] =~ /$host/i; if( $l->[4] =~ /^\Q$LOGPFX\E.*?\bSRC=([0-9.]+).*?\bPROTO=(ICMP)\b.*?\bTYPE=(\d+)/ ) { $ips{$1}{lc $2}{$3}++; } elsif( $l->[4] =~ /^\Q$LOGPFX\E.*?\bSRC=([0-9.]+).*?\bPROTO=(\w+).*?\bDPT=(\d+)/ ) { $ips{$1}{lc $2}{$3}++; } } close $lh; } return 0 unless %ips; # List each intercepted IP, its country, the protocols, ports and number of packets for each print "Intercepts by host IP:\n"; my( %ccip, %ccn ); foreach (glob "$ZONEDIR/*.zone") { /$ZONEDIR\/(.*).zone/; my $cc = $1; next unless( defined code2country( $cc ) ); # Skip undocumented zone files open( my $ifh, '<', $_ ) or next; while( ) { s/\s*#.*$//; next unless( length ); push @{$ccip{$cc}}, NetAddr::IP->new( $_ ); } close $ifh; } for my $ip (sort map {NetAddr::IP->new($_)} keys %ips) { my $ccn; CCSEARCH: foreach my $cc (keys %ccip) { foreach my $cip (@{$ccip{$cc}}) { if( $cip->contains($ip) ) { print "$cc: "; $ccn = $cc; last CCSEARCH; } } } unless( $ccn ) { # Possible if we've stopped blocking a country but have old log entries $ccn = '??'; print '??: '; } print $ip->addr; my @plist = sort keys %{$ips{$ip->addr}}; # Protocol for my $p (@plist) { my @rlist = sort keys %{$ips{$ip->addr}{$p}}; # Ports print ' ', join( ' ', map { my $n = $ips{$ip->addr}{$p}{$_}; $ccn{$ccn} += $n; "$p-$_($n)" } @rlist ); } print "\n"; } print "Intercepts by country:\n"; for my $cc (sort {$ccn{$b} $ccn{$a} } keys %ccn) { my $cn = code2country($cc); if( defined $cn ) { $cn = "$cc ($cn)"; } else { $cn = $cc; } printf "%10u %s\n", $ccn{$cc}, $cn; } return 1; } if( $cmd eq 'start' ) { exit !start(); } if( $cmd eq 'stop' ) { exit !stop(); } if( $cmd eq 'restart' ) { exit !restart(); } if( $cmd eq 'condrestart' ) { exit !(running && restart()); } if( $cmd eq 'status' ) { exit !status(); } if( $cmd eq 'list' ) { exit !list(); } if( $cmd eq 'intercepts' ) { exit !intercepts(); } if( $cmd eq 'help' ) { Usage(); exit; } print "Usage: $prog (start|stop|restart|condrestart|status|list|intercepts|help)\n"; exit 1;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 don’t visit this forum so I’m a bit late.
IPv6 is handled by the latest Perl script. Those DO still change.
See https://github.com/tlhackque/BlockCountries for the latest (and all future) versions.
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.
I am using my server to run applications used only within my country. White-list drawbacks should not affect my users. A working white-list script would be greatly appreciated
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.
I hacked up your script, I think my version is a little better xD
http://pastebin.com/NDTZdjBH
Joseph – you made some minor modifications to the original script. If you go back to the top of the post, there are some references to the enhanced scripts in bash and perl which have more features and better performance than the original started by Vivek. You should build on those scripts unless you’re taking this in a new direction.
No I am not. I was just trying to keep it simple and improve the “original” script by adding the countries into their own chains. Also made it so it wouldn’t remove any current rules you already have saved and added a feature to remove only the rules added by the script by executing “./block_countries_iptables.sh flush”.
I did check the enhanced scripts and they may be overwhelming for a novice but good work by their authors.
The enhanced scripts do that and also load the tables through iptables-restore – the script you are using can take hours to load if you have too many ip ranges as the iptables command commits the change in the kernel after every additional rule. The original script also blocks responses from any country you block – when I blocked Korea I couldn’t update my Samsung blue ray player. You might want to read through the discussions on the thread if you run into any of these issues with your modifications – the thread outlines a number of issues and their fixes.
I don’t mean to go back and forth but I understand how saving the rules to a file and using iptables-restore < your_file would be faster and more efficient. I was only trying to improve the original script posted and not do a complete rewrite when they have already been done by 2 others. I wouldn't suggest running the script as a cron job but it is very simple and easy to understand to quickly block a few countries.
Also I blocked af, cn, kr, and th in less then a minute.
Hi Joeseph, I am not having any luck getting any of these scripts to work under Centos 5.9 on my dedicated VPS account.
Yours gives me this error
: command not foundline 4:
: command not foundline 11:
: command not foundline 16:
‘BlockCountries.sh: line 21: syntax error near unexpected token `do
‘BlockCountries.sh: line 21: ` do
Any ideas how to fix this for Centos?
Thanks heaps for pointing me to that solution,
I have edited the script to block India as per instructions
I uploaded the script to /root/scripts and gave it 755 permissions
I uploaded the zone file for india (in.zone) into the same directory
I then logged into as root via SSH ad and tried to run the script /root/scripts/country.block.iptables.sh
but am getting this error on Centos 5.0
-bash: country.block.iptables.sh: command not found
What am I doing wrong?
The BlockCountries perl script has been updated to V2.0, supporting improved chain creation and IPv6. It also gets the IP address allocations directly from the NICs.
I will not be monitoring or posting here. The new script can be found, and any updates posted on
https://github.com/tlhackque/BlockCountries
This should make it easier to find and also makes it possible to manage any updates.
I don’t promise to respond to issues, but they can be posted there.
Of course, positive feedback is also appreciated.
tlhackque – i ran bcinstall first and installed a couple of required modules under CentOS release 5.9 (Final). Then uploaded BlockCountries 2.0 from github and did to a CHMOD to execute.
Am getting this error – any idea’s?
root@server2 [/]# bash BlockCountries.sh
BlockCountries.sh: line 9: use: command not found
BlockCountries.sh: line 10: use: command not found
BlockCountries.sh: line 52: my: command not found
BlockCountries.sh: line 56: my: command not found
BlockCountries.sh: line 57: my: command not found
BlockCountries.sh: line 59: my: command not found
BlockCountries.sh: line 60: my: command not found
BlockCountries.sh: line 61: my: command not found
BlockCountries.sh: line 63: syntax error near unexpected token `(‘
BlockCountries.sh: line 63: `my %config = ( # Parameters that can vary between IPV4 and IPV6’
Please put any issues on the github site – it’s only random chance that I happened across this. (I came here to unsubscribe!)
BlockCountries is not a bash (.sh) script. It’s a Perl script.
If you downloaded it correctly, chmod +x and just execute it.
bash# chmod+x BlockCountries
bash# BlockCountries help
bash# BlockCountries -update -start
You said:
bash# bash BlockCountries
That tells tells bash to execute it as a shell script, which it is not.
(bcinstall is a bash script because it has to be able to tell you to install Perl.)
@vivek – please update your FAQ to point to https://github.com/tlhackque/BlockCountries
for all future versions of the perl BlockCountries script.
I appreciate this forum – but besides having grown too hard to follow, maintaining code on a BBS is not feasible.
I’m unsubscribing, so really, anyone who is interested in the Perl script should visit GitHub – there is an Issues page (https://github.com/tlhackque/BlockCountries/issues) where there’s some chance I’ll see questions.
Done. Updated the link. Thank you for your contribution!
tlhackque – A MASSIVE THANKYOU !!
it’s working fine now, i had the config file in the wrong place as well not understanding the Centos cmd line variations very well.. it works with this cmd:
#perl BlockCountries start
My server has been plagued by hackers & spammers for nearly a year and I was going mad bailing out the ship with buckets! This is exactly what I was looking for so a million thanks. hopefully I don’t have to close my web business down now.
I´ve used the script from david without problems. Thank you for this great script, David! But now i have a problem. Since my serverprovider do an update to the kernel i get these error: “iptables-restore: line 2354 failed”. Line 2354 is “COMMIT”.
On my server running iptables v. 1.4.8, is something changed here? Sadly i dont know which version it was before and i dont know if it depends on the new kernel itself.
Can anyone help me?
What are the kernel versions from to ?
Thanks for your reply Jouni!
The old kernel version was “2.6.32-5-xen-amd64”, the new kernel is “3.8.5-xen”. The server running debian on a xen virtualization.
Got it! It depends on the “log lines” like this for not working:
-A kr-countrydrop-DROP -j LOG –log-prefix “iptables: kr-Country-Drop:”
Since i´ve comment this out in the mainscript it works. I dont know why because the syntax seems to be ok…
I took a look at the latest iptables man pages on Fedora – there is a limit of 29 characters on the prefix and I’m wondering if the –log-level option is now required. If you add a log level of notice and uncomment one of the LOG lines with a prefix under 29 characters, does the logging work and the chain commit?
Thank you for your reply, David.
I´ve tested your ideas and nothing works. I´ve edited the generated iptables.cb like this:
-A af-countrydrop-DROP -j LOG –log-prefix “af-Drop:” –log-level notice
This should work but it dont work.
If i do this for example:
-A af-countrydrop-DROP -j LOG –log-level notice
it results in the same error.
I think it is a problem in the kernel config. I will have a look now if the Log-Module working or not. If not I will contact my provider.
How could we add a sleep delay in between each iptables rule processed?
(if even possible at all).
Thanks.
Use sleep to suspend execution for an interval of time:
for c in $ISO do # sleep 60 seconds ## sleep 60 # 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 doneAce, thanks for a prompt response with this extension to your script… :)
Why add a sleep delay? Are you using Vivek’s original script? If so, please use my modified script which gets around the issues with single rule commit that cause loading to take hours, my script does a batch load of the rules and takes seconds.
Hey David,
Everytime I was trying to add lots of iptables rules at once, I kept receiving ‘iptables unknown errors’. After testing, it didn’t seem to be an issue with particular rules but more a case of randomly throwing the error when multiple iptables were being processed so I wanted to try adding a delay between rules to see if this resolved.
It subsequently didn’t by the way but did stop long enough at the start of the script for me to see this error:-
iptables v1.3.5: can’t initialize iptables table `nat’: Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
iptables v1.3.5: can’t initialize iptables table `nat’: Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
I am able to add individual iptables though. So not really sure where I’m going with this at the moment.
Trying to block the entire country of China by the way using iptables.
The nat table allows you to implement forwarding and masquerade packets as the device connected to the internet – if you don’t want nat, simply remove those rules from the script.
I use nat because my machine is the gateway for my home network connecting every device to the internet. Many home cable or dsl routers now implement this capability for you so you don’t technically need to make your machine a gateway.
Thanks, I’ll give this a go and see if it improves anything my end.
No more than 18 iptables rules were being accepted. I think the issue is actually because I was trying to use iptables on a VPS that is a shared single kernel across multiple containers… :(
Folks using the Perl script:
Due to a format change in the RIR statistics files, a new version has been released on github. Download from:
https://github.com/tlhackque/BlockCountries/archive/master.zip
After updating, ‘BlockCountries help’ should report version 2.1
You should run ‘BlockCountries start -update’ to get the latest data.
Any issues, feedback at https://github.com/tlhackque/BlockCountries/issues, as I am no longer subscribed and do not monitor this BBS.
I recommend getting a github account (they’re free) and watching the repository for any future updates – I don’t promise to provide release notices here.
i might have gotten something wrong, but anybody else realized that most of the files at http://www.ipdeny.com/ipblocks/data/countries/ are of size 0 since sept 29 2013?
something wrong at ipdeny? anybody found a more reliable source?
anyway, thanks for the many interesting aspects and scripts around this issue ;)
I took a look at the files and they look like they’re now empty – however maxmind.com also provides a source for the IP addresses but the dump of the data requires some translation to make it readable by the scripts here. The following shell script does this translation:
#!/bin/sh CIP_CACHE=/var/cache/country-ips DL_DATAFILE=GeoLite2-Country-CSV.zip WGET=/usr/bin/wget # Create cache location for country ip range files [ ! -d ${CIP_CACHE} ] && mkdir -p ${CIP_CACHE} cd ${CIP_CACHE} # Download data if source file is not present if [ ! -f ${DL_DATAFILE} ] ; then $WGET http://geolite.maxmind.com/download/geoip/database/${DL_DATAFILE} unzip -j ${DL_DATAFILE} */GeoLite2-Country-Locations.csv */GeoLite2-Country-Blocks.csv fi # Read each country from the country file export IFS="," typeset -l -g CN_CD while read GEO_ID RG_CD RG_NAME CN_CD CN_NAME CN_LINE; do if [ "Z${CN_CD}" != "Z" ] ; then printf "# Zone file for country %s : %s\n" ${CN_NAME} ${CN_CD} printf "# Zone file for country %s : %s\n" ${CN_NAME} ${CN_CD} > ${CN_CD}.zone while read GEO_IP IP_MASK_LEN IP_RG_CD IP_CN_CD IP_LINE ; do declare -i CIDR=$((32-(128-${IP_MASK_LEN}))) if [ "${IP_CN_CD}" = "${GEO_ID}" ] ; then printf "%s/%s\n" ${GEO_IP} ${CIDR} | sed -e 's/::ffff://g' >> ${CN_CD}.zone fi done < "GeoLite2-Country-Blocks.csv" fi done < "GeoLite2-Country-Locations.csv"Hello look at this script: https://gist.github.com/anonymous/7415239
its much simpler and does the same and its much faster than using iptables only
dont forget to install ipset befor running the script!
Greetings Stingray
Hello everyone, i have modify the script in order to secure my Centos 6.4 server
Just place in your server the zone files and set your static ip address in order to manage your server only from your ip address
#!/bin/bash ### Block all traffic from AFGHANISTAN (af) and CHINA (cn). Use ISO code ### ### dloghis ### ### 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. # Most common spammers ISO="af cn br cl co hk in ir th ve ne ro rs ru pa jp tw tr my pk ng ua a1 kr kp sa etc" # All options # ISO="ad ae af ag ai al am an ao ap ar as at au aw ax az ba bb bd be bf bg bh bi bj bm bn bo bq br bs bt bw by bz ca cd cf cg ch ci ck cl cm cn co cr cs cu cv cw cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fm fo fr ga gb gd ge gf gg gh gi gl gm gn gp gq gt gu gw gy hk hn hr ht hu id ie il im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mf mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl pm pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg si sk sl sm sn so sr ss st sv sx sy sz tc td tg th tj tk tl tm tn to tr tt tv tw tz ua ug uk um us uy uz va vc ve vg vi vn vu wf ws ye za zm zw" ### Set PATH ### IPT=/sbin/iptables WGET=/usr/bin/wget EGREP=/bin/egrep ### No editing below ### SPAMLIST="countrydrop" ZONEROOT="/root/cron-scripts/iptables" DLROOT="http://www.yourserver.gr/ipblocks" SERVERIP=$(hostname -i) # Your Server IP SERVERHOSTNAME=$(hostname -s) # Your Server Host Name just for info is you have multiple servers ACCESS_IP="xxx.xxx.xxx.xxx" # Your static IP here, only from this IP you are going to SSH SSHPort=xxx # Your port here, only from this port you are going to SSH adate=$(date +%Y-%m-%d,%X) # Title and Version echo "*----------------------------------------*" echo "* IPtables V-20140301 *" echo "*----------------------------------------*" echo "Server Name: "$SERVERHOSTNAME "IP="$SERVERIP echo "*************** WARNING ****************" echo "Check your IPs & Ports and type your choice" echo "After running this script you are going to" echo "access with SSH only from:" $ACCESS_IP " port:" $SSHPort echo "" # Ask if you want to save settings AskSaveSettings () { PS3="Do you want to save settings?" select result in Yes No do case "$result" in Yes) SaveSettings && ListSettings && echo "" && echo "Settings saved..." && GiveDate && exit 0;; No) ListSettings && GiveDate && echo "Settings not saved..." && exit 0;; *) echo "Invalid";; esac done } # clean old rules command 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 } # Set default policies for INPUT, FORWARD and OUTPUT chains (drop all incoming) DropIncoming () { $IPT -P INPUT DROP $IPT -P FORWARD DROP $IPT -P OUTPUT ACCEPT } # Accept the ports you need AcceptPorts () { # Set access for localhost $IPT -A INPUT -i lo -j ACCEPT # Allow SSH connections on tcp with ACCESS_IP and port SSHPort values $IPT -A INPUT -p tcp -s $ACCESS_IP --dport $SSHPort -j ACCEPT # All standard ports # Allow ftp connections on tcp port 21 $IPT -A INPUT -p tcp --dport 21 -j ACCEPT # Allow SSH connections on tcp port 22 ?? # Allow smtp connections on tcp port 25 $IPT -A INPUT -p tcp --dport 25 -j ACCEPT # Allow http connections on tcp port 80 $IPT -A INPUT -p tcp --dport 80 -j ACCEPT # Allow https connections on tcp port 443 $IPT -A INPUT -p tcp --dport 443 -j ACCEPT # Allow POP3 connections on tcp port 110 $IPT -A INPUT -p tcp --dport 110 -j ACCEPT # Allow SPOP3 connections on tcp port 995 $IPT -A INPUT -p tcp --dport 995 -j ACCEPT # Allow IMAP connections on tcp port 143 $IPT -A INPUT -p tcp --dport 143 -j ACCEPT # Allow IMAP connections on tcp port 993 $IPT -A INPUT -p tcp --dport 993 -j ACCEPT # zimbra admin connections on tcp port 7071 from your ip address $IPT -A INPUT -p tcp -s $ACCESS_IP --dport 7071 -j ACCEPT } DropCountries () { # create a dir [ ! -d $ZONEROOT ] && /bin/mkdir -p $ZONEROOT # 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 from SPAMLIST (-I=incoming) $IPT -I INPUT -j $SPAMLIST $IPT -I OUTPUT -j $SPAMLIST $IPT -I FORWARD -j $SPAMLIST } # Save Settings Action SaveSettings () { /sbin/service iptables save && echo "" && echo "Saving settings please wait..." && echo "" } # List Settings ListSettings () { echo "" echo "Your Settings are:" $IPT -L -n -v } # Information Just to know how long it took GiveDate () { echo "" bdate=$(date +%Y-%m-%d,%X) echo "Job Start in: "$adate echo "All Done! End time: "$bdate } # Ask for actions CleanOldRules DropIncoming AcceptPorts DropCountries SaveSettings PS3="Are you soure you want to Continue?" select result in Continue_Clean_Add_All_Rules Continue_Clean_Add_All_Rules_and_Save Continue_Add_New_Countries Cancel do case "$result" in Continue_Clean_Add_All_Rules) CleanOldRules && DropIncoming && AcceptPorts && DropCountries && exit 0;; Continue_Clean_Add_All_Rules_and_Save) CleanOldRules && DropIncoming && AcceptPorts && DropCountries && SaveSettings && ListSettings && GiveDate && exit 0;; Continue_Add_New_Countries) DropCountries && AskSaveSettings && GiveDate && exit 0;; Cancel) exit 0;; *) echo "Invalid option";; esac doneHi Dimitrios – your script appears to be a complete rewrite of the original script. It discards any previously established iptables rules on the system and does not warn the security admin that their rules are being discarded. It also updates iptables one rule at a time and would take many hours to run if the zone files were populated – the files on the server are empty though so none of the scripts are functional.
You should read the full thread to understand my bash version as referenced and vivek’s perl version which uses some add-on kernel modules – both of which have optimizations on initial loading and performance of the tables themselves as the rules are applied so as to minimize impact on network performance. Both scripts also maintain rules in a separate table and do not alter the base firewall settings and initialize in a few seconds
Intrusion Prevetion System Like Suricata Provides GeoIP block.
https://redmine.openinfosecfoundation.org/projects/suricata/wiki/GeoIP
Linux router with inline suricata performs well.
Hi all.
I have a few questions with regards the scripts listed.
1. How to i stop and reload the scripts properly. or do i just rerun the script?
2. i take it that the last version of the script is the most up to date and bug free?
and 3. Thanks very very much for the time and effort to everyone who has contributed to the thread. I very much appreciate it.
Steve
How about block all Countries and allow just the ones you like to allow instead of blacklisting you dont want ? I bet the whitelist would be shorter :D
Possible?
Just use iptables with xtables-addons and the xt_geoip module. You can block by country or allow by country. Just google xtables-addons and check the instructions for the geoip module.
Is there any way to exclude specific ip’s , for example googlebot or others crawlers?
Looks like they are empty again today..
Love this. Thanks
Can we use also the new aggregated format?http://www.ipdeny.com/ipblocks/data/aggregated/
If yes, how the configuration need to be changed?
thanks
Ed
Yes – the aggregated zones can and should be used – you would need to change the ipdeny base URL – defined as IPDENYDLROOT – in the script I posted for this to work.
The updated script is available at http://blog.psind.com/solutions/products/iptables-cb/
BTW – That script does not have the change in the base URL – you would need to change that yourself
I have a question. If I have fial2ban rules will function of cleanOldRules delete the fail2ban rules??
Kind regards.
How does one install the script on CentOS 6? The one on psind.com.
Hi,
I really like your site, it has helped me countless times. :) Good stuff.
We have a sites that only deal with western and nordic countries, all other traffic can easily be blocked. However to be sure we do not drop traffic anybody we do want to do business with, is it possible to forward all dropped http traffic on ports 443 and 80 to a specific site and port where we can have a default nginx page that explains that they have been geo blocked and to contact us if they need access. In which case we can specifically allow a certain IP on these ports.
I hope someone can point me in the right direction for solving this. :)
Re: Problems Running perl (CountryBlock) script on boot-up (start-up) in debian linux.
Hi,
Both of the programs of blocking countries are excellent jobs, But, I prefer running perl script in linux.
When I run perl script in the terminal mode, I had no problem and it run perfectly, however, when i tried to run in cron job, it doesn’t run although I set it up in the /etc/init.d/program-names with the chmod 755 or chmod +x program-names.
Would someone put me in the right directions how I can run perl BlocKCountries during start-up or boot-up in linux debian. Your help and assistance is much appreciated. Thank you.
@Alex, did you try “sudo update-rc.d BlockCountries enable” to register the script with the init system? The cron job is supposed to run weekly and look something like this: “11 23 * * Sun /etc/init.d/BlockCountries start -update”
Just a reminder that the BlockCountries perl script is maintained on
https://github.com/tlhackque/BlockCountries and has been for over 2 years.
The old versions posted here have known issues, including the inability to deal with some
database format changes made at the registries. (I wish I could delete the code posted here.)
The current version supports IPv6 and has no known issues. It’s also better documented.
Also, I don’t look for issues or post release announcements here. Report issues at
https://github.com/tlhackque/BlockCountries/issues Watch the repository for release
announcements.
I encourage everyone visiting this site who is interested in BlockCountries to switch to github.
That’s where you’ll get prompt support and current releases.
Note that I appreciate this (cybersiti.biz) blog’s contribution to IP address blocking. The shell
scripts produced by Vivek and Dave inspired creation of the Perl script. The blog provided
valuable feedback, and allowed initial distribution.
However, a blog is not a good vehicle for software distribution, maintenance or issue tracking.
That’s why I moved to github.
just reinstalled debian 7 did the update and the whole shabang so i would say its ok to setup the script from github ?
but i get this ??
Carp v1.20 *OUTDATED Must upgrade to v1.3301 or higher
File::Temp v0.22 *OUTDATED Must upgrade to v0.2304 or higher
IO::Compress::Zip v2.033 *OUTDATED Must upgrade to v2.069 or higher
IO::Uncompress::Gunzip v2.033 *OUTDATED Must upgrade to v2.064 or higher
LWP::Simple *NOT INSTALLED Must install v6.00 or higher
Locale::Country v3.16 *OUTDATED Must upgrade to v3.37 or higher
Net::Domain v2.20 *OUTDATED Must upgrade to v2.23 or higher
NetAddr::IP *NOT INSTALLED Must install v4.044 or higher
Regexp::IPv6 *NOT INSTALLED Must install v0.03 or higher
Socket v1.94 *OUTDATED Must upgrade to v2.006 or higher
Storable v2.27 *OUTDATED Must upgrade to v2.30 or higher
Sys::Syslog v0.27 *OUTDATED Must upgrade to v0.29 or higher
i must say the install could have some extra’s like a automated install that also fixes the missing or upgrades
i love this script and always used the original script above that worked perfect for me but now i want to try the github script
how can i fix these errors ?
Really useful! Can I block this IP’s from Squid ?
that was good script – blocking by countries is good thing.
block by ports not always be as close as good, because, i every day see many and many attempts to log in to administration account in my CMS , and i have had my wordpress site hacked some months ago ( i thing via some query injection or so, as so – 80 port also is unsafe).
this is awesome. saved me some time.
Thanks. Needed a script after I saw 200k+ attempts of chinese ip’s trying to ssh into my server