File Coverage

blib/lib/Net/CIDR/Overlap.pm
Criterion Covered Total %
statement 34 103 33.0
branch 4 40 10.0
condition 0 12 0.0
subroutine 8 13 61.5
pod 8 8 100.0
total 54 176 30.6


line stmt bran cond sub pod time code
1             package Net::CIDR::Overlap;
2              
3 2     2   130404 use 5.006;
  2         14  
4 2     2   11 use strict;
  2         3  
  2         35  
5 2     2   8 use warnings;
  2         4  
  2         68  
6 2     2   911 use Net::CIDR;
  2         10196  
  2         90  
7 2     2   900 use Net::CIDR::Set;
  2         15197  
  2         1473  
8              
9             =head1 NAME
10              
11             Net::CIDR::Overlap - A utility module for helping make sure a list of CIDRs don't overlap.
12              
13             =head1 VERSION
14              
15             Version 0.2.0
16              
17             =cut
18              
19             our $VERSION = '0.2.0';
20              
21             =head1 SYNOPSIS
22              
23             my $nco=Net::CIDR::Overlap->new;
24            
25             # add some subnets
26             eval{
27             $nco->add( '127.0.0.0/24' );
28             $nco->add( '192.168.42.0/24' );
29             $nco->add( '10.10.0.0/16' );
30             }
31             if ( $@ ){
32             warn( $@ );
33             }
34            
35             # this will fail as they have already been added
36             eval{
37             $nco->add( '127.0.0.0/25' );
38             $nco->add( '10.10.10/24' );
39             }
40             if ( $@ ){
41             warn( $@ );
42             }
43            
44             # this will fail this is not a valid CIDR
45             eval{
46             $nco->add( 'foo' );
47             }
48             if ( $@ ){
49             warn( $@ );
50             }
51            
52             # print the subnets we added with out issue
53             my $list=$nco->list;
54             foreach my $cidr ( @${ $list } ){
55             print $cidr."\n";
56             }
57              
58             This works with eithe IPv4 or IPv6. Two instances of L
59             are maintained, one for IPv4 and one for IPv6.
60              
61             =head1 METHODS
62              
63             =head2 new
64              
65             This initates the object.
66              
67             my $nco=Net::CIDR::Overlap->new;
68              
69             =cut
70              
71             sub new{
72 1     1 1 84 my $self = {
73             set4=>Net::CIDR::Set->new( { type => 'ipv4' } ),
74             set6=>Net::CIDR::Set->new( { type => 'ipv6' } ),
75             list=>{},
76             set4init=>undef,
77             set6init=>undef,
78             };
79 1         84 bless $self;
80              
81 1         2 return $self;
82             }
83              
84             =head2 add
85              
86             This adds a subnet to the set being checked.
87              
88             Net::CIDR::cidrvalidate is used to validate passed CIDR/IP.
89              
90             This will die if it is called with a undef value of if validation fails.
91              
92             This does not check if what is being added overlaps with anything already
93             added.
94              
95             eval{
96             $nco->add( $cidr );
97             }
98             if ( $@ ){
99             warn( $@ );
100             }
101              
102             =cut
103              
104             sub add{
105 3     3 1 1630 my $self=$_[0];
106 3         4 my $cidr=$_[1];
107              
108             # makes sure we have a defined+valid valueand get what set we should remove it from
109 3         8 my $set='set'.$self->ip_type( $cidr );
110              
111 1         11 $self->{$set}->add( $cidr );
112 1         135 $self->{list}{$cidr}=1;
113 1         3 $self->{init}=1;
114              
115 1         2 return 1;
116             }
117              
118             =head2 available
119              
120             This checks to see if the subnet is available.
121              
122             There is one required argument and two optional.
123              
124             The first and required is the CIDR/IP. This will be
125             validated using Net::CIDR::cidrvalidate.
126              
127             The second is if to invert the check or not. If set to
128             true, it will only be added if overlap is found.
129              
130             The third is if overlap should be any or all. This is boolean
131             and a value of true sets it to all. The default value is false,
132             meaning any overlap.
133              
134             my $available;
135             eval{
136             $available=$nco->available( $cidr );
137             };
138             if ( $@ ){
139             # do something to handle the error
140             die( 'Most likely a bad CIDR...'.$@ );
141             }elsif( ! $available ){
142             print "Some or all of the IPs in ".$cidr." are unavailable.\n";
143             }
144              
145             # this time invert the search and check if all of them are unavailable
146             eval{
147             $available==$nco->available( $cidr, 1, 1 );
148             };
149             if ( $@ ){
150             # do something to handle the error
151             die( 'Most likely a bad CIDR...'.$@ );
152             }elsif( $available ){
153             print "All of the IPs in ".$cidr." are unavailable.\n";
154             }
155              
156             =cut
157              
158             sub available{
159 0     0 1 0 my $self=$_[0];
160 0         0 my $cidr=$_[1];
161 0         0 my $invert=$_[2];
162 0         0 my $all=$_[3];
163              
164             # makes sure we have a defined+valid valueand get what set we should remove it from
165 0         0 my $set='set'.$self->ip_type( $cidr );
166              
167             # set here so we produce nice output if we die
168 0 0       0 if ( !defined( $invert ) ){
169 0         0 $invert=0;
170             }
171 0 0       0 if ( !defined( $all ) ){
172 0         0 $all=0;
173             }
174 0         0 my $valid;
175 0         0 eval{
176 0         0 $valid=Net::CIDR::cidrvalidate($cidr);
177             };
178 0 0       0 if (! defined( $valid ) ){
179 0         0 die $cidr.' is not a valid CIDR or IP';
180             }
181              
182 0         0 my $contains=0;
183 0 0 0     0 if (
    0 0        
184             $all &&
185             $self->{$set}->contains_all( $cidr )
186             ){
187 0         0 $contains=1;
188             }elsif(
189             ( ! $all ) &&
190             $self->{$set}->contains_any( $cidr )
191             ){
192 0         0 $contains=1;
193             }
194              
195 0 0       0 if ( $invert ){
196 0         0 $contains = $contains ^ 1;
197             }
198              
199 0 0       0 if( $contains ){
200 0         0 return 0;
201             }
202              
203              
204 0         0 return 1;
205             }
206              
207             =head2 compare_and_add
208              
209             This first checks for overlap and then adds it.
210              
211             There is one required argument and two optional.
212              
213             The first and required is the CIDR/IP. This will be
214             validated using Net::CIDR::cidrvalidate.
215              
216             The second is if to invert the check or not. If set to
217             true, it will only be added if overlap is found.
218              
219             The third is if overlap should be any or all. This is boolean
220             and a value of true sets it to all. The default value is false,
221             meaning any overlap.
222              
223             # just add it if there is no overlap
224             eval{
225             $nco->compare_and_add( $cidr );
226             }
227             if ( $@ ){
228             warn( $@ );
229             }
230              
231             # this time invert it and use use any for the overlap check
232             eval{
233             $nco->compare_and_add( $cidr, '1', '0' );
234             }
235             if ( $@ ){
236             warn( $@ );
237             }
238              
239             =cut
240              
241             sub compare_and_add{
242 0     0 1 0 my $self=$_[0];
243 0         0 my $cidr=$_[1];
244 0         0 my $invert=$_[2];
245 0         0 my $all=$_[3];
246              
247             # makes sure we have a defined+valid valueand get what set we should remove it from
248 0         0 my $set='set'.$self->ip_type( $cidr );
249              
250             # set here so we produce nice output if we die
251 0 0       0 if ( !defined( $invert ) ){
252 0         0 $invert=0;
253             }
254 0 0       0 if ( !defined( $all ) ){
255 0         0 $all=0;
256             }
257              
258 0 0       0 if ( ! $self->{$set.'init'} ){
259 0         0 $self->{$set}->add($cidr);
260 0         0 $self->{list}{$cidr}=1;
261 0         0 $self->{$set.'init'}=1;
262 0         0 return 1;
263             }
264              
265 0         0 my $contains=0;
266 0 0 0     0 if (
    0 0        
267             $all &&
268             $self->{$set}->contains_all( $cidr )
269             ){
270 0         0 $contains=1;
271             }elsif(
272             ( ! $all ) &&
273             $self->{$set}->contains_any( $cidr )
274             ){
275 0         0 $contains=1;
276             }
277              
278 0 0       0 if ( $invert ){
279 0         0 $contains = $contains ^ 1;
280             }
281              
282 0 0       0 if( $contains ){
283 0         0 die( 'The compare matched... invert='.$invert.' all='.$all );
284             }
285              
286 0         0 $self->{$set}->add($cidr);
287 0         0 $self->{list}{$cidr}=1;
288 0         0 $self->{$set.'init'}=1;
289              
290 0         0 return 1;
291             }
292              
293             =head2 exists
294              
295             This check if the specified value exists in the list or not.
296              
297             One value is taken and that is a CIDR. If this is not defined,
298             it will die.
299              
300             my $xists;
301             eval{
302             $nco->exists( $cidr );
303             };
304             if ( $@ ){
305              
306             }elsif( ! $exist ){
307             print $cidr." does not exist in the list.\n";
308             }else{
309             print $cidr." does exist in the list.\n";
310             }
311              
312             =cut
313              
314             sub exists{
315 0     0 1 0 my $self=$_[0];
316 0         0 my $cidr=$_[1];
317              
318 0 0       0 if (!defined( $cidr )){
319 0         0 die('No CIDR defined');
320             }
321              
322 0 0       0 if ( defined( $self->{list}{$cidr} ) ){
323 0         0 return 1;
324             }
325              
326 0         0 return undef;
327             }
328              
329             =head2 list
330              
331             This returns a array of successfully added items.
332              
333             my @list=$nco->list;
334             foreach my $cidr ( @list ){
335             print $cidr."\n";
336             }
337              
338             =cut
339              
340             sub list{
341 0     0 1 0 my $self=$_[0];
342              
343 0         0 return keys( %{ $self->{list} } );
  0         0  
344             }
345              
346             =head2 remove
347              
348             This removes the specified CIDR from the list.
349              
350             One argument is taken and that is the CIDR to remove.
351              
352             If the CIDR is not one that has been added, it will error.
353              
354             Upon any errors, this method will die.
355              
356             eval{
357             $nco->remove( $cidr );
358             };
359             if ( $@ ){
360             die( 'Did you make sure the $cidr was defined and added previously?' );
361             }
362              
363             =cut
364              
365             sub remove{
366 0     0 1 0 my $self=$_[0];
367 0         0 my $cidr=$_[1];
368              
369             # makes sure we have a defined+valid valueand get what set we should remove it from
370 0         0 my $set='set'.$self->ip_type( $cidr );
371              
372 0 0       0 if ( !defined( $self->{list}{$cidr} ) ){
373 0         0 die( '"'.$cidr.'" is not in the list' );
374             }
375              
376 0         0 $self->{$set}->remove( $cidr );
377 0         0 delete( $self->{list}{$cidr} );
378              
379 0         0 return 1;
380             }
381              
382             =head2 ip_type
383              
384             This returns either 4 or 6 based on if it is IPv4 or IPv6.
385              
386             Upon undef or invalid CIDR, this will die.
387              
388             my $type=$nco->ip_type( $cidr );
389             if ( $type eq '4' ){
390             print "It is IPv4\n";
391             }else{
392             print "It is IPv6\n";
393             }
394              
395             =cut
396              
397             sub ip_type{
398 3     3 1 4 my $self=$_[0];
399 3         5 my $cidr=$_[1];
400              
401             # make sure we have input
402 3 50       7 if (!defined( $cidr )){
403 0         0 die('No CIDR defined');
404             }
405              
406             # make sure we are valid
407 3         3 my $valid;
408 3         5 eval{
409 3         8 $valid=Net::CIDR::cidrvalidate($cidr);
410             };
411 3 100       1111 if (! defined( $valid ) ){
412 2         15 die $cidr.' is not a valid CIDR or IP';
413             }
414              
415             # if it contains a :, then it is IPv6
416 1 50       4 if ( $cidr =~ /\:/ ){
417 0         0 return '6';
418             }
419              
420             # valid and not IPv6, so IPv4
421 1         3 return '4';
422             }
423              
424             =head1 AUTHOR
425              
426             Zane C. Bowers-Hadley, C<< >>
427              
428             =head1 BUGS
429              
430             Please report any bugs or feature requests to C, or through
431             the web interface at L. I will be notified, and then you'll
432             automatically be notified of progress on your bug as I make changes.
433              
434              
435              
436              
437             =head1 SUPPORT
438              
439             You can find documentation for this module with the perldoc command.
440              
441             perldoc Net::CIDR::Overlap
442              
443              
444             You can also look for information at:
445              
446             =over 4
447              
448             =item * RT: CPAN's request tracker (report bugs here)
449              
450             L
451              
452             =item * AnnoCPAN: Annotated CPAN documentation
453              
454             L
455              
456             =item * CPAN Ratings
457              
458             L
459              
460             =item * Search CPAN
461              
462             L
463              
464             =item * GIT Repository
465              
466             L
467              
468             =back
469              
470              
471             =head1 ACKNOWLEDGEMENTS
472              
473              
474             =head1 LICENSE AND COPYRIGHT
475              
476             This software is Copyright (c) 2019 by Zane C. Bowers-Hadley.
477              
478             This is free software, licensed under:
479              
480             The Artistic License 2.0 (GPL Compatible)
481              
482              
483             =cut
484              
485             1; # End of Net::CIDR::Overlap