strredwolf has asked for the wisdom of the Perl Monks concerning the following question:

I need to check an arbitrary IP address ("192.168.1.2") with an IP/mask setup ("10.0.2.0/24"). Has anyone done some code on this before I pull the source to tcpwrappers and do a port job? (I'm trying to impliment an by-IP checker for my mail filters)

--
$Stalag99{"URL"}="http://stalag99.keenspace.com";

Replies are listed 'Best First'.
Re: Checking IP's with an IP/mask
by jwest (Friar) on Apr 24, 2001 at 00:21 UTC
    Unfortunately, this is a terse answer, as I haven't done this myself before, but Net::Netmask should do the trick for you, I'd think.

    use Net::Netmask; $block=new Net::Netmask("10.0.2.0/24"); if ($block->match("192.168.1.2")) { ... }
    Update: added code example

    -><- -><- -><- -><- -><-
    All things are Perfect
        To every last Flaw
        And bound in accord
             With Eris's Law
     - HBT; The Book of Advice, 1:7
    
      OUTSTANDING!

      #!/usr/bin/perl -wT #-*-perl-*- use strict; use Net::Netmask; my $block=new Net::Netmask( $ARGV[0] ); if ($block->match( $ARGV[1] ) ) { print $ARGV[1] , " exists in " , $ARGV[0] , "\n"; } else { print "CIDR = " , $block->desc() , "\n"; # a.b.c. +d/bits print "IP Address = " , $block->base() , "\n"; print "Netmask = " , $block->mask() , "\n"; print "Hostmask = " , $block->hostmask() , "\n"; print "Mask bits = " , $block->bits() , "\n"; print "Subnet size = " , $block->size() , "\n"; print "Max netmask = " , $block->maxblock() , "\n"; print "Broadcast = " , $block->broadcast() , "\n"; print "Next netmask = " , $block->next() , "\n"; print "First host = " , $block->nth(1) , "\n"; }

      I wish I had found this module a few years ago! My binary math is quite slow.

      THX!
      --
      idnopheq
      Apply yourself to new problems without preparation, develop confidence in your ability to to meet situations as they arrise.

Re: Checking IP's with an IP/mask
by tadman (Prior) on Apr 24, 2001 at 00:24 UTC
    There might be a CPAN module to do this, but in case there isn't:
    #!/usr/bin/perl -w use strict; use Socket; sub MaskbitsToNetmask { my ($bits) = @_; return 0xFFFFFFFF-((1<<(32-$bits))-1); } sub MaskToRange { my ($range) = @_; my ($ip,$mask) = split (/\//, $range); $ip = CompactIP($ip); return ($ip, $ip | (1<<(32-$mask) - 1)); } sub ExpandIP ($) { return inet_ntoa (pack ("N", @_)); } sub CompactIP ($) { return unpack ("N", inet_aton (@_)); } sub IsInRange { my ($ip, $range) = @_; $ip = CompactIP($ip); my ($range_low, $range_hi) = MaskToRange ($range); return (($range_low <= $ip) && ($ip <= $range_hi)); }
    Here's some examples:
    print IsInRange ("192.168.2.42","192.168.0.0/16"),"\n"; # 1 print IsInRange ("192.168.2.42","192.168.0.0/24"),"\n"; # undef print IsInRange ("192.168.2.42","192.168.2.0/24"),"\n"; # 1 print IsInRange ("24.123.234.1","24.0.0.0/8"),"\n"; # 1
    Of course, this is just off the top, so YMMV.

    Some notes:
    • The numbers are converted to flat integers instead of the "dotted quad" notation used conventionally. This way, they can be compared numerically.
    • The "netmask" generation routine could be optimized, even to the point of Perl Golf. This one contains the essentials.
    • You must specify your entire network. Normally, you can get away with '24/8', but inet_ntoa gives you grief if you do that, interpreting it as '0.0.0.24' instead of '24.0.0.0'.
      I don't know much about network addresses, but isn't something like this sufficient?
      use Socket; sub IsInRange { my ($addr,$base) = @_; my $bits; ($base, $bits) = split /\//, $base; return ( (unpack("N", inet_aton($base)) >> (32 - $bits)) == (unpack ("N", inet_aton($addr)) >> (32 - $bits)) ); }
        This should work too, and is probably better because of simplicity.

        For those wondering, what this code effectively does is compare the network numbers of the $addr and the $base specification by shifting the host bits off the end (with the >> operator). What I was doing was building a range specification, which given the task, is really overkill.

        Well put.
Re: Checking IP's with an IP/mask
by strredwolf (Chaplain) on Apr 24, 2001 at 00:02 UTC
    To update, jwest says it's the CIDR notation I'm checking against. Wish I could update that node...

    --
    $Stalag99{"URL"}="http://stalag99.keenspace.com";

Re: Checking IP's with an IP/mask
by atcroft (Abbot) on Dec 11, 2001 at 08:05 UTC

    You mean to find out if the arbitrary IP is in the range given by the CIDR notation (such as is 192.168.1.2 in the range 10.0.2.0/24)?

    *sigh* I wish I had found this post earlier today, when I wrote my own function to do this (which someone else has probably already come up with, and easier, probably):

    sub inblock { my($block, $cidr, $target) = @_; my $result = 1, $machinebits = (32 - $cidr); my $lip1 = unpack("N", pack("c4", split(/\D/, $block, 4))); my $lip2 = unpack("N", pack("c4", split(/\D/, $target, 4))); $result = 0 if (($lip1 >> $machinebits) != ($lip2 >> $machinebits)); return($result); }
      Intresting, but I'm now using Net::Netmask for that now. Bloodtyger's happy for it.

      --
      $Stalag99{"URL"}="http://stalag99.keenspace.com";