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