File Coverage

blib/lib/Net/IPv6Addr.pm
Criterion Covered Total %
statement 268 288 93.0
branch 56 76 73.6
condition 18 21 85.7
subroutine 36 36 100.0
pod 16 30 53.3
total 394 451 87.3


line stmt bran cond sub pod time code
1             package Net::IPv6Addr;
2              
3 15     15   624261 use strict;
  15         145  
  15         421  
4 15     15   74 use warnings;
  15         25  
  15         1680  
5              
6             our @ISA = qw(Exporter);
7             our @EXPORT = qw();
8             our @EXPORT_OK = qw(
9             in_network
10             in_network_of_size
11             ipv6_chkip
12             ipv6_parse
13             is_ipv6
14             to_string_preferred
15             to_string_compressed
16             to_bigint
17             to_intarray
18             to_array
19             to_string_ip6_int
20             to_string_base85
21             to_string_ipv4
22             to_string_ipv4_compressed
23             from_bigint
24             );
25             our %EXPORT_TAGS = (all => \@EXPORT_OK);
26             our $VERSION = '1.0';
27              
28 15     15   106 use Carp;
  15         35  
  15         929  
29 15     15   7003 use Net::IPv4Addr;
  15         49709  
  15         681  
30 15     15   16971 use Math::BigInt '1.999813';
  15         428776  
  15         71  
31 15     15   382795 use Math::Base85;
  15         23006  
  15         85  
32              
33             # ____ _ _
34             # | _ \ __ _| |_| |_ ___ _ __ _ __ ___
35             # | |_) / _` | __| __/ _ \ '__| '_ \/ __|
36             # | __/ (_| | |_| || __/ | | | | \__ \
37             # |_| \__,_|\__|\__\___|_| |_| |_|___/
38             #
39              
40             # Match one to four digits of hexadecimal
41              
42             my $h = qr/[a-f0-9]{1,4}/i;
43              
44             # Match one to three digits
45              
46             #my $d = qr/[0-9]{1,3}/;
47             my $ipv4 = "((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))";
48              
49             # base-85
50              
51             my $digits = $Math::Base85::base85_digits;
52             $digits =~ s/-//;
53             my $x = "[" . $digits . "-]";
54             my $n = "{20}";
55              
56             my %ipv6_patterns = (
57             'preferred' => [
58             qr/^(?:$h:){7}$h$/i,
59             \&ipv6_parse_preferred,
60             ],
61             'compressed' => [
62             qr/^[a-f0-9]{0,4}::$/i,
63             qr/^:(?::$h){1,7}$/i,
64             qr/^(?:$h:){1,}:$/i,
65             qr/^(?:$h:)(?::$h){1,6}$/i,
66             qr/^(?:$h:){2}(?::$h){1,5}$/i,
67             qr/^(?:$h:){3}(?::$h){1,4}$/i,
68             qr/^(?:$h:){4}(?::$h){1,3}$/i,
69             qr/^(?:$h:){5}(?::$h){1,2}$/i,
70             qr/^(?:$h:){6}(?::$h)$/i,
71             \&ipv6_parse_compressed,
72             ],
73             'ipv4' => [
74             qr/^(?:0:){5}ffff:$ipv4$/i,
75             qr/^(?:0:){6}$ipv4$/,
76             \&ipv6_parse_ipv4,
77             ],
78             'ipv4 compressed' => [
79             qr/^::(?:ffff:)?$ipv4$/i,
80             \&ipv6_parse_ipv4_compressed,
81             ],
82             'ipv6v4' => [
83             qr/^[a-f0-9]{0,4}::$ipv4$/i,
84             # ::1:2:3:4:1.2.3.4
85             qr/^::(?:$h:){1,5}$ipv4$/i,
86             qr/^(?:$h:):(?:$h:){1,4}$ipv4$/i,
87             qr/^(?:$h:){2}:(?:$h:){1,3}$ipv4$/i,
88             qr/^(?:$h:){3}:(?:$h:){1,2}$ipv4$/i,
89             qr/^(?:$h:){4}:(?:$h:){1}$ipv4$/i,
90             # 1:2:3:4:5::1.2.3.4
91             qr/^(?:$h:){1,5}:$ipv4$/i,
92             # 1:2:3:4:5:6:1.2.3.4
93             qr/^(?:$h:){6}$ipv4$/i,
94             \&parse_mixed_ipv6v4_compressed,
95             ],
96             'base85' => [
97             qr/^$x$n$/,
98             \&ipv6_parse_base85,
99             ],
100             );
101              
102             # ____ _ _
103             # | _ \ _ __(_)_ ____ _| |_ ___
104             # | |_) | '__| \ \ / / _` | __/ _ \
105             # | __/| | | |\ V / (_| | || __/
106             # |_| |_| |_| \_/ \__,_|\__\___|
107             #
108              
109             # Errors which include the package name and the subroutine name. This
110             # is for consistency with earlier versions of the module.
111              
112             sub mycroak
113             {
114 81     81 0 139 my ($message) = @_;
115 81         426 my @caller = caller (1);
116 81         8572 croak $caller[3] . ' -- ' . $message;
117             }
118              
119             # Given one argument with a slash or two arguments, return them as two
120             # arguments, and check there are one or two arguments.
121              
122             sub getargs
123             {
124 24     24 0 38 my ($ip, $pfx);
125 24 100       101 if (@_ == 2) {
    50          
126 5         10 ($ip, $pfx) = @_;
127             }
128             elsif (@_ == 1) {
129 19         69 ($ip, $pfx) = split(m!/!, $_[0], 2)
130             }
131             else {
132 0         0 mycroak "wrong number of arguments (need 1 or 2)";
133             }
134             # if (defined $pfx && $pfx !~ /^[0-9]*$/) {
135             # mycroak "Non-numerical suffix $pfx";
136             # }
137 24         67 return ($ip, $pfx);
138             }
139              
140             # Match $ip against the regexes of type $type, or die.
141              
142             sub match_or_die
143             {
144 502     502 0 812 my ($ip, $type) = @_;
145             # Instead of trying to construct a gigantic regex which only
146             # allows two colons in a row, just check here.
147 502 100       1076 if ($ip =~ /:::/) {
148 2         8 mycroak "invalid address $ip for type $type";
149             }
150 500         677 my $patterns = $ipv6_patterns{$type};
151 500         775 for my $p (@$patterns) {
152             # The last thing in the pattern is a code reference, so this
153             # match indicates no matches were found.
154 916 100       1517 if (ref($p) eq 'CODE') {
155 36         110 mycroak "invalid address $ip for type $type";
156             }
157 880 100       3360 if ($ip =~ $p) {
158 464         771 return;
159             }
160             }
161             }
162              
163             # Make the bit mask for "in_network_of_size".
164              
165             sub bitmask
166             {
167 3     3 0 4 my ($j) = @_;
168 3         9 my $bitmask = '1' x $j . '0' x (16 - $j);
169 3         15 my $k = unpack("n",pack("B16", $bitmask));
170 3         5 return $k;
171             }
172              
173             # ____
174             # | _ \ __ _ _ __ ___ ___ _ __ ___
175             # | |_) / _` | '__/ __|/ _ \ '__/ __|
176             # | __/ (_| | | \__ \ __/ | \__ \
177             # |_| \__,_|_| |___/\___|_| |___/
178             #
179              
180             # Private parser
181              
182             sub ipv6_parse_preferred
183             {
184 358     358 0 1711 my $ip = shift;
185 358         615 match_or_die ($ip, 'preferred');
186 350         940 my @pieces = split(/:/, $ip);
187 350         553 splice(@pieces, 8);
188 350         469 return map { hex } @pieces;
  2800         3754  
189             }
190              
191             # Private parser
192              
193             sub ipv6_parse_compressed
194             {
195 87     87 0 767 my $ip = shift;
196 87         126 my $type = 'compressed';
197 87         204 match_or_die ($ip, $type);
198 82         187 my $colons = ($ip =~ tr/:/:/);
199 82         207 my $expanded = ':' x (9 - $colons);
200 82         274 $ip =~ s/::/$expanded/;
201 82         350 my @pieces = split(/:/, $ip, 8);
202 82         167 return map { hex } @pieces;
  656         1091  
203             }
204              
205             sub parse_mixed_ipv6v4_compressed
206             {
207 26     26 0 45 my $ip = shift;
208 26         83 match_or_die ($ip, 'ipv6v4');
209 26         55 my @result;
210             my $v4addr;
211 26         0 my $colons;
212 26         61 $colons = ($ip =~ tr/:/:/);
213 26         55 my $expanded = ':' x (8 - $colons);
214 26         85 $ip =~ s/::/$expanded/;
215 26         112 my @v6pcs = split(/:/, $ip, 7);
216 26         43 $v4addr = $v6pcs[-1];
217 26         55 splice(@v6pcs, 6);
218 26         51 push @result, map { hex } @v6pcs;
  156         259  
219 26         118 Net::IPv4Addr::ipv4_parse($v4addr);
220 26         960 my @v4pcs = split(/\./, $v4addr);
221 26         45 splice(@v4pcs, 4);
222 26         159 push @result, unpack("n", pack("CC", @v4pcs[0,1]));
223 26         72 push @result, unpack("n", pack("CC", @v4pcs[2,3]));
224 26         84 return @result;
225             }
226              
227             # Private parser
228              
229             sub ipv6_parse_ipv4
230             {
231 13     13 0 1327 my $ip = shift;
232 13         29 match_or_die ($ip, 'ipv4');
233 2         3 my @result;
234             my $v4addr;
235 2         29 my @v6pcs = split(/:/, $ip);
236 2         6 $v4addr = $v6pcs[-1];
237 2         4 splice(@v6pcs, 6);
238 2         5 push @result, map { hex } @v6pcs;
  12         22  
239 2         11 Net::IPv4Addr::ipv4_parse($v4addr);
240 2         102 my @v4pcs = split(/\./, $v4addr);
241 2         19 push @result, unpack("n", pack("CC", @v4pcs[0,1]));
242 2         9 push @result, unpack("n", pack("CC", @v4pcs[2,3]));
243 2         8 return @result;
244             }
245              
246             # Private parser
247              
248             sub ipv6_parse_ipv4_compressed
249             {
250 13     13 0 1455 my $ip = shift;
251 13         27 match_or_die ($ip, 'ipv4 compressed');
252 2         6 my @result;
253             my $v4addr;
254 2         0 my $colons;
255 2         4 $colons = ($ip =~ tr/:/:/);
256 2         6 my $expanded = ':' x (8 - $colons);
257 2         6 $ip =~ s/::/$expanded/;
258 2         9 my @v6pcs = split(/:/, $ip, 7);
259 2         3 $v4addr = $v6pcs[-1];
260 2         4 splice(@v6pcs, 6);
261 2         4 push @result, map { hex } @v6pcs;
  12         17  
262 2         9 Net::IPv4Addr::ipv4_parse($v4addr);
263 2         82 my @v4pcs = split(/\./, $v4addr);
264 2         3 splice(@v4pcs, 4);
265 2         10 push @result, unpack("n", pack("CC", @v4pcs[0,1]));
266 2         6 push @result, unpack("n", pack("CC", @v4pcs[2,3]));
267 2         8 return @result;
268             }
269              
270             # Private parser
271              
272             sub ipv6_parse_base85
273             {
274 5     5 0 2468 my $ip = shift;
275 5         17 match_or_die ($ip, 'base85');
276 2         2 my $r;
277 2         12 my $bigint = Math::Base85::from_base85($ip);
278 2         10703 my @result;
279 2         11 while ($bigint > 0) {
280 16         5145 $r = $bigint & 0xffff;
281 16         3741 unshift @result, sprintf("%d", $r);
282 16         387 $bigint = $bigint >> 16;
283             }
284 2         659 foreach $r ($#result+1..7) {
285 0         0 $result[$r] = 0;
286             }
287 2         14 return @result;
288             }
289              
290             # ____ _ _ _
291             # | _ \ _ _| |__ | (_) ___
292             # | |_) | | | | '_ \| | |/ __|
293             # | __/| |_| | |_) | | | (__
294             # |_| \__,_|_.__/|_|_|\___|
295             #
296              
297             sub new
298             {
299 489     489 1 195980 my $proto = shift;
300 489   33     1421 my $class = ref($proto) || $proto;
301 489         679 my $maybe_ip = shift;
302 489         735 my $parser = ipv6_chkip($maybe_ip);
303 489 100       928 if (ref $parser ne 'CODE') {
304 31         96 mycroak "invalid IPv6 address $maybe_ip";
305             }
306 458         826 my @hexadecets = $parser->($maybe_ip);
307 458         672 my $self = \@hexadecets;
308 458         639 bless $self, $class;
309 458         962 return $self;
310             }
311              
312              
313             sub ipv6_chkip
314             {
315 739     739 1 67535 my $ip = shift;
316              
317 739         1019 my $parser = undef;
318              
319             TYPE:
320 739         1864 for my $k (keys %ipv6_patterns) {
321 3666         4030 my @patlist = @{$ipv6_patterns{$k}};
  3666         6180  
322             PATTERN:
323 3666         4654 for my $pattern (@patlist) {
324 14013 100       24507 last PATTERN if (ref($pattern) eq 'CODE');
325 10928 100       31703 if ($ip =~ $pattern) {
326 581         850 $parser = $patlist[-1];
327 581         1001 last TYPE;
328             }
329             }
330             }
331 739         1746 return $parser;
332             }
333              
334              
335             sub ipv6_parse
336             {
337 19     19 1 480 my ($ip, $pfx) = getargs (@_);
338              
339 19 100       50 if (! ipv6_chkip($ip)) {
340 4         20 mycroak "invalid IPv6 address $ip";
341             }
342              
343 15 100       45 if (! defined $pfx) {
344 3         26 return $ip;
345             }
346              
347 12         27 $pfx =~ s/\s+//g;
348              
349 12 100       38 if ($pfx =~ /^[0-9]+$/) {
350 7 100       26 if ($pfx > 128) {
351 2         8 mycroak "invalid prefix length $pfx";
352             }
353             }
354             else {
355 5         19 mycroak "non-numeric prefix length $pfx";
356             }
357              
358 5 100       14 if (wantarray ()) {
359 2         11 return ($ip, $pfx);
360             }
361 3         11 return "$ip/$pfx";
362             }
363              
364              
365             sub is_ipv6
366             {
367 10     10 1 14179 my $r;
368 10         18 eval {
369 10         22 $r = ipv6_parse (@_);
370             };
371 10 100       56 if ($@) {
372 6         26 return undef;
373             }
374 4         14 return $r;
375             }
376              
377              
378             sub to_string_preferred
379             {
380 26     26 1 96 my $self = shift;
381 26 50       60 if (ref $self ne __PACKAGE__) {
382 0         0 $self = Net::IPv6Addr->new ($self);
383             }
384 26         69 return v6part (@$self);
385             }
386              
387              
388             sub to_string_compressed
389             {
390 426     426 1 3344 my $self = shift;
391 426 100       791 if (ref $self ne __PACKAGE__) {
392 212         342 $self = Net::IPv6Addr->new ($self);
393             }
394 426         681 my $expanded = v6part (@$self);
395 426         823 $expanded =~ s/^0:/:/;
396 426         1111 $expanded =~ s/:0/:/g;
397 426 100 100     2261 if ($expanded =~ s/:::::::/_/ or
      100        
      100        
      100        
      100        
398             $expanded =~ s/::::::/_/ or
399             $expanded =~ s/:::::/_/ or
400             $expanded =~ s/::::/_/ or
401             $expanded =~ s/:::/_/ or
402             $expanded =~ s/::/_/
403             ) {
404 409         926 $expanded =~ s/:(?=:)/:0/g;
405 409         484 $expanded =~ s/^:(?=[0-9a-f])/0:/;
406 409         481 $expanded =~ s/([0-9a-f]):$/$1:0/;
407 409         699 $expanded =~ s/_/::/;
408             }
409 426         991 return $expanded;
410             }
411              
412             sub bytes
413             {
414 24     24 0 36 my ($in) = @_;
415 24         35 my $low = $in & 0xff;
416 24         31 my $high = $in >> 8;
417 24         66 return ($high, $low);
418             }
419              
420             sub v4part
421             {
422 12     12 0 22 my ($t, $b) = @_;
423 12         22 return join('.', bytes ($t), bytes ($b));
424             }
425              
426             sub v6part
427             {
428 464     464 0 637 return join(':', map { sprintf("%x", $_) } @_);
  3688         6652  
429             }
430              
431             sub to_string_ipv4
432             {
433 6     6 1 12 my $self = shift;
434 6 50       20 if (ref $self ne __PACKAGE__) {
435 0         0 $self = Net::IPv6Addr->new ($self);
436             }
437 6         28 my $v6part = v6part (@$self[0..5]);
438 6         18 my $v4part = v4part (@$self[6, 7]);
439 6         28 return "$v6part:$v4part";
440             }
441              
442              
443             sub to_string_ipv4_compressed
444             {
445 6     6 1 12 my $self = shift;
446 6 50       20 if (ref $self ne __PACKAGE__) {
447 0         0 $self = Net::IPv6Addr->new ($self);
448             }
449 6         18 my $v6part = v6part (@$self[0..5]);
450 6         13 $v6part .= ':';
451 6         36 $v6part =~ s/(^|:)(0:)+/::/;
452 6         16 my $v4part = v4part (@$self[6, 7]);
453 6         24 return "$v6part$v4part";
454             }
455              
456              
457             sub to_string_base85
458             {
459 1     1 1 3998 my $self = shift;
460 1 50       7 if (ref $self ne __PACKAGE__) {
461 0         0 $self = Net::IPv6Addr->new ($self);
462             }
463 1         9 my $bigint = new Math::BigInt("0");
464 1         115 for my $i (@{$self}[0..6]) {
  1         4  
465 7         1350 $bigint = $bigint + $i;
466 7         1005 $bigint = $bigint << 16;
467             }
468 1         213 $bigint = $bigint + $self->[7];
469 1         126 return Math::Base85::to_base85($bigint);
470             }
471              
472              
473             sub to_bigint
474             {
475 104     104 1 281 my $self = shift;
476 104 50       200 if (ref $self ne __PACKAGE__) {
477 0         0 $self = Net::IPv6Addr->new ($self);
478             }
479 104         293 my $bigint = new Math::BigInt("0");
480 104         8925 for my $i (@{$self}[0..6]) {
  104         216  
481 728         120857 $bigint = $bigint + $i;
482 728         106954 $bigint = $bigint << 16;
483             }
484 104         21654 $bigint = $bigint + $self->[7];
485 104         13504 $bigint =~ s/\+//;
486 104         2645 return $bigint;
487             }
488              
489              
490             sub to_array
491             {
492 104     104 1 248 my $self = shift;
493 104 50       217 if (ref $self ne __PACKAGE__) {
494 0         0 $self = Net::IPv6Addr->new ($self);
495             }
496 104         179 return map {sprintf "%04x", $_} @$self;
  832         1513  
497             }
498              
499              
500             sub to_intarray
501             {
502 114     114 1 323 my $self = shift;
503 114 50       255 if (ref $self ne __PACKAGE__) {
504 0         0 $self = Net::IPv6Addr->new ($self);
505             }
506 114         252 return @$self;
507             }
508              
509              
510             sub to_string_ip6_int
511             {
512 6     6 1 13 my $self = shift;
513 6 50       17 if (ref $self ne __PACKAGE__) {
514 0         0 $self = Net::IPv6Addr->new ($self);
515             }
516 6         32 my $hexdigits = sprintf("%04x" x 8, @$self);
517 6         44 my @nibbles = ('INT', 'IP6', split(//, $hexdigits));
518 6         31 my $ptr = join('.', reverse @nibbles);
519 6         34 return $ptr . ".";
520             }
521              
522             # Private - validate a given netsize
523              
524             sub validate_netsize
525             {
526 15     15 0 27 my ($netsize) = @_;
527 15 100 66     84 if ($netsize !~ /^[0-9]+$/ || $netsize > 128) {
528 1         18 mycroak "invalid network size $netsize";
529             }
530             }
531              
532              
533             sub in_network_of_size
534             {
535 10     10 1 559 my $self = shift;
536 10 50       26 if (ref $self ne __PACKAGE__) {
537 0 0       0 if ($self =~ m!(.+)/(.+)!) {
538 0         0 unshift @_, $2;
539 0         0 $self = $1;
540             }
541 0         0 $self = Net::IPv6Addr->new($self);
542             }
543 10         22 my $netsize = shift;
544 10 50       18 if (! defined $netsize) {
545 0         0 mycroak "network size not given";
546             }
547 10         17 $netsize =~ s!/!!;
548 10         19 validate_netsize ($netsize);
549 10         29 my @parts = @$self;
550 10         26 my $i = int ($netsize / 16);
551 10 50       27 if ($i < 8) {
552 10         16 my $j = $netsize % 16;
553 10 100       18 if ($j) {
554             # If $netsize is not a multiple of 16, truncate the lowest
555             # 16-$j bits of the $ith element of @parts.
556 3         8 $parts[$i] &= bitmask ($j);
557             # Jump over this element.
558 3         5 $i++;
559             }
560             # Set all the remaining lower parts to zero.
561 10         22 for ($i..$#parts) {
562 41         51 $parts[$_] = 0;
563             }
564             }
565 10         36 return bless \@parts;
566             }
567              
568              
569             sub in_network
570             {
571 5     5 1 1200 my $self = shift;
572 5 50       15 if (ref $self ne __PACKAGE__) {
573 0         0 $self = Net::IPv6Addr->new ($self);
574             }
575 5         13 my ($net, $netsize) = getargs (@_);
576 5 50       13 unless (defined $netsize) {
577 0         0 mycroak "not enough parameters, need netsize";
578             }
579 5         12 $netsize =~ s!/!!;
580 5         9 validate_netsize ($netsize);
581 4 50       9 if (! ref $net) {
582 4         12 $net = Net::IPv6Addr->new($net);
583             }
584 4         10 my @s = $self->in_network_of_size($netsize)->to_intarray;
585 4         21 my @n = $net->in_network_of_size($netsize)->to_intarray;
586 4         11 my $i = int ($netsize / 16) + 1;
587 4 50       10 if ($i > $#s) {
588 0         0 $i = $#s;
589             }
590 4         8 for (0..$i) {
591 17 100       37 if ($s[$_] != $n[$_]) {
592 1         6 return undef;
593             }
594             }
595 3         12 return 1;
596             }
597              
598             sub from_bigint
599             {
600 104     104 1 304 my ($big) = @_;
601             # Input is a scalar or a Math::BigInt object.
602 104 50       196 if (! ref ($big)) {
603 0         0 $big = Math::BigInt->new ($big);
604             }
605 104 50       182 if (ref ($big) ne 'Math::BigInt') {
606 0         0 mycroak "Cannot process non-scalar, non-Math::BigInt input";
607             }
608             # Convert the number to a hexadecimal string
609 104         219 my $hex = $big->to_hex ();
610             # Pad if necessary for the colon placement
611 104 100       24886 if (length ($hex) < 32) {
612 66         117 my $leading = '0' x (32 - length ($hex));
613 66         116 $hex = $leading . $hex;
614             }
615             # Reversing the string makes adding colons with a substitution
616             # operator easier.
617 104         194 my $ipr = reverse $hex;
618 104         938 $ipr =~ s/(....)/$1:/g;
619 104         218 $ipr = reverse $ipr;
620             # Remove the excess colon.
621 104         288 $ipr =~ s/^://;
622             # Should be OK now, let "new" handle any further issues.
623 104         248 return Net::IPv6Addr->new ($ipr);
624             }
625              
626             1;