http://www.perlmonks.org?node_id=119206
Category: Networking Code
Author/Contact Info runrig
Description: Inspired by Dominus' Challenge Problem: Merging Network Addresses, I posted a reply script which was (about 20 times) faster than the Net::CIDR solution, and thought I'd make a module out of it. Also looking for comments on whether it ought to be on CPAN, and under what name. I'd never before heard of let alone used the Socket::inet_* functions, so I couldn't have done it this way without that thread. It might be interesting to get this to work optionally with IPv6 addresses, but then you'd probably have to use some big integer library like Bit::Vector, so I'm open to suggestions on that :)

Updated with tye's recommendation.

Update: Net::CIDR::Lite has been on CPAN for awhile now and updated several times over. Consider the code on this page obsolete.

Sample usage:
use Net::CIDR::Lite;
my $cidr = Net::CIDR::Lite->new;

$cidr->add("209.152.214.112/30");
$cidr->add("209.152.214.116/31");
$cidr->add("209.152.214.118/31");

print "$_\n" for $cidr->list;

And the module:
##################################
package Net::CIDR::Lite;

use strict;
use Socket qw(inet_aton inet_ntoa);

use vars qw($VERSION);

$VERSION = '0.01';

my @masks = (0,0,map { pack("B*", substr("1" x $_ . "0" x 32, 0, 32)) 
+} 2..32);
my @bits2rng = (0,0,map { 2**(32 - $_) } 2..32);
my %rng2bits = map { $bits2rng[$_] => $_ } 0..32;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    bless {}, $class;
}

sub add {
    my $self = shift;
    local $_ = shift;
    my ($ip, $mask) = split "/";
    my $start = inet_aton($ip) & $masks[$mask];
    my $end = pack("N", unpack("N", $start) + $bits2rng[$mask]);
    $$self{$start}++;
    $$self{$end}--;
}

sub clean {
    my $self = shift;
    %$self = map { $$self{$_} ? ($_ => $$self{$_}) : () } keys %$self;
}

sub list {
    my $self = shift;
    my ($start, $total);
    my @results;
    for my $ip (sort keys %$self) {
        $start = $ip unless $total;
        $total += $$self{$ip};
        unless ($total) {
            my $diff = unpack("N", $ip) - unpack("N", $start);
            while ($diff) {
                (my $zeros = unpack("B*", $start)) =~ s/^.*1//;
                my $range;
                for my $i (32-length($zeros)..32) {
                    $range = $bits2rng[$i], last if $bits2rng[$i] <= $
+diff;
                }
                push @results, inet_ntoa($start)."/".$rng2bits{$range}
+;
                $diff -= $range;
                $start = pack("N", unpack("N", $start)+$range);
            }
        }
    }
    wantarray ? @results : \@results;
}

1;
__END__

=head1 NAME

Net::CIDR::Lite - Perl extension for merging CIDR addresses

=head1 SYNOPSIS

  use Net::CIDR::Lite;

  my $cidr = Net::CIDR::Lite->new;
  $cidr->add($cidr_address);
  @cidr_list = $cidr->list;

=head1 DESCRIPTION

Faster alternative to Net::CIDR::cidradd. Limited
for the time being to IPv4 addresses.

=head1 METHODS

=item new() 

 $cidr = Net::CIDR::Lite->new

Creates an object to represent a list of CIDR address ranges.

=item add()

 $cidr->add($cidr_address)

Adds a CIDR address range to the list.

=item $cidr->clean()

 $cidr->clean;

If you are going to call the list method more than once on the
same data, then for optimal performance, you can call this to
purge null nodes from the list.

=item $cidr->list()

 @cidr_list = $cidr->list;

Returns a list of the merged CIDR addresses.

=head1 CAVEATS

Garbage in/garbage out. This module makes no attempt to validate
the format of your data.

=head1 AUTHOR
runrig
=head1 COPYRIGHT

 This module is free software; you can redistribute it and/or
 modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::CIDR>.

=cut