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   84 use 5.002;
  3         12  
  3         134  
11 3     3   20 use strict;
  3         5  
  3         136  
12 3     3   15 use warnings;
  3         6  
  3         160  
13              
14             package Net::IP::Identifier::Net;
15 3     3   1819 use parent 'Net::IP';
  3         1023  
  3         19  
16              
17 3     3   34890 use Math::BigInt;
  3         6  
  3         32  
18 3     3   1362 use Carp;
  3         7  
  3         368  
19              
20             our $VERSION = '0.111'; # VERSION
21              
22 3     3   19 use overload '""' => \&print;
  3         6  
  3         35  
23              
24             # Accept any of:
25             # Net::IP::Identifier::Net object or class
26             # Net::IP object
27             sub new {
28 1704     1704 1 3415 my ($class, $net) = @_;
29              
30 1704 50       4139 croak "Must have exactly one argument to 'new'\n" if (@_ != 2);
31 1704 50       3578 if (ref $net) {
32 0 0       0 return $net if (ref $net eq __PACKAGE__); # already correct
33              
34             # it's an object, but wrong kind
35 0         0 my $src_str;
36 0 0       0 $src_str = $net->src_str if ($net->can('src_str'));
37 0         0 bless $net, $class; # rebless to this package
38             # make sure we have a source string
39 0   0     0 $net->src_str($src_str || $net->print);
40 0         0 return $net;
41             }
42 1704         6311 my $self = $class->SUPER::new($net);
43 1704         1381233 bless $self, $class; # rebless to this package
44 1704         5197 $self->src_str($net); # set the source string
45              
46 1704         5852 return $self;
47             }
48              
49             sub src_str {
50 1704     1704 1 2333 my ($self, $new) = @_;
51              
52 1704 50       4538 if (@_ > 1) {
53 1704         3429 $self->{src_str} = $new;
54             }
55 1704         2819 return $self->{src_str};
56             }
57              
58             # print an int as a dotted decimal quad
59             sub int_to_ip {
60 752     752 1 1577 my ($self, $int, $version) = @_;
61              
62 752   50     5005 $version ||= $self->version || 4;
      33        
63 752         5861 my @parts;
64 752 100       2372 if ($version eq '6') {
65 58   66     205 while ($int or @parts < 8) {
66 464         77965 unshift @parts, $int & 0xffff;
67 464         78605 $int >>= 16;
68             }
69 58         10638 my $addr = join(':', map { sprintf "%x", $_ } @parts);
  464         5827  
70 58         1027 return Net::IP::ip_compress_address($addr, 6);
71             }
72              
73             # else IPv4
74 694   66     2328 while ($int or @parts < 4) {
75 2776         468482 unshift @parts, $int & 0xff;
76 2776         502919 $int >>= 8;
77             }
78 694         143810 return join('.', @parts);
79             }
80              
81             # split a range at the binary changeover point
82             sub _split_range {
83 615     615   1489832 my ($self, $idx, $low, $high) = @_;
84              
85 3     3   1304 no warnings 'recursion'; # IPv6 requires at least 128 levels
  3         5  
  3         2063  
86              
87 615         1004 if (0) {
88             printf "idx $idx, low %s, high %s\n",
89             $self->int_to_ip($low),
90             $self->int_to_ip($high);
91             }
92              
93 615 100       2666 if ($high <= $low) {
94 4         182 return Net::IP::Identifier::Net->new($self->int_to_ip($low));
95             }
96 611         30371 my $mask = Math::BigInt->new(2);
97 611         24971 $mask->bpow($idx);
98 611         119263 $mask--;
99 611   100     34379 while ($idx and ($high ^ $low) <= $mask) { # find first mask where different bit is outside it
100 881         226250 $mask->brsft(1);
101 881         125834 $idx--;
102             }
103 611         163443 $mask->blsft(1); # undo one shift
104 611         88500 $mask += 1;
105 611         74543 $idx++;
106 611 100 100     2189 if (($mask & $low) == 0 and
107             ($mask & $high) == $mask) {
108 374         174017 $low = $self->int_to_ip($low);
109 374         33119 $high = $self->int_to_ip($high);
110 374         30013 return Net::IP::Identifier::Net->new("$low - $high");
111             }
112              
113 237 50       87740 croak sprintf "ran out of indexes: 0x%x-0x%x\n", $low, $high if($idx <= 0);
114              
115             # need to split
116 237         405 my $new_split;
117 237   33     358 do {
118 237         833 $new_split = ($low & ~$mask);
119 237         121461 $mask >>= 1;
120 237         37755 $new_split += $mask + 1;
121 237         41722 $idx--;
122             } while ($new_split > $high and $idx >= 0);
123              
124 237         9877 my @low = $self->_split_range($idx, $low, $new_split - 1);
125 237         1903 my @high = $self->_split_range($idx, $new_split, $high);
126              
127 237         2780 return(@low, @high);
128             }
129              
130             # method to convert range into cidrs
131             sub range_to_cidrs {
132 141     141 1 309 my ($self) = @_;
133              
134 141 50       446 return $self if ($self->prefixlen); # don't need to split
135              
136 141         1159 return $self->_split_range(
137             $self->masklen - 1,
138             $self->intip,
139             $self->last_int);
140             }
141              
142             my $zero_v4 = '0' x 32;
143             my $zero_v6 = '0' x 128;
144             # return the length of inverse of the prefixlen, the length of the 1s in the mask
145             # always returns a value, unlike prefixlen. If not on an even binary
146             # boundary, the masklen represents a big enough CIDR that the range fits
147             # in it
148             sub masklen {
149 1704     1704 1 8526 my ($self) = @_;
150              
151 1704 100       3403 my $len = $self->version == 6 ? 128 : 32;
152             # xor with zero mask also to ensure proper upper bits
153 1704 100       9832 my $differ = $self->binip ^ $self->last_bin ^ ($self->version == 6 ? $zero_v6 : $zero_v4);
154 1704         26115 my $idx = index($differ, '1');
155 1704 100       4966 return 0 if $idx < 0;
156 1655         9960 return $len - $idx;
157             }
158              
159             # return the masked portion of the IP (upper part)
160             sub masked_ip {
161 1563     1563 1 2727 my ($self) = @_;
162              
163 1563 100       3614 my $len = $self->version == 6 ? 128 : 32;
164 1563         10726 return substr($self->binip, 0, $len - $self->masklen);
165             }
166              
167             # override print: bare IP (without prefixlen) for single IPs or network
168             # with prefixlength if CIDR, or as range if not single and not CIDR
169             sub print {
170 1300     1300 1 7887 my ($self) = @_;
171              
172 1300 50       2674 return $self->ip if ($self->ip eq $self->last_ip);
173 1300 100       10523 if (defined $self->prefixlen) {
174 1159 100       5326 if ($self->version eq 6) {
175 97         715 return $self->SUPER::print;
176             }
177 1062         4606 return $self->ip . '/' . $self->prefixlen;
178             }
179 141         814 return $self->ip . '-' . $self->last_ip;
180             }
181              
182             # return ->ip, but compress if IPv6
183             sub compressed_ip {
184 0     0 1   my ($self) = @_;
185              
186 0           return Net::IP::ip_compress_address($self->ip, $self->version);
187             }
188              
189             1;
190              
191             __END__