Hi, 

Jeff Pang <[EMAIL PROTECTED]> asked:

> I have a text file which contains lots of IPs,like:
> 
> 58.253.0.0/16;
> 58.254.0.0/16;
> 58.255.0.0/16;
> 60.0.0.0/16;
> 60.1.0.0/16;
> 60.10.0.0/16;
> 60.16.0.0/16;
> 60.17.0.0/16;
> 60.18.0.0/16;
> 60.19.0.0/16;
> 60.2.0.0/16;
> 60.20.0.0/16;
> 60.21.0.0/16;
> 60.22.0.0/16;
> 60.23.0.0/16;
> 60.3.0.0/16;
> 
> My question is,given an IP,ie 59.32.232.33,how can I know it 
> exists in this file or not?
> Is there a module already?thanks.

It's not a module, but here's some of my code that
might be useful if you want to test many ip adresses
against a fixed set of networks.

It is built on the facts that IP adresses can be 
converted to integers that retain the ordering
of the adresses and that networks are a range of
ip adresses - so your problem is really figuring
out whether a number x falls into one of n discrete
intervals.

The method of choice for this kind of problem is
doing a binary search.

I've tested several approaches and settled for
dynamically creating an unrolled variant of an
iterative binary search.

HTH,
Thomas

#!/usr/bin/perl -w

use strict;
use Socket; # we need Socket.pm for inet_aton()

#
# list of local networks, i.e. contents of your file
#

my %localnets = (
        '58.253.0.0/16' => 1,
        '58.254.0.0/16' => 1,
        '58.255.0.0/16' => 1,
        '60.0.0.0/16' => 1,
        '60.1.0.0/16' => 1,
        '60.10.0.0/16' => 1,
        '60.16.0.0/16' => 1,
        '60.17.0.0/16' => 1,
        '60.18.0.0/16' => 1,
        '60.19.0.0/16' => 1,
        '60.2.0.0/16' => 1,
        '60.20.0.0/16' => 1,
        '60.21.0.0/16' => 1,
        '60.22.0.0/16' => 1,
        '60.23.0.0/16' => 1,
        '60.3.0.0/16' => 1,
);

# used to build the binary search tree
my @netdata;


#
# number of mask bits => inverted Bit mask
#
# converts the xx number of mask bits (in ip.ip.ip.ip/xx) to an
# inverted bit mask, i.e. 24 becomes 0x000000FF
#

sub make_mask {

  if( $_[0] < 0 || $_[0] > 32 ){
    die "Illegal mask /$_[0]";
  } else {
    return( 2 ** ( 32 - $_[0] ) - 1);
  }
}



#
# ip.ip.ip.ip => 0x????????
#
# convert a dottect decimal IP address to an unsigned long number
# in host byte order.
#

sub ip_to_number {

  if( defined ( my $nl = inet_aton( $_[0] ) ) ){
    # convert network to host byte order
    return( unpack( 'N', $nl ) );
  } else {
    die "Invalid IP address '$_[0]'!\n";
  }

}



#
# Here's a different approach to binary searching intervals for a
# number: We dynmically create a subroutine that is a completely
# unrolled binary search over all intervals using nested if statements.
#
# This requires that we first create a text representation of our
# code which is then eval()'ed to create the subroutine. This adds
# some startup overhead, but it is also significantly faster than the
# other methods.
#
# The code is created using a recursive subroutine.
#
# Most of the "complexity" here is added by the design goal to create
# properly indented code that can easily be inspected visually.
#

sub create_binsearchcode_recursion {
  my( $start_index, $end_index, $iter ) = @_;

  # we use the $iter depth marker specifically to create a nice
  # indentation using the string repeat operator 'x'.

  # further subdivision is neither necessary nor possible
  return "  "x$iter . "  return 0;\n" if $start_index > $end_index;

  # no further recursion necessary if we have just 1 interval to check.
  if( $start_index == $end_index ){

    return "  "x$iter . "if( $netdata[$start_index]->[0] <= \$num && \$num <= 
$netdata[$start_index]->[1] ){\n" .
           "  "x$iter . "  return( $netdata[$start_index]->[2] );\n" .
           "  "x$iter . "} else {\n" .
           "  "x$iter . "  return( 0 );\n" .
           "  "x$iter . "}\n";
  }

  # compute "median" point between start and end indices.
  my $partition = $start_index + int(($end_index - $start_index) / 2);

  # write code to handle hits, misses and use recursion to
  # fill out "upper" and "lower" branches of the search tree.
  return "  "x$iter . "if( $netdata[$partition]->[0] <= \$num && \$num <= 
$netdata[$partition]->[1] ){\n" .
         "  "x$iter . "  return( $netdata[$partition]->[2] );\n" .
         "  "x$iter . "} else {\n" .
         "  "x$iter . "  if( $netdata[$partition]->[0] > \$num ){\n" .
         create_binsearchcode_recursion( $start_index, $partition - 1, $iter+2) 
.
         "  "x$iter . "  } else {\n" .
         create_binsearchcode_recursion( $partition + 1, $end_index, $iter+2) .
         "  "x$iter . "  }\n" .
         "  "x$iter . "}\n";
}



#
# This subroutine is the stub that contains all of the "unique" code
# parts of the subroutine constructs. It calls the recursive sub defined
# above to fill in the search tree. After that is done, it evaluates the
# code it created to add the subroutine dynamically to the program code.
#
# The die() statement at the end of the sub isn't really necessary but
# a nice early warning if something is wrong with the code we created.
#
# Please note that the code snippet ends with a 1; statement - this is
# since eval() always returns the value of the last statement.
#

sub setup_unrolled_binsearch {

  # Just the wrapper for the search tree:
  my $code = "sub unrolled_binsearch {\n" .
             "  my \$num = shift;\n\n" .
             create_binsearchcode_recursion( 0, $#netdata, 1 ) .
             "\n  die \"Error in unrolled_binsearch(): unhandled input: 
\$num\"\n" .
             "}\n\n" .
             "1;\n";

  ## print $code for visual inspection
  print "\n===[dynamically created 
code]===\n\n$code================================\n\n";

  # wipe previous function definition so we are not warned
  # about a redefinition of this function
  undef &unrolled_binsearch;

  # eval the code we just created to instantiate our subroutine
  eval( $code ) or die "eval() failed: $@";
}



#
# Initialize the data structures used by is_local_ip()
#
# e.g. the binary search mechanism shown above
#

sub parse_localnets {

  foreach my $network ( keys %localnets ){
    my( $netnum, $mask ) = split( /\//, $network);

    # Convert dotted decimal IP network address and number of mask bits
    # to numbers representing those values. Please not that we operate
    # with host byte order, not network byte order.
    $netnum = ip_to_number( $netnum );
    $mask = make_mask( $mask );

    # Sanity check: Network number ANDed to the host mask must be 0
    die "IP is not a network number in '$network'!\n" if $netnum & $mask;

    push @netdata, [ $netnum, $netnum + $mask, $localnets{ $network } ];
  }

  # sort the networks, since our binary search requires that the
  # local ip ranges be sorted in ascending order.
  @netdata = sort { $a->[0] <=> $b->[0] } @netdata;

  setup_unrolled_binsearch();
}



#
# Check wether a particular IP is part of the local network
#

sub is_local_ip {
  return( unrolled_binsearch( ip_to_number( $_[0] ) ) ? 1 : 0 );
}


# initialize locality search tree
parse_localnets();

# search
while( my $ip = <DATA> ){
  chomp( $ip );
  print "$ip is " . ( is_local_ip( $ip ) ? '' : 'not ' ) . "in a local network 
range.\n";
}

__DATA__
59.32.232.33
60.1.1.1

--
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
http://learn.perl.org/


Reply via email to