Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Matching Sequential IP Addresses

by Dru (Hermit)
on Apr 03, 2008 at 18:18 UTC ( #678234=perlquestion: print w/replies, xml ) Need Help??
Dru has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks,

Two questions:

1.) I have a bunch of ip addresses saved to a hash, with the ip being the hash key. I would like to know those networks (Class C's) that have the last octet in a sequential order from 0.255. For example:
192.168.1.0 192.168.1.1 192.168.1.2 . . 192.168.1.255
I honestly can not think of an easy way to do this. The only thing I can think of is have 255 if statements and if they all match, I have what I am looking for. I know this is just asinine and I would rather jump off the nearest bridge.

2.) Since I am trying to expand my limited knowledge of map and grep functions, I would be most gratefull if someone could post a solution using either or both of those, which from what I understand of them is probably my best choice.

The closes I can get with grep is the following:
my @segments = grep {/^(\d{1,3}\.\d{1,3}\.\d{1,3})/} keys %hashIPs;
Although this saves the entire ip, rather then the first three octets, which is what I am after. Is it possible to save $1 with grep?


Thanks,
Dru

Perl, the Leatherman of Programming languages. - qazwart

Replies are listed 'Best First'.
Re: Matching Sequential IP Addresses
by Joost (Canon) on Apr 03, 2008 at 18:58 UTC
    I don't exactly understand the question, but looking at your grep example, you probably want map instead. grep should only be used to match entities and pass them through unmodified, map can be used to process entities and change them into something else:
    my @segments = map { /^(\d{1,3}\.\d{1,3}\.\d{1,3})/ ? $1 : () } keys %hashIPs;
    this pushes $1 onto @segments for each match, and the empty list, IOW: nothing, for no match.

    Note that as far as I can see, this is NOT what you want in your case.

    Note also that hashes are unordered, so asking about a sequential order of keys is nonsense: they're always unordered unless you order them explicitly using (for instance) my @ordered = sort keys %hash).

Re: Matching Sequential IP Addresses
by poolpi (Hermit) on Apr 03, 2008 at 18:51 UTC


    map {  /$RE{net}{IPv4}{-keep}/ and print "last octet => $5\n" } keys %hashIPs;

    hth,
    PooLpi

    'Ebry haffa hoe hab im tik a bush'. Jamaican proverb

      Took me a while to realize that idiom's from Regexp::Common::net. I'm not normally a big fan of advocating non-core modules should be in the core, but I would vote for including Regexp::Common.

      -derby
Re: Matching Sequential IP Addresses
by Limbic~Region (Chancellor) on Apr 03, 2008 at 20:09 UTC
    Dru,
    This sounds like a very fun problem. Unfortunately, I don't have time to work out a solution for you. Here is the algorithm I would start with:
    my %class_c; for my $ip (keys %IPs) { my $class_c = get_class_c_from_ip($ip); my $integer = convert_quad_ip_to_int($ip); my $bit = $integer - first_integer_in_class_c($class_c); vec($class_c{$class_c}, $bit, 1) = 1; } for $network (keys %class_c) { print "$network\n" if all_bits_are_1($class_c{$network}); }

    Sorry it doesn't imply map and sorry I left out all the interesting subs but it is often important to lay out the structure of a solution first. You can then ensure that you can make it work and refactor later.

    Cheers - L~R

Re: Matching Sequential IP Addresses
by ikegami (Pope) on Apr 03, 2008 at 21:41 UTC

    I'm having a hard time understanding what you are trying to do. I think you are trying to list every 24 bit subnet* whose 256 IP addresses are included in a given list of IP addresses (keys %hashIPs). Correct me if I'm wrong.

    Since keys %hashIPs can't contain any duplicates, all you have to do is count how many addresses are in each subnet.

    For starters, it's much easier to work with IP addresses as numbers so that numerical operators can be used. It's also easy to work on them in packed form, since most numerical operators will also work on those.

    use strict; use warnings; sub pack_ipv4 { my $dotted_ip = @_ ? $_[0] : $_; return pack('C4', split(/\./, $dotted_ip)); } sub unpack_ipv4 { my $packed_ip = @_ ? $_[0] : $_; return join('.', unpack('C4', $packed_ip)); } sub get_subnet { my ($packed_ip, $subnet_size) = @_; my $packed_mask = pack('B32', ('1' x $subnet_size) . ('0' x (32-$su +bnet_size))); return $packed_ip & $packed_mask; } my %hashIPs = map { $_ => 1 } ( (map { "10.0.0.$_" } 0..255), (map { "10.0.1.$_" } 0..254), (map { "10.0.2.$_" } 0..255), ); my %count; for (keys %hashIPs) { ++$count{get_subnet(pack_ipv4($_), 24)}; } for (grep { $count{$_} == 256 } keys %count) { print(unpack_ipv4($_), "/24\n"); }

    The bottom bit could also be written as

    my %count; print("$_/24\n") for map unpack_ipv4, grep ++$count{$_} == 256, map get_subnet($_, 24), map pack_ipv4, keys %hashIPs;

    * — There's really no such thing as subnet classes anymore. What used to be a "Class C subnet" is now a "24 bit subnet" or "subnet with mask 255.255.255.0". You're doing yourself a disservice by thinking in terms of classes.

Re: Matching Sequential IP Addresses
by apl (Monsignor) on Apr 03, 2008 at 18:48 UTC
    The only thing I can think of is have 255 if statements

    Set a state variable to 1. Use a foreach. Construct the IP address. If it's not valid, set the state to 0 and exit the loop.

Re: Matching Sequential IP Addresses
by runrig (Abbot) on Apr 03, 2008 at 19:08 UTC
    use Socket qw(inet_aton); my $start = inet_aton('192.168.1.0'); my $end = inet_aton('192.168.1.255'); my @ips_in_range = grep { my $n = inet_aton($_); $n ge $start and $n l +e $end } @ips;
    Update: Hmm, probably not what you're looking for...but still, maybe something along these lines, not in a single regex

    Maybe...sort all ips by inet_aton order uniquely into an array. For all ips that end in ".0", see if the ip 255 slots down in the array is the same but ending in ".255" (then you know you have 0-255).

Re: Matching Sequential IP Addresses
by NetWallah (Canon) on Apr 03, 2008 at 23:32 UTC
    If I understand your requirements correctly, you are trying to see if a given IP address is part of a class C (or a derivative of that problem: which of a particular Class-C address is missing).

    The NetAddr::IP module contains several functions that can help:

    • $me->contains($other)
      Returns true when $me completely contains $other. False is returned otherwise and undef is returned if $me and $other are not both NetAddr::IP objects.
    • ->network()
      Returns a new object refering to the network address of a given subnet. A network address has all zero bits where the bits of the netmask are zero. Normally this is used to refer to a subnet.
    • ->hostenum()
      Returns the list of hosts within a subnet.
    When dealing with IP addresses, this module will significantly reduce development time, improve readability, and decrease bugs. At least, it did, for me.

         "As you get older three things happen. The first is your memory goes, and I can't remember the other two... " - Sir Norman Wisdom

Re: Matching Sequential IP Addresses
by BrowserUk (Pope) on Apr 03, 2008 at 21:19 UTC

    Assuming that you want to ensure the hash contains all the IPs with the first 3 octets + 0..255:

    die "IPs missing" unless keys %hashIPs == 256; my $base = each( %hashIPs) =~ s[(\.\d+$)][]; exists $hashIPs{ $base . $_ } or die "Missing IP $base.$_" for 0 .. 25 +5;

    That assumes that there are no strays in the hash, but it's not clear from your description what it might contain.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Matching Sequential IP Addresses
by jwkrahn (Monsignor) on Apr 03, 2008 at 21:21 UTC
    1. This may be what you want:

      $ perl -le' my %hashIPs = qw( 192.168.1.0 1 192.168.1.1 1 192.168.1.2 1 192.168.1.4 1 192.168.1.5 1 192.168.1.6 1 192.168.1.10 1 192.168.1.11 1 192.168.1.12 1 ); my %check; for my $ip ( keys %hashIPs ) { next unless $ip =~ /^(\d+\.\d+\.\d+)\.(\d+)$/; $check{ $1 } = 0 x 256 unless exists $check{ $1 }; substr $check{ $1 }, $2, 1, 1; } for my $key ( keys %check ) { print "Missing: $key.$-[0] - $key.", $+[0] - 1, "\n" while $check{ $key } =~ /(?<=1)0+(?=1)|^0+|0+$/g; } ' Missing: 192.168.1.3 - 192.168.1.3 Missing: 192.168.1.7 - 192.168.1.9 Missing: 192.168.1.13 - 192.168.1.255
    2. Instead of using grep:

      my @segments = grep {/^(\d{1,3}\.\d{1,3}\.\d{1,3})/} keys %hashIPs;

      You need to use map:

      my @segments = map /^(\d{1,3}\.\d{1,3}\.\d{1,3})/, keys %hashIPs;
Re: Matching Sequential IP Addresses
by Dru (Hermit) on Apr 07, 2008 at 03:43 UTC
    Thank you all for the great responses. I actually found a fairly simple way (although not using grep or map) to do what I needed to do:
    my %hashSeg; for my $ip (sort keys %hashIPs) { if ($ip =~ /(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}/){ my $seg = $1; $hashSeg{$seg}++; } } for my $seg (keys %hashSeg){ if ($hashSeg{$seg} ne '255'){ print "$seg is not a full \/24\n"; } else { print "$seg is a full \/24\n"; } }
    Thanks,
    Dru

    Perl, the Leatherman of Programming languages. - qazwart
      ne '255' should be != 255. Shouldn't that be 256 anyway? (0..255 is 256 numbers.)

        A standard Class C network consists of 256 addresses (0 to 255 inclusive), of which one is the network address (.0) and the other is the network broadcast address (.255). Although there are exceptions you can't really use .0 or .255 so there are really only 254 useable addresses.

        As a policy I would use < 255 myself as would deal with edge cases where ips are repeated more than once. I have some trial software that amuses me with it's message that it expires in -119 days. Clearly they coded an == rather than a < 0.

        To make this stype of solution reliable you need a pre-filter to exclude duplicate ips from the list as our logic requires that they must be unique. In this case the OP is using that so all will be well.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://678234]
Approved by herveus
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (3)
As of 2018-07-20 22:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    It has been suggested to rename Perl 6 in order to boost its marketing potential. Which name would you prefer?















    Results (441 votes). Check out past polls.

    Notices?