Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

finding if an ip is in a subnet

by AltGrendel (Acolyte)
on Mar 05, 2014 at 15:58 UTC ( [id://1077095]=perlquestion: print w/replies, xml ) Need Help??

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

This is frustrating. I have one file with a list of arbitrary source IP addresses. In another file I have subnet,netmask,dest-hostname for that subnet. I need to find a quick way to find out if that IP is in a particular subnet. I was thinking that something like this would help, but there are over 4500 ip addresses and 1500 subnets to go through.

Please note. I have some severe limitations with this.

1) This is running on a Solaris 5.8 box, perl version is 5.005_03

2) I do not have root access.

3) I do not have access to CPAN.

4) Only core modules are installed.

Any help/suggestions would be appreciated.

Replies are listed 'Best First'.
Re: finding if an ip is in a subnet
by atcroft (Abbot) on Mar 05, 2014 at 19:09 UTC

    While I think there are better methods of doing this (a horse that will be beaten long after it is turning to dust), within the criteria given the following method would work, given here as a small test script (debug code left in place). (Reading the IP information into a similar structure is left as an exercise to the reader.) The central trick is converting the IP in question, the network address, and netmask to unsigned numbers, then performing a binary AND of the IP and netmask and the network address and netmask, and see if the results match.

    Actually, after testing the code at the bottom of this post, I realized there was a quicker way-build a hash of the results of AND on the network address and netmask, then loop through the IPs and netmasks and look for a match.

    #!perl use strict; use warnings; use Data::Dumper; $Data::Dumper::Deepcopy = 1; $Data::Dumper::Sortkeys = 1; $| = 1; srand(); my $_DEBUG = 0; my %known = ( q{192.168.0.0} => { network => q{192.168.0.0}, mask => q{24}, }, q{192.168.42.128} => { network => q{192.168.42.128}, mask => q{255.255.255.128}, }, q{192.168.127.0} => { network => q{192.168.127.0}, mask => q{255.255.255.0}, }, ); my @test = ( q{192.168.0.1}, q{192.168.42.25}, q{192.168.42.192}, q{192.168.127.0}, q{192.168.127.10}, q{192.168.127.255}, ); my %seen; foreach my $k ( keys %known ) { my $n = str2n( $known{$k}{network} ); my $m; if ( $known{$k}{mask} !~ m/\./ ) { $m = 0xFFFFFFFF << ( 32 - $known{$k}{mask} ); } else { $m = str2n( $known{$k}{mask} ); } $seen{$m}{ $n & $m } = $k; } TESTING: foreach my $ip (@test) { my $i = str2n($ip); foreach my $m ( sort { $b <=> $a } keys %seen ) { my $result = $i & $m; if ( defined( $seen{$m}{ $i & $m } ) ) { my $k = $seen{$m}{$result}; print sprintf qq{%s in %s / %s\n}, $ip, $known{$k}{network}, $known{$k}{mask}; next TESTING; } } print sprintf qq{%s not in provided ranges\n}, $ip; } print Data::Dumper->Dump( [ \@test, \%known, \%seen, ], [qw( *test *known *seen )] ), qq{\n} if ($_DEBUG); sub str2n { print sprintf( qq{\t\t%d %s\n\t\t%d %s\n\t\t%d %d\n}, __LINE__, $_[0], __LINE__, join( q{ }, split /\D/, $_[0] ), __LINE__, unpack( q{N}, pack( q{C4}, split /\D/, $_[0] ), ) ) if ($_DEBUG); return unpack( q{N}, pack( q{C4}, split /\D/, $_[0] ) ); } # Output: # # $ perl test-20140305-01.pl # 192.168.0.1 in 192.168.0.0 / 24 # 192.168.42.25 not in provided ranges # 192.168.42.192 in 192.168.42.128 / 255.255.255.128 # 192.168.127.0 in 192.168.127.0 / 255.255.255.0 # 192.168.127.10 in 192.168.127.0 / 255.255.255.0 # 192.168.127.255 in 192.168.127.0 / 255.255.255.0 #

    Original code:

    Hope that helps.

    Update: 2014-03-05
    Updated code to remove $flag variable.

Re: finding if an ip is in a subnet
by McA (Priest) on Mar 05, 2014 at 16:12 UTC

    Hi,

    in the thread referenced by you there is an answer from BrowserUK (Re: Iterating through all IP addresses in a CIDR) where he presented a solution to enumerate every Host-IP in a subnet. When you build up a hash with all generated hosts you can check your IP addresses with a exists lookup into that hash.

    You need memory for that. But in your list you didn't say you don't have that. :-)

    McA

Re: finding if an ip is in a subnet
by hazylife (Monk) on Mar 05, 2014 at 17:12 UTC
    How about this. Put your subnets in a hash table of the following form:
    %subnets = ( "$subnet1/$prefix_length1" => undef, "$subnet2/$prefix_le +ngth2" => undef, ... );
    Then for each IP address, build a list of "$subnet/$prefix_length" pairs covering all the subnets it could theoretically belong to.
      Here's a sample implementation of the above outline:
      #!/usr/bin/perl -w use strict; # BrowserUk's conversion subs sub bin2dd{ join '.', unpack 'C4', pack 'N', $_[0] } sub dd2bin{ unpack 'N', pack 'C4', split '\.', $_[0] } my @ips; push @ips, shift while @ARGV && -1 == index $ARGV[0], '/'; die "usage: $0 IPs subnets\n" unless @ips && @ARGV; my %subnets; foreach my $subnet (@ARGV) { my ($net, $plen) = split /\//, $subnet; $net .= '.0' x (3 - $net =~ tr/.//); my $net_bin = dd2bin $net; my $zero_bits = ~(~0 << 32-$plen); if ($net_bin & $zero_bits) { print STDERR "warning: fixing $net/$plen => "; $net_bin &= ~$zero_bits; $net = bin2dd $net_bin; print STDERR "$net/$plen\n"; } $subnets{"$net_bin/$plen"} = "$net/$plen"; } foreach my $ip (@ips) { my $ip_bin = dd2bin $ip; foreach my $plen (8..32) { my $key = (~0 << 32-$plen & $ip_bin) . "/$plen"; if (exists $subnets{$key}) { print "$ip matches $subnets{$key}\n"; # last; } } } __END__ $ ip-in-subnet.pl 192.168.1.7 64.96.128.11 2.219.155.11 \ 8.8.8.8 64.96.128.0/28 64.96.128.0/17 64.96.128.11/32 64/8 \ 192.168.1.0/24 192.168/16 8/8 2.219/16 192.168.1.7 matches 192.168.0.0/16 192.168.1.7 matches 192.168.1.0/24 64.96.128.11 matches 64.0.0.0/8 64.96.128.11 matches 64.96.128.0/17 64.96.128.11 matches 64.96.128.0/28 64.96.128.11 matches 64.96.128.11/32 2.219.155.11 matches 2.219.0.0/16 8.8.8.8 matches 8.0.0.0/8
      Update 1: bloody 'noexpandtab'!
      Update 2: added a bit of error checking
Re: finding if an ip is in a subnet
by runrig (Abbot) on Mar 05, 2014 at 18:19 UTC
Re: finding if an ip is in a subnet
by Anonymous Monk on Mar 05, 2014 at 16:24 UTC
    Even with 4500 IPs and 1500 subnets, it would just take a bit of time to run through two nested loops to obtain an answer that is presumably needed only once. A fairly brute force approach should work nicely, and the total memory requirement will be quite small.
Re: finding if an ip is in a subnet
by Lennotoecom (Pilgrim) on Mar 06, 2014 at 20:59 UTC
    @ip = qw\10.0.0.1 10.1.2.3 192.168.12.5 77.75.0.1\; @subnets = qw\10.0.0.0/24 10.1.0.0/16 192.168.0.0/12\; foreach $ip (@ip){ @a = split /\./, $ip; $di = getIp(@a); foreach $subnet (@subnets){ ($a, $b) = getNetwork($subnet); if(($di >= $a) && ($di <= $b)){print "$ip in $subnet\n";} } } sub getIp { return ($_[0]*256*256*256) + ($_[1]*256*256) + ($_[2]*256) + $_[3] +; } sub getNetwork { @a = split(/[\/|\.]/, +shift); return (getIp(@a[0 .. 3]), (getIp(@a[0 .. 3]) + (2 ** (32 - $a[4]) +))); }

      This is not working, "(getIp(@a[0 .. 3]) + (2 ** (32 - $a[4])".

      By example 10.3.3.0/30 use 10.3.3.0 to 10.3.3.3 but with your method it will return 10.3.3.0 to 10.3.3.4 because 10.3.3.0 + 2 ** 2 = 10.3.3.4

      you need to add '-1' to this, like "(2 ** (32 - $a[4])) - 1"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1077095]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (6)
As of 2024-04-24 09:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found