File Coverage

blib/lib/Net/IP/Identifier/Net.pm
Criterion Covered Total %
statement 42 105 40.0
branch 8 34 23.5
condition 0 23 0.0
subroutine 11 18 61.1
pod 3 10 30.0
total 64 190 33.6


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   77 use 5.002;
  3         11  
  3         121  
11 3     3   15 use strict;
  3         4  
  3         115  
12 3     3   13 use warnings;
  3         5  
  3         150  
13              
14             package Net::IP::Identifier::Net;
15 3     3   1660 use parent 'Net::IP';
  3         933  
  3         14  
16              
17 3     3   57310 use Math::BigInt;
  3         6  
  3         32  
18 3     3   1118 use Carp;
  3         6  
  3         292  
19              
20             our $VERSION = '0.106'; # VERSION
21              
22             use overload (
23 3         25 '""' => 'print',
24 3     3   17 );
  3         3  
25              
26             # Accept any of:
27             # Net::IP::Identifier::Net object or class
28             # Net::IP object
29             sub new {
30 6     6 0 16 my ($class, $net) = @_;
31              
32 6 50       22 croak "Must have exactly one argument to 'new'\n" if (@_ != 2);
33 6 50       18 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 6         42 my $self = $class->SUPER::new($net);
45 6         28902 bless $self, $class; # rebless to this package
46 6         31 $self->src_str($net); # set the source string
47              
48 6         26 return $self;
49             }
50              
51             sub src_str {
52 6     6 0 13 my ($self, $new) = @_;
53              
54 6 50       29 if (@_ > 1) {
55 6         18 $self->{src_str} = $new;
56             }
57 6         14 return $self->{src_str};
58             }
59              
60             # print an int as a dotted decimal quad
61             sub int_to_ip {
62 0     0 0 0 my ($self, $ip, $version) = @_;
63              
64 0   0     0 $version ||= $self->version || 4;
      0        
65 0         0 my @parts;
66 0 0       0 if ($version eq '6') {
67 0   0     0 while ($ip or @parts < 8) {
68 0         0 unshift @parts, $ip & 0xffff;
69 0         0 $ip >>= 16;
70             }
71 0         0 my $addr = join(':', map { sprintf "%x", $_ } @parts);
  0         0  
72 0         0 return Net::IP::ip_compress_address($addr, 6);
73             }
74              
75             # else IPv4
76 0   0     0 while ($ip or @parts < 4) {
77 0         0 unshift @parts, $ip & 0xff;
78 0         0 $ip >>= 8;
79             }
80 0         0 return join('.', @parts);
81             }
82              
83             # split a range at the binary changeover point
84             sub split_range {
85 0     0 0 0 my ($self, $idx, $low, $high) = @_;
86              
87 3     3   1167 no warnings 'recursion'; # IPv6 requires at least 128 levels
  3         5  
  3         2083  
88              
89 0         0 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 0 0       0 if ($high <= $low) {
96 0         0 return Net::IP::Identifier::Net->new($self->int_to_ip($low));
97             }
98 0         0 my $mask = Math::BigInt->new(2);
99 0         0 $mask->bpow($idx);
100 0         0 $mask--;
101 0   0     0 while ($idx and ($high ^ $low) <= $mask) { # find first mask where different bit is outside it
102 0         0 $mask->brsft(1);
103 0         0 $idx--;
104             }
105 0         0 $mask->blsft(1); # undo one shift
106 0         0 $mask += 1;
107 0         0 $idx++;
108 0 0 0     0 if (($mask & $low) == 0 and
109             ($mask & $high) == $mask) {
110 0         0 $low = $self->int_to_ip($low);
111 0         0 $high = $self->int_to_ip($high);
112 0         0 return Net::IP::Identifier::Net->new("$low - $high");
113             }
114              
115 0 0       0 croak sprintf "ran out of indexes: 0x%x-0x%x\n", $low, $high if($idx <= 0);
116              
117             # need to split
118 0         0 my $new_split;
119 0   0     0 do {
120 0         0 $new_split = ($low & ~$mask);
121 0         0 $mask >>= 1;
122 0         0 $new_split += $mask + 1;
123 0         0 $idx--;
124             } while ($new_split > $high and $idx >= 0);
125              
126 0         0 my @low = $self->split_range($idx, $low, $new_split - 1);
127 0         0 my @high = $self->split_range($idx, $new_split, $high);
128              
129 0         0 return(@low, @high);
130             }
131              
132             # method to convert range into cidrs
133             sub range_to_cidrs {
134 0     0 1 0 my ($self) = @_;
135              
136 0 0       0 return $self if ($self->prefixlen); # don't need to split
137              
138 0         0 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 XOR of the first and last IP addresses
147             sub differ {
148 0     0 0 0 my ($self) = @_;
149              
150             # xor with zero mask also to ensure proper upper bits
151 0 0       0 return $self->binip ^ $self->last_bin ^ ($self->version == 6 ? $zero_v6 : $zero_v4);
152             }
153              
154             # return the inverse of the prefixlen, the length of the 1s in the mask
155             # always returns a value, unlike prefixlen. If not on an even binary
156             # boundary, the masklen represents a big enough CIDR that the range fits
157             # in it
158             sub masklen {
159 0     0 0 0 my ($self) = @_;
160              
161 0 0       0 my $len = $self->version == 6 ? 128 : 32;
162 0         0 my $differ = $self->differ;
163 0         0 my $idx = index($differ, '1');
164 0 0       0 return 0 if $idx < 0;
165 0         0 return $len - $idx;
166             }
167              
168             # return the masked portion of the IP (upper part)
169             sub masked_ip {
170 0     0 1 0 my ($self) = @_;
171              
172 0 0       0 my $len = $self->version == 6 ? 128 : 32;
173 0         0 return substr($self->binip, 0, $len - $self->masklen);
174             }
175              
176             # override print: bare IP (without prefixlen) for single IPs or network
177             # with prefixlength if CIDR, or as range if not single and not CIDR
178             sub print {
179 6     6 1 139 my ($self) = @_;
180              
181 6 50       25 return $self->ip if ($self->ip eq $self->last_ip);
182 6 100       98 if (defined $self->prefixlen) {
183 4 100       48 if ($self->version eq 6) {
184 1         24 return $self->SUPER::print;
185             }
186 3         24 return $self->ip . '/' . $self->prefixlen;
187             }
188 2         17 return $self->ip . '-' . $self->last_ip;
189             }
190              
191             # return ->ip, but compress if IPv6
192             sub compressed_ip {
193 0     0 0   my ($self) = @_;
194              
195 0           return Net::IP::ip_compress_address($self->ip, $self->version);
196             }
197              
198             1;
199              
200             __END__