File Coverage

blib/lib/Net/CIDR/Set.pm
Criterion Covered Total %
statement 205 226 90.7
branch 51 60 85.0
condition 16 29 55.1
subroutine 41 48 85.4
pod 25 26 96.1
total 338 389 86.8


line stmt bran cond sub pod time code
1             package Net::CIDR::Set;
2              
3 6     6   226549 use warnings;
  6         18  
  6         205  
4 6     6   32 use strict;
  6         11  
  6         353  
5 6     6   34 use Carp qw( croak confess );
  6         16  
  6         472  
6 6     6   4186 use Net::CIDR::Set::IPv4;
  6         14  
  6         205  
7 6     6   4609 use Net::CIDR::Set::IPv6;
  6         19  
  6         245  
8              
9 6     6   14738 use overload '""' => 'as_string';
  6         8527  
  6         50  
10              
11             our $VERSION = '0.13';
12              
13             =head1 NAME
14              
15             Net::CIDR::Set - Manipulate sets of IP addresses
16              
17             =head1 VERSION
18              
19             This document describes Net::CIDR::Set version 0.13
20              
21             =head1 SYNOPSIS
22              
23             use Net::CIDR::Set;
24              
25             my $priv = Net::CIDR::Set->new( '10.0.0.0/8', '172.16.0.0/12',
26             '192.168.0.0/16' );
27             for my $ip ( @addr ) {
28             if ( $priv->contains( $ip ) ) {
29             print "$ip is private\n";
30             }
31             }
32              
33             =head1 DESCRIPTION
34              
35             C represents sets of IP addresses and allows standard
36             set operations (union, intersection, membership test etc) to be
37             performed on them.
38              
39             In spite of the name it can work with sets consisting of arbitrary
40             ranges of IP addresses - not just CIDR blocks.
41              
42             Both IPv4 and IPv6 addresses are handled - but they may not be mixed in
43             the same set. You may explicitly set the personality of a set:
44              
45             my $ip4set = Net::CIDR::Set->new({ type => 'ipv4 }, '10.0.0.0/8');
46              
47             Normally this isn't necessary - the set will guess its personality from
48             the first data that is added to it.
49              
50             =head1 INTERFACE
51              
52             =head2 C<< new >>
53              
54             Create a new Net::CIDR::Set. All arguments are optional. May be passed a
55             list of list of IP addresses or ranges which, if present, will be
56             passed to C.
57              
58             The first argument may be a hash reference which will be inspected for
59             named options. Currently the only option that may be passed is C
60             which should be 'ipv4', 'ipv6' or the name of a coder class. See
61             L and L for examples of
62             coder classes.
63              
64             =cut
65              
66             {
67             my %type_map = (
68             ipv4 => 'Net::CIDR::Set::IPv4',
69             ipv6 => 'Net::CIDR::Set::IPv6',
70             );
71              
72             sub new {
73 36     36 1 4973 my $self = shift;
74 36   66     173 my $class = ref $self || $self;
75 36         148 my $set = bless { ranges => [] }, $class;
76 36 50       105 my $opt = 'HASH' eq ref $_[0] ? shift : {};
77 36 50       139 if ( defined( my $type = delete $opt->{type} ) ) {
    100          
78 0   0     0 my $coder_class = $type_map{$type} || $type;
79 0         0 $set->{coder} = $coder_class->new;
80             }
81             elsif ( ref $self ) {
82 16         36 $set->{coder} = $self->{coder};
83             }
84 36         173 my @unk = keys %$opt;
85 36 50       101 croak "Unknown options: ", _and( sort @unk ) if @unk;
86 36 100       115 $set->add( @_ ) if @_;
87 35         136 return $set;
88             }
89             }
90              
91             # Return the index of the first element >= the supplied value. If the
92             # supplied value is larger than any element in the list the returned
93             # value will be equal to the size of the list.
94             sub _find_pos {
95 118     118   148 my $self = shift;
96 118         138 my $val = shift;
97 118   100     348 my $low = shift || 0;
98              
99 118         122 my $high = scalar( @{ $self->{ranges} } );
  118         212  
100              
101 118         335 while ( $low < $high ) {
102 129         235 my $mid = int( ( $low + $high ) / 2 );
103 129         215 my $cmp = $val cmp $self->{ranges}[$mid];
104 129 100       313 if ( $cmp < 0 ) {
    100          
105 43         93 $high = $mid;
106             }
107             elsif ( $cmp > 0 ) {
108 64         149 $low = $mid + 1;
109             }
110             else {
111 22         48 return $mid;
112             }
113             }
114              
115 96         169 return $low;
116             }
117              
118             sub _inc {
119 396     396   20873 my @b = reverse unpack 'C*', shift;
120 396         783 for ( @b ) {
121 2083 100       3823 last unless ++$_ == 256;
122 1688         1783 $_ = 0;
123             }
124 396         1646 return pack 'C*', reverse @b;
125             }
126              
127             sub _dec {
128 118     118   2903 my @b = reverse unpack 'C*', shift;
129 118         243 for ( @b ) {
130 437 100       823 last unless $_-- == 0;
131 320         361 $_ = 255;
132             }
133 118         611 return pack 'C*', reverse @b;
134             }
135              
136             sub _guess_coder {
137 18     18   30 my ( $self, $ip ) = @_;
138 18         28 for my $class ( qw( Net::CIDR::Set::IPv4 Net::CIDR::Set::IPv6 ) ) {
139 24         114 my $coder = $class->new;
140 24         41 my @rep = eval { $coder->encode( $ip ) };
  24         75  
141 24 100       507 return $coder unless $@;
142             }
143 3         341 croak "Can't decode $ip as an IPv4 or IPv6 address";
144             }
145              
146             sub _encode {
147 47     47   139 my ( $self, $ip ) = @_;
148 47   66     320 my $cdr = $self->{coder} ||= $self->_guess_coder( $ip );
149 44         137 return $cdr->encode( $ip );
150             }
151              
152             {
153             for my $dele ( qw( _decode _nbits ) ) {
154 6     6   6035 no strict 'refs';
  6         11  
  6         19641  
155             ( my $meth = $dele ) =~ s/^_//;
156             *{$dele} = sub {
157 67     67   84 my $self = shift;
158 67   33     179 my $cdr = $self->{coder} || croak "Don't know how to $meth yet";
159 67         222 return $cdr->$meth( @_ );
160             };
161             }
162             }
163              
164             sub _conjunction {
165 2     2   437 my ( $conj, @list ) = @_;
166 2         3 my $last = pop @list;
167 2         12 return join " $conj ", join( ', ', @list ), $last;
168             }
169              
170 1     1   2 sub _and { _conjunction( 'and', @_ ) }
171              
172             sub _check_and_coerce {
173 9     9   16 my ( $self, @others ) = @_;
174              
175             my %class = map {
176 20   100     23 eval { ( defined $_ && $_->nbits || '' ) => $_ }
  20         133  
  20         38  
177 9         15 } map { $_->{coder} } grep { defined } $self, @others;
  20         40  
178              
179 9         40 my @found = sort grep $_, keys %class;
180              
181 9 50       27 croak "Can't mix ", _and( @found ), " bit addresses"
182             if @found > 1;
183              
184 9   66     37 $self->{coder} ||= $class{ $found[0] };
185 9         25 return $self;
186             }
187              
188             =head2 C<< invert >>
189              
190             Invert (negate, complement) a set in-place.
191              
192             my $set = Net::CIDR::Set->new;
193             $set->invert;
194              
195             =cut
196              
197             sub invert {
198 15     15 1 22 my $self = shift;
199              
200 15         33 my @pad = ( 0 ) x ( $self->_nbits / 8 );
201 15         53 my ( $min, $max ) = map { pack 'C*', $_, @pad } 0, 1;
  30         88  
202              
203 15 50       41 if ( $self->is_empty ) {
204 0         0 $self->{ranges} = [ $min, $max ];
205 0         0 return;
206             }
207              
208 15 100       46 if ( $self->{ranges}[0] eq $min ) {
209 8         11 shift @{ $self->{ranges} };
  8         15  
210             }
211             else {
212 7         8 unshift @{ $self->{ranges} }, $min;
  7         26  
213             }
214              
215 15 100       41 if ( $self->{ranges}[-1] eq $max ) {
216 8         10 pop @{ $self->{ranges} };
  8         21  
217             }
218             else {
219 7         9 push @{ $self->{ranges} }, $max;
  7         25  
220             }
221             }
222              
223             =head2 C<< copy >>
224              
225             Make a deep copy of a set.
226              
227             my $set2 = $set->copy;
228              
229             =cut
230              
231             sub copy {
232 16     16 1 22 my $self = shift;
233 16         32 my $copy = $self->new;
234 16         20 @{ $copy->{ranges} } = @{ $self->{ranges} };
  16         47  
  16         33  
235 16         31 return $copy;
236             }
237              
238             sub _add_range {
239 59     59   100 my ( $self, $from, $to ) = @_;
240 59         109 my $fpos = $self->_find_pos( $from );
241 59         114 my $tpos = $self->_find_pos( _inc( $to ), $fpos );
242              
243 59 100       158 $from = $self->{ranges}[ --$fpos ] if ( $fpos & 1 );
244 59 100       122 $to = $self->{ranges}[ $tpos++ ] if ( $tpos & 1 );
245              
246 59         66 splice @{ $self->{ranges} }, $fpos, $tpos - $fpos, ( $from, $to );
  59         306  
247             }
248              
249             =head2 C<< add >>
250              
251             Add a number of addresses or ranges to a set.
252              
253             $set->add(
254             '10.0.0.0/8',
255             '192.168.0.32-192.168.0.63',
256             '127.0.0.1'
257             );
258              
259             It is legal to add ranges that overlap with each other and/or with the
260             ranges already in the set. Overlapping ranges are merged.
261              
262             =cut
263              
264             sub add {
265 20     20 1 1428 my ( $self, @addr ) = @_;
266 20         35 for my $ip ( map { split /\s*,\s*/ } @addr ) {
  47         137  
267 47 50       108 my ( $lo, $hi ) = $self->_encode( $ip )
268             or croak "Can't decode $ip";
269 43         113 $self->_add_range( $lo, $hi );
270             }
271             }
272              
273             =head2 C<< remove >>
274              
275             Remove a number of addresses or ranges from a set.
276              
277             $set->remove(
278             '8.8.0.0/16',
279             '158.152.1.58'
280             );
281              
282             There is no requirement that the addresses being removed be members
283             of the set.
284              
285             =cut
286              
287             sub remove {
288 1     1 1 518 my $self = shift;
289              
290 1         15 $self->invert;
291 1         3 $self->add( @_ );
292 1         3 $self->invert;
293             }
294              
295             =head2 C<< merge >>
296              
297             Merge the contents of other sets into this set.
298              
299             $set = Net::CIDR::Set->new;
300             $set->merge($s1, $s2);
301              
302             =cut
303              
304             sub merge {
305 9     9 1 12 my $self = shift;
306 9         26 $self->_check_and_coerce( @_ );
307              
308             # TODO: This isn't very efficient - and merge gets called from all
309             # sorts of other places.
310 9         18 for my $other ( @_ ) {
311 11         27 my $iter = $other->_iterate_runs;
312 11         25 while ( my ( $from, $to ) = $iter->() ) {
313 16         33 $self->_add_range( $from, $to );
314             }
315             }
316             }
317              
318             =head2 C<< contains >>
319              
320             A synonmym for C.
321              
322             =head2 C<< contains_all >>
323              
324             Return true if the set contains all of the supplied addresses.
325             Given this set:
326              
327             my $set = Net::CIDR::Set->new('244.188.12.0/8');
328              
329             this condition is true:
330              
331             if ( $set->contains_all('244.188.12.128/3') ) {
332             # ...
333             }
334              
335             while this condition is false:
336              
337             if ( $set->contains_all('244.188.12.0/12') ) {
338             # ...
339             }
340              
341             =cut
342              
343             *contains = *contains_all;
344              
345             sub contains_all {
346 0     0 1 0 my $self = shift;
347 0         0 my $class = ref $self;
348 0         0 return $class->new( @_ )->subset( $self );
349             }
350              
351             =head2 C<< contains_any >>
352              
353             Return true if there is any overlap between the supplied
354             addresses/ranges and the contents of the set.
355              
356             =cut
357              
358             sub contains_any {
359 0     0 1 0 my $self = shift;
360 0         0 my $class = ref $self;
361 0         0 return !$class->new( @_ )->intersection( $self )->is_empty;
362             }
363              
364             sub _iterate_runs {
365 24     24   35 my $self = shift;
366              
367 24         29 my $pos = 0;
368 24         26 my $limit = scalar( @{ $self->{ranges} } );
  24         46  
369              
370             return sub {
371 69 100   69   259 return if $pos >= $limit;
372 45         63 my @r = @{ $self->{ranges} }[ $pos, $pos + 1 ];
  45         134  
373 45         60 $pos += 2;
374 45         173 return @r;
375 24         137 };
376             }
377              
378             sub compliment {
379 0     0 0 0 croak "That's very kind of you - but I expect you meant complement";
380             }
381              
382             =head2 C<< complement >>
383              
384             Return a new set that is the complement of this set.
385              
386             my $inv = $set->complement;
387              
388             =cut
389              
390             sub complement {
391 10     10 1 23 my $new = shift->copy;
392             # TODO: What if it's empty?
393 10         24 $new->invert;
394 10         33 return $new;
395             }
396              
397             =head2 C<< union >>
398              
399             Return a new set that is the union of a number of sets. This is
400             equivalent to a logical OR between sets.
401              
402             my $everything = $east->union($west);
403              
404             =cut
405              
406             sub union {
407 6     6 1 30 my $new = shift->copy;
408 6         32 $new->merge( @_ );
409 6         22 return $new;
410             }
411              
412             =head2 C<< intersection >>
413              
414             Return a new set that is the intersection of a number of sets. This is
415             equivalent to a logical AND between sets.
416              
417             my $overlap = $north->intersection($south);
418              
419             =cut
420              
421             sub intersection {
422 3     3 1 10 my $self = shift;
423 3         6 my $class = ref $self;
424 3         8 my $new = $class->new;
425 3         7 $new->merge( map { $_->complement } $self, @_ );
  6         57  
426 3         21 $new->invert;
427 3         11 return $new;
428             }
429              
430             =head2 C<< xor >>
431              
432             Return a new set that is the exclusive-or of existing sets.
433              
434             my $xset = $this->xor($that);
435              
436             The resulting set will contain all addresses that are members of one set
437             but not the other.
438              
439             =cut
440              
441             sub xor {
442 0     0 1 0 my $self = shift;
443 0         0 return $self->union( @_ )
444             ->intersection( $self->intersection( @_ )->complement );
445             }
446              
447             =head2 C<< diff >>
448              
449             Return a new set containing all the addresses that are present in this
450             set but not another.
451              
452             my $diff = $this->diff($that);
453              
454             =cut
455              
456             sub diff {
457 0     0 1 0 my $self = shift;
458 0         0 my $other = shift;
459 0         0 return $self->intersection( $other->union( @_ )->complement );
460             }
461              
462             =head2 C<< is_empty >>
463              
464             Return a true value if the set is empty.
465              
466             if ( $set->is_empty ) {
467             print "Nothing there!\n";
468             }
469              
470             =cut
471              
472             sub is_empty {
473 16     16 1 22 my $self = shift;
474 16         18 return @{ $self->{ranges} } == 0;
  16         61  
475             }
476              
477             =head2 C<< superset >>
478              
479             Return true if this set is a superset of the supplied set.
480              
481             =cut
482              
483             sub superset {
484 0     0 1 0 my $other = pop;
485 0         0 return $other->subset( reverse( @_ ) );
486             }
487              
488             =head2 C<< subset >>
489              
490             Return true if this set is a subset of the supplied set.
491              
492             =cut
493              
494             sub subset {
495 0     0 1 0 my $self = shift;
496 0   0     0 my $other = shift || croak "I need two sets to compare";
497 0         0 return $self->equals( $self->intersection( $other ) );
498             }
499              
500             =head2 C<< equals >>
501              
502             Return true if this set is identical to another set.
503              
504             if ( $set->equals($foo) ) {
505             print "We have the same addresses.\n";
506             }
507              
508             =cut
509              
510             sub equals {
511 2 50   2 1 15 return unless @_;
512              
513             # Array of array refs
514 2         5 my @edges = map { $_->{ranges} } @_;
  4         12  
515 2         5 my $medge = scalar( @edges ) - 1;
516              
517 2         4 POS: for ( my $pos = 0;; $pos++ ) {
518 10         18 my $v = $edges[0]->[$pos];
519 10 100       22 if ( defined( $v ) ) {
520 8         18 for ( @edges[ 1 .. $medge ] ) {
521 8         14 my $vv = $_->[$pos];
522 8 50 33     58 return unless defined( $vv ) && $vv eq $v;
523             }
524             }
525             else {
526 2         5 for ( @edges[ 1 .. $medge ] ) {
527 2 50       9 return if defined $_->[$pos];
528             }
529             }
530              
531 10 100       30 last POS unless defined( $v );
532             }
533              
534 2         15 return 1;
535             }
536              
537             =head1 Retrieving Set Contents
538              
539             The following methods allow the contents of a set to be retrieved in
540             various representations. Each of the following methods accepts an
541             optional numeric argument that controls the formatting of the returned
542             addresses. It may take one of the following values:
543              
544             =over
545              
546             =item C<0>
547              
548             Format each range of addresses as compactly as possible. If the range
549             contains only a single address format it as such. If it can be
550             represented as a single CIDR block use CIDR representation (/)
551             otherwise format it as an arbitrary range (-).
552              
553             =item C<1>
554              
555             Always format as either a CIDR block or an arbitrary range even if the
556             range is just a single address.
557              
558             =item C<2>
559              
560             Always use arbitrary range format (-) even if the range is a
561             single address or a legal CIDR block.
562              
563             =back
564              
565             Here's an example of the different formatting options:
566              
567             my $set = Net::CIDR::Set->new( '127.0.0.1', '192.168.37.0/24',
568             '10.0.0.11-10.0.0.17' );
569              
570             for my $fmt ( 0 .. 2 ) {
571             print "Using format $fmt:\n";
572             print " $_\n" for $set->as_range_array( $fmt );
573             }
574              
575             And here's the output from that code:
576              
577             Using format 0:
578             10.0.0.11-10.0.0.17
579             127.0.0.1
580             192.168.37.0/24
581             Using format 1:
582             10.0.0.11-10.0.0.17
583             127.0.0.1/32
584             192.168.37.0/24
585             Using format 2:
586             10.0.0.11-10.0.0.17
587             127.0.0.1-127.0.0.1
588             192.168.37.0-192.168.37.255
589              
590             Note that this option never affects the addresses that are returned;
591             only how they are formatted.
592              
593             For most purposes the formatting argument can be omitted; it's default
594             value is C<0> which provides the most general formatting.
595              
596             =head2 C<< iterate_addresses >>
597              
598             Return an iterator (a closure) that will return each of the addresses in
599             the set in ascending order. This code
600              
601             my $set = Net::CIDR::Set->new('192.168.37.0/24');
602             my $iter = $set->iterate_addresses;
603             while ( my $ip = $iter->() ) {
604             print "Got $ip\n";
605             }
606              
607             outputs 256 distinct addresses from 192.168.37.0 to 192.168.27.255.
608              
609             =cut
610              
611             sub iterate_addresses {
612 1     1 1 7 my ( $self, @args ) = @_;
613 1         5 my $iter = $self->_iterate_runs;
614 1         3 my @r = ();
615             return sub {
616 2     2   3 while ( 1 ) {
617 3 100 100     12 @r = $iter->() or return unless @r;
618 2 100       8 return $self->_decode( ( my $last, $r[0] )
619             = ( $r[0], _inc( $r[0] ) ), @args )
620             unless $r[0] eq $r[1];
621 1         3 @r = ();
622             }
623 1         8 };
624             }
625              
626             =head2 C<< iterate_cidr >>
627              
628             Return an iterator (a closure) that will return each of the CIDR blocks
629             in the set in ascending order. This code
630              
631             my $set = Net::CIDR::Set->new('192.168.37.9-192.168.37.134');
632             my $iter = $set->iterate_cidr;
633             while ( my $cidr = $iter->() ) {
634             print "Got $cidr\n";
635             }
636              
637             outputs
638              
639             Got 192.168.37.9
640             Got 192.168.37.10/31
641             Got 192.168.37.12/30
642             Got 192.168.37.16/28
643             Got 192.168.37.32/27
644             Got 192.168.37.64/26
645             Got 192.168.37.128/30
646             Got 192.168.37.132/31
647             Got 192.168.37.134
648              
649             This is the most compact CIDR representation of the set because its
650             limits don't fall on convenient CIDR boundaries.
651              
652             =cut
653              
654             sub iterate_cidr {
655 8     8 1 18 my ( $self, @args ) = @_;
656 8         17 my $iter = $self->_iterate_runs;
657 8         24 my $size = $self->_nbits;
658 8         14 my @r = ();
659             return sub {
660 39     39   41 while ( 1 ) {
661 55 100 100     130 @r = $iter->() or return unless @r;
662 47 100       115 unless ( $r[0] eq $r[1] ) {
663 31         569 ( my $bits = unpack 'B*', $r[0] ) =~ /(0*)$/;
664 31         63 my $pad = length $1;
665 31 100       61 $pad = $size if $pad > $size;
666 31         33 while ( 1 ) {
667 205         789 my $next = _inc( $r[0] | pack 'B*',
668             ( '0' x ( length( $bits ) - $pad ) ) . ( '1' x $pad ) );
669 205 100       513 return $self->_decode( ( my $last, $r[0] ) = ( $r[0], $next ),
670             @args )
671             if $next le $r[1];
672 174         178 $pad--;
673             }
674             }
675 16         27 @r = ();
676             }
677 8         63 };
678             }
679              
680             =head2 C<< iterate_ranges >>
681              
682             Return an iterator (a closure) that will return each of the ranges
683             in the set in ascending order. This code
684              
685             my $set = Net::CIDR::Set->new(
686             '192.168.37.9-192.168.37.134',
687             '127.0.0.1',
688             '10.0.0.0/8'
689             );
690             my $iter = $set->iterate_ranges;
691             while ( my $range = $iter->() ) {
692             print "Got $range\n";
693             }
694              
695             outputs
696              
697             Got 10.0.0.0/8
698             Got 127.0.0.1
699             Got 192.168.37.9-192.168.37.134
700              
701             =cut
702              
703             sub iterate_ranges {
704 4     4 1 10 my ( $self, @args ) = @_;
705 4         10 my $iter = $self->_iterate_runs;
706             return sub {
707 16 100   16   26 return unless my @r = $iter->();
708 12         29 return $self->_decode( @r, @args );
709 4         22 };
710             }
711              
712             =head2 C<< as_array >>
713              
714             Convenience method that gathers all of the output from one of the
715             iterators above into an array.
716              
717             my @ranges = $set->as_array( $set->iterate_ranges );
718              
719             Normally you will use one of C, C or
720             C instead.
721              
722             =cut
723              
724             sub as_array {
725 13     13 1 33 my ( $self, $iter ) = @_;
726 13         18 my @addr = ();
727 13         28 while ( my $addr = $iter->() ) {
728 44         157 push @addr, $addr;
729             }
730 13         71 return @addr;
731             }
732              
733             =head2 C<< as_address_array >>
734              
735             Return an array containing all of the distinct addresses in a set. Note
736             that this may very easily create a very large array. At the time of
737             writing it is, for example, unlikely that you have enough memory for an
738             array containing all of the possible IPv6 addresses...
739              
740             =cut
741              
742             sub as_address_array {
743 1     1 1 2 my $self = shift;
744 1         4 return $self->as_array( $self->iterate_addresses( @_ ) );
745             }
746              
747             =head2 C<< as_cidr_array >>
748              
749             Return an array containing all of the distinct CIDR blocks in a set.
750              
751             =cut
752              
753             sub as_cidr_array {
754 8     8 1 335 my $self = shift;
755 8         35 return $self->as_array( $self->iterate_cidr( @_ ) );
756             }
757              
758             =head2 C<< as_range_array >>
759              
760             Return an array containing all of the ranges in a set.
761              
762             =cut
763              
764             sub as_range_array {
765 4     4 1 12 my $self = shift;
766 4         14 return $self->as_array( $self->iterate_ranges( @_ ) );
767             }
768              
769             =head2 C<< as_string >>
770              
771             Return a compact string representation of a set.
772              
773             =cut
774              
775 2     2 1 63 sub as_string { join ', ', shift->as_range_array( @_ ) }
776              
777             1;
778              
779             __END__