File Coverage

blib/lib/Net/IPv6Addr.pm
Criterion Covered Total %
statement 245 292 83.9
branch 56 82 68.2
condition 18 21 85.7
subroutine 34 36 94.4
pod 16 32 50.0
total 369 463 79.7


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