How to block large ip subset on the example of TOR

I wrote before how i blocked TOR exit nodes by iptables, disadvantages of method that i used before – big amount of rules (one per each ip). This solution easy and obvious, but had speed penalty. Today i want write about more effective solution that use ipset, let’s see what is ipset:

IP sets are a framework inside the Linux 2.4.x and 2.6.x kernel, which can be administered by the ipset utility. Depending on the type, currently an IP set may store IP addresses, (TCP/UDP) port numbers or IP addresses with MAC addresses in a way, which ensures lightning speed when matching an entry against a set. If you want to store multiple IP addresses or port numbers and match against the collection by iptables at one swoop; dynamically update iptables rules against IP addresses or ports without performance penalty; express complex IP address and ports based rulesets with one single iptables rule and benefit from the speed of IP sets then ipset may be the proper tool for you.


Because of exit nodes can exist on hosts that using dynamic IP i want to delete addresses from list after timeout (otherwise list will always growing and contain unused addresses or addresses without TOR exit nodes, larger list requires more memory and CPU time for processing). If addresses persist between list updates, timeout will be reseted.

To do this i used iptree set (you can learn more about set types in manual for ipset), because that type provide timeout for each address.

First i installed ipset:

# apt-get install xtables-addons-common

After that i modify scripts that i used before. In perl script i made a pair of minor bug fixes and in shell script i add new facility for loading rules into ipset.
Perl script:

#!/usr/bin/perl -w
use strict;
use LWP::Simple;
my $list = get("");
my $i;
my @ips;
if( ! defined( $list ) ) {
    exit( 1 );
if( $#ARGV == -1 ) {
foreach $i (split( /\n/, $list )) {
    push( @ips, $1 ) if( $i =~ m/((?:\d{1,3}\.){3}\d{1,3})/);
if( $ARGV[0] eq "-ip" ) {
    print( join( "\n", @ips ) . "\n");

That script return exit code without arguments that signaled can this script fetch addresses or not, with parameter “-ip” they return list of addresses.
Next shell script:

case "$1" in
        if ! /usr/bin/perl $SOLVE
                echo "Can not fetch Tor exit nodes" 1>&2
                exit 1
        if ! $IPSET -L tor 2>&1 > /dev/null
                $IPSET -N tor iptree --timeout 259200
        for i in `/usr/bin/perl $SOLVE -ip`
                $IPSET -A tor $i 2> /dev/null
        if /usr/bin/perl $SOLVE
                /sbin/iptables -F TorExitnodes
                /sbin/iptables -I TorExitnodes -j RETURN
                for i in `/usr/bin/perl $SOLVE -ip`
                 /sbin/iptables -I TorExitnodes -s $i -j TorBlockAndLog
                        echo "Can not fetch Tor exit nodes" 1>&2
        exit 1;
        echo "Usage ./$0 "
        exit 1
exit 0

This script can add rules into iptables (old variant) or into ipset. I added this script in cron and run every few hours. When ipset found that address all ready in list, they update timeout, if address will not observed in 72 hours, they will be automatically deleted.

Finishing touch – new rule in iptables:

# iptables -A INPUT -i eth+ -m set --match-set tor src -m comment --comment "Block TOR exit nodes thru IPSET" -j DROP

That’s all. Do not forget to place this rule before rules where you permit access to your server.

One comment on “How to block large ip subset on the example of TOR”

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>