newspaint

Documenting Problems That Were Difficult To Find The Answer To

Perl Script to Calculate IP Blocks Given a Range of IP Addresses

Finding IP Blocks in a Range of IP Addresses

This is not an uncommon problem. You look through your web logs to find hack attempts from a range of IP addresses around the same area. To protect your machine you want to set up a series of iptables (firewall for Linux) rules for that general address range. You look up the address range using the whois tool – and sometimes the range of addresses allocated to an AS (autonomous system) are not a simple /24 or /16. So how do you quickly and easily work out the netblocks involved to feed your firewall?

I’ve written a simple Perl script below to calculate the IP blocks that fit within a given IP range (from the lower IP address to the upper IP address, inclusive).

If you save the below code as calc_range.pl and call it thus you will get the following output:

user@host:~$ perl -w calc_range.pl 125.40.67.93 125.43.255.255
  125.40.67.93/32
  125.40.67.94/31
  125.40.67.96/27
  125.40.67.128/25
  125.40.68.0/22
  125.40.72.0/21
  125.40.80.0/20
  125.40.96.0/19
  125.40.128.0/17
  125.41.0.0/16
  125.42.0.0/15
#!/usr/bin/perl -w

use strict;

sub ipstr_to_int {
    my ( $s ) = @_;
    my @z = split( /\./, $s );
    return( ($z[0] << 24) + ($z[1] << 16) + ($z[2] << 8) + $z[3] );
}

sub int_to_ipstr {
    my ( $i ) = @_;
    my $s = sprintf( 
        "%d.%d.%d.%d", ($i>>24)&0xFF, ($i>>16)&0xFF, ($i>>8)&0xFF, $i & 0xFF
    );
    return( $s );
}

my @ip_masks = (
    0x00000000
    ,0x80000000,0xc0000000,0xe0000000,0xf0000000
    ,0xf8000000,0xfc000000,0xfe000000,0xff000000
    ,0xff800000,0xffc00000,0xffe00000,0xfff00000
    ,0xfff80000,0xfffc0000,0xfffe0000,0xffff0000
    ,0xffff8000,0xffffc000,0xffffe000,0xfffff000
    ,0xfffff800,0xfffffc00,0xfffffe00,0xffffff00
    ,0xffffff80,0xffffffc0,0xffffffe0,0xfffffff0
    ,0xfffffff8,0xfffffffc,0xfffffffe,0xffffffff
);

my @ip_ranges = (
    4294967296
    ,2147483648,1073741824,536870912,268435456
    ,134217728,67108864,33554432,16777216
    ,8388608,4194304,2097152,1048576
    ,524288,262144,131072,65536
    ,32768,16384,8192,4096
    ,2048,1024,512,256
    ,128,64,32,16
    ,8,4,2,1
);

sub calc_ranges {
    my ( $ip_from, $ip_to ) = @_;

    # convert IP strings into integers
    my ( $int_ip_from ) = ipstr_to_int( $ip_from );
    my ( $int_ip_to ) = ipstr_to_int( $ip_to );
    
    my @results = (); # holding variable for results
    
    # iterate until we've found blocks for all parts of the range
    my $int_ip_next = $int_ip_from;
    while ( $int_ip_next <= $int_ip_to ) {
        # find widest netblock/mask that commences from this IP
        my $netmask_bits = 31; # we could start at 32 but pointless
        while ( $netmask_bits > 0 ) {
            # check - does this block start below our minimum address?
            my $mask = $ip_masks[$netmask_bits];
            last if ( $int_ip_next != ( $int_ip_next & $mask ) );
            
            # check - does this block exceed the range we want?
            my $this_block_ip_to = $int_ip_next + $ip_ranges[$netmask_bits] - 1;
            last if ( $this_block_ip_to > $int_ip_to );
            
            # widen netmask/range for this block and try again
            $netmask_bits--;
        }
        
        # last block was 1 bit too wide
        $netmask_bits++;
        
        push( @results, int_to_ipstr( $int_ip_next ) . "/$netmask_bits" );
        $int_ip_next += $ip_ranges[$netmask_bits];
    }
    
    return( @results );
}

# demonstration routine
# you can call from command line like:
#   perl -w calc_range.pl 125.40.67.93 125.43.255.255
my $from = shift;
my $to = shift;
my @results = calc_ranges( $from, $to );
print( "  $_\n" ) foreach ( @results );

In the code I’ve used hard coded arrays for the binary netmask and IP range. Subroutines can be used instead but, of course, they will be a lot slower – especially given that for every block up to 32 netmasks can be “tried” for fit.

This Perl code should be easily/trivially translated to other languages if you have the need for such a tool yourself.

4 responses to “Perl Script to Calculate IP Blocks Given a Range of IP Addresses

  1. Matthew August 13, 2012 at 4:04 pm

    perl -MNet::IP -e ‘$ip = new Net::IP (“$ARGV[0] – $ARGV[1]”); print map { “$_\n” } $ip->find_prefixes();’ “127.0.0.1” “127.0.0.2”

    No error checking or validating of inputs, but it still does what you wanted (it matches your output when using your example). I doubt it would make such an impressive blog post though!

    • newspaint August 13, 2012 at 4:08 pm

      Great response! The CPAN module Net::IP is easily available in the Debian/Ubuntu package libnet-ip-perl, too.

      The blog post may allow those using different languages to easily port the function to their environment of choice.

  2. neuhru March 25, 2013 at 10:58 pm

    I am trying to determine the IP address given a specific range and I find this example useful. I am a newbie and have some questions.

    (remainder of the question removed by editor)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: