File Coverage

blib/lib/Net/IP/Identifier/Net.pm
Criterion Covered Total %
statement 95 103 92.2
branch 24 34 70.5
condition 13 23 56.5
subroutine 16 17 94.1
pod 8 8 100.0
total 156 185 84.3


line stmt bran cond sub pod time code
1             #===============================================================================
2             # PODNAME: Net::IP::Identifier::Net
3             # ABSTRACT: subclass Net::IP to add some functionality
4             #
5             # AUTHOR: Reid Augustin (REID)
6             # EMAIL: reid@hellosix.com
7             # CREATED: Sun Jul 20 17:48:21 PDT 2014
8             #===============================================================================
9              
10 3     3   96 use 5.002;
  3         9  
  3         173  
11 3     3   20 use strict;
  3         5  
  3         150  
12 3     3   20 use warnings;
  3         5  
  3         186  
13              
14             package Net::IP::Identifier::Net;
15 3     3   1523 use parent 'Net::IP';
  3         958  
  3         20  
16              
17 3     3   47380 use Math::BigInt;
  3         5  
  3         28  
18 3     3   1103 use Carp;
  3         6  
  3         350  
19              
20             our $VERSION = '0.110'; # VERSION
21              
22             use overload (
23 3         23 '""' => 'print',
24 3     3   19 );
  3         5  
25              
26             # Accept any of:
27             # Net::IP::Identifier::Net object or class
28             # Net::IP object
29             sub new {
30 1710     1710 1 3537 my ($class, $net) = @_;
31              
32 1710 50       4647 croak "Must have exactly one argument to 'new'\n" if (@_ != 2);
33 1710 50       3554 if (ref $net) {
34 0 0       0 return $net if (ref $net eq __PACKAGE__); # already correct
35              
36             # it's an object, but wrong kind
37 0         0 my $src_str;
38 0 0       0 $src_str = $net->src_str if ($net->can('src_str'));
39 0         0 bless $net, $class; # rebless to this package
40             # make sure we have a source string
41 0   0     0 $net->src_str($src_str || $net->print);
42 0         0 return $net;
43             }
44 1710         6697 my $self = $class->SUPER::new($net);
45 1710         1664008 bless $self, $class; # rebless to this package
46 1710         5969 $self->src_str($net); # set the source string
47              
48 1710         7372 return $self;
49             }
50              
51             sub src_str {
52 1710     1710 1 2793 my ($self, $new) = @_;
53              
54 1710 50       5166 if (@_ > 1) {
55 1710         3910 $self->{src_str} = $new;
56             }
57 1710         3041 return $self->{src_str};
58             }
59              
60             # print an int as a dotted decimal quad
61             sub int_to_ip {
62 758     758 1 1411 my ($self, $int, $version) = @_;
63              
64 758   50     4487 $version ||= $self->version || 4;
      33        
65 758         5897 my @parts;
66 758 100       1968 if ($version eq '6') {
67 58   66     177 while ($int or @parts < 8) {
68 464         77628 unshift @parts, $int & 0xffff;
69 464         77567 $int >>= 16;
70             }
71 58         10642 my $addr = join(':', map { sprintf "%x", $_ } @parts);
  464         5457  
72 58         952 return Net::IP::ip_compress_address($addr, 6);
73             }
74              
75             # else IPv4
76 700   66     2188 while ($int or @parts < 4) {
77 2800         432814 unshift @parts, $int & 0xff;
78 2800         469351 $int >>= 8;
79             }
80 700         135962 return join('.', @parts);
81             }
82              
83             # split a range at the binary changeover point
84             sub _split_range {
85 620     620   1430286 my ($self, $idx, $low, $high) = @_;
86              
87 3     3   1306 no warnings 'recursion'; # IPv6 requires at least 128 levels
  3         5  
  3         1834  
88              
89 620         965 if (0) {
90             printf "idx $idx, low %s, high %s\n",
91             $self->int_to_ip($low),
92             $self->int_to_ip($high);
93             }
94              
95 620 100       2461 if ($high <= $low) {
96 4         114 return Net::IP::Identifier::Net->new($self->int_to_ip($low));
97             }
98 616         29970 my $mask = Math::BigInt->new(2);
99 616         19223 $mask->bpow($idx);
100 616         107684 $mask--;
101 616   100     32123 while ($idx and ($high ^ $low) <= $mask) { # find first mask where different bit is outside it
102 889         224054 $mask->brsft(1);
103 889         118831 $idx--;
104             }
105 616         150909 $mask->blsft(1); # undo one shift
106 616         80834 $mask += 1;
107 616         70184 $idx++;
108 616 100 100     2125 if (($mask & $low) == 0 and
109             ($mask & $high) == $mask) {
110 377         168293 $low = $self->int_to_ip($low);
111 377         30708 $high = $self->int_to_ip($high);
112 377         28023 return Net::IP::Identifier::Net->new("$low - $high");
113             }
114              
115 239 50       86729 croak sprintf "ran out of indexes: 0x%x-0x%x\n", $low, $high if($idx <= 0);
116              
117             # need to split
118 239         546 my $new_split;
119 239   33     476 do {
120 239         881 $new_split = ($low & ~$mask);
121 239         115907 $mask >>= 1;
122 239         35734 $new_split += $mask + 1;
123 239         40927 $idx--;
124             } while ($new_split > $high and $idx >= 0);
125              
126 239         9487 my @low = $self->_split_range($idx, $low, $new_split - 1);
127 239         1757 my @high = $self->_split_range($idx, $new_split, $high);
128              
129 239         2926 return(@low, @high);
130             }
131              
132             # method to convert range into cidrs
133             sub range_to_cidrs {
134 142     142 1 356 my ($self) = @_;
135              
136 142 50       521 return $self if ($self->prefixlen); # don't need to split
137              
138 142         1019 return $self->_split_range(
139             $self->masklen - 1,
140             $self->intip,
141             $self->last_int);
142             }
143              
144             my $zero_v4 = '0' x 32;
145             my $zero_v6 = '0' x 128;
146             # return the length of inverse of the prefixlen, the length of the 1s in the mask
147             # always returns a value, unlike prefixlen. If not on an even binary
148             # boundary, the masklen represents a big enough CIDR that the range fits
149             # in it
150             sub masklen {
151 1710     1710 1 8530 my ($self) = @_;
152              
153 1710 100       4533 my $len = $self->version == 6 ? 128 : 32;
154             # xor with zero mask also to ensure proper upper bits
155 1710 100       10300 my $differ = $self->binip ^ $self->last_bin ^ ($self->version == 6 ? $zero_v6 : $zero_v4);
156 1710         25759 my $idx = index($differ, '1');
157 1710 100       4444 return 0 if $idx < 0;
158 1662         10284 return $len - $idx;
159             }
160              
161             # return the masked portion of the IP (upper part)
162             sub masked_ip {
163 1568     1568 1 2679 my ($self) = @_;
164              
165 1568 100       3653 my $len = $self->version == 6 ? 128 : 32;
166 1568         10593 return substr($self->binip, 0, $len - $self->masklen);
167             }
168              
169             # override print: bare IP (without prefixlen) for single IPs or network
170             # with prefixlength if CIDR, or as range if not single and not CIDR
171             sub print {
172 1303     1303 1 13975 my ($self) = @_;
173              
174 1303 50       3147 return $self->ip if ($self->ip eq $self->last_ip);
175 1303 100       13678 if (defined $self->prefixlen) {
176 1161 100       6699 if ($self->version eq 6) {
177 96         801 return $self->SUPER::print;
178             }
179 1065         6358 return $self->ip . '/' . $self->prefixlen;
180             }
181 142         856 return $self->ip . '-' . $self->last_ip;
182             }
183              
184             # return ->ip, but compress if IPv6
185             sub compressed_ip {
186 0     0 1   my ($self) = @_;
187              
188 0           return Net::IP::ip_compress_address($self->ip, $self->version);
189             }
190              
191             1;
192              
193             __END__