http://www.perlmonks.org?node_id=1077139


in reply to finding if an ip is in a subnet

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:

#!perl use strict; use warnings; use Data::Dumper; $| = 1; srand(); my $_DEBUG = 0; my %known = ( 192.168.0.0 => { network => q{192.168.0.0}, mask => q{24}, }, 192.168.42.128 => { network => q{192.168.42.128}, mask => q{255.255.255.128}, }, 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}, ); TESTING: foreach my $ip (@test) { my $i = str2n($ip); print sprintf( qq{%d %s: %d (0x%8x)\n}, __LINE__, $ip, $i, $i, ) if ($_DEBUG); foreach my $k ( keys %known ) { print sprintf( qq{\t%d %s / %s\n}, __LINE__, $known{$k}{network}, $known{$k}{mask}, ) if ($_DEBUG); my ( $m, $n, ); $n = str2n( $known{$k}{network} ); if ( $known{$k}{mask} !~ m/\./ ) { $m = 0xFFFFFFFF << ( 32 - $known{$k}{mask} ); } else { $m = str2n( $known{$k}{mask} ); } print sprintf( qq{\t%d %d / %d\n}, __LINE__, $n, $m, ) if ($_DEBUG); my $n_m = $n & $m; my $i_m = $i & $m; print sprintf( qq{\tTest range: %d / %d\n} . qq{\tnet & mask: %d\n} . qq{ip & mask: %d\nResults: %d\n}, $n, $m, $n_m, $i_m, $i_m == $n_m ) if ($_DEBUG); if ( $n_m == $i_m ) { print $ip, q{ in }, $known{$k}{network}, q{/}, $known{$k}{mask}, qq{\n}; next TESTING; } } print $ip, qq{ not in provided ranges\n}; } 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-00.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 #

Hope that helps.

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