File Coverage

blib/lib/Paranoid/Network/IPv6.pm
Criterion Covered Total %
statement 172 176 97.7
branch 35 44 79.5
condition 13 21 61.9
subroutine 27 27 100.0
pod 6 6 100.0
total 253 274 92.3


line stmt bran cond sub pod time code
1             # Paranoid::Network::IPv6 -- IPv6-specific network functions
2             #
3             # $Id: lib/Paranoid/Network/IPv6.pm, 2.08 2020/12/31 12:10:06 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Network::IPv6;
33              
34 3     3   604 use 5.008;
  3         10  
35              
36 3     3   17 use strict;
  3         5  
  3         59  
37 3     3   16 use warnings;
  3         5  
  3         99  
38 3     3   15 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  3         6  
  3         185  
39 3     3   18 use base qw(Exporter);
  3         6  
  3         222  
40 3     3   20 use Paranoid;
  3         6  
  3         169  
41 3     3   20 use Paranoid::Debug qw(:all);
  3         5  
  3         596  
42 3     3   503 use Paranoid::Network::Socket;
  3         8  
  3         1790  
43              
44             my @base = qw(ipv6NetConvert ipv6NetPacked ipv6NetIntersect);
45             my @constants = qw(MAXIPV6CIDR IPV6REGEX IPV6CIDRRGX IPV6BASE IPV6BRDCST
46             IPV6MASK);
47             my @ipv6sort = qw(ipv6StrSort ipv6PackedSort ipv6NumSort);
48              
49             ($VERSION) = ( q$Revision: 2.08 $ =~ /(\d+(?:\.\d+)+)/sm );
50             @EXPORT = @base;
51             @EXPORT_OK = ( @base, @constants, @ipv6sort );
52             %EXPORT_TAGS = (
53             all => [@EXPORT_OK],
54             base => [@base],
55             constants => [@constants],
56             ipv6Sort => [@ipv6sort],
57             );
58              
59 3     3   25 use constant MAXIPV6CIDR => 128;
  3         7  
  3         434  
60 3         288 use constant IPV6REGEX => qr/
61             :(?::[abcdef\d]{1,4}){1,7} |
62             \b[abcdef\d]{1,4}(?:::?[abcdef\d]{1,4}){1,7} |
63             (?:\b[abcdef\d]{1,4}:){1,7}:
64 3     3   24 /six;
  3         6  
65 3         6 use constant IPV6CIDRRGX =>
66 3     3   23 qr#@{[ IPV6REGEX ]}/(?:1(?:[01]\d|2[0-8])|\d\d?)#s;
  3         6  
  3         446  
67 3     3   23 use constant IPV6BASE => 0;
  3         6  
  3         144  
68 3     3   19 use constant IPV6BRDCST => 1;
  3         5  
  3         156  
69 3     3   20 use constant IPV6MASK => 2;
  3         5  
  3         151  
70 3     3   19 use constant CHUNKMASK => 0xffffffff;
  3         6  
  3         165  
71 3     3   19 use constant CHUNK => 32;
  3         6  
  3         149  
72 3     3   16 use constant IPV6CHUNKS => 4;
  3         6  
  3         256  
73 3     3   23 use constant IPV6LENGTH => 16;
  3         6  
  3         3160  
74              
75             #####################################################################
76             #
77             # Module code follows
78             #
79             #####################################################################
80              
81             sub ipv6NetConvert {
82              
83             # Purpose: Takes a string representation of an IPv6 network
84             # address and returns a list of lists containing
85             # the binary network address, broadcast address,
86             # and netmask, each broken into 32bit chunks.
87             # Also allows for a plain IP being passed, in which
88             # case it only returns the binary IP.
89             # Returns: Array, empty on errors
90             # Usage: @network = ipv6NetConvert($netAddr);
91              
92 43     43 1 8030 my $netAddr = shift;
93 43         73 my ( $bnet, $bmask, $t, @tmp, @rv );
94              
95 43         104 pdebug( 'entering w/%s', PDLEVEL1, $netAddr );
96 43         105 pIn();
97              
98 43 50 33     90 if ( has_ipv6() or $] >= 5.012 ) {
99              
100             # Extract net address, mask
101 43 100       95 if ( defined $netAddr ) {
102 42         73 ($t) =
103 42         78 ( $netAddr =~ m#^(@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$#s )
  42         694  
104             [0];
105 42 100       199 ( $bnet, $bmask ) = split m#/#s, $t if defined $t;
106             }
107              
108 43 100 66     150 if ( defined $bnet and length $bnet ) {
109              
110             # First, convert $bnet to see if we have a valid IP address
111 32         186 $bnet = [ unpack 'NNNN', inet_pton( AF_INET6(), $bnet ) ];
112              
113 32 50 33     133 if ( defined $bnet and length $bnet ) {
114              
115             # Save our network address
116 32         61 push @rv, $bnet;
117              
118 32 100 66     88 if ( defined $bmask and length $bmask ) {
119              
120             # Convert netmask
121 14 50       47 if ( $bmask <= MAXIPV6CIDR ) {
122              
123             # Add the mask in 32-bit chunks
124 14         20 @tmp = ();
125 14         30 while ( $bmask >= CHUNK ) {
126 24         32 push @tmp, CHUNKMASK;
127 24         52 $bmask -= CHUNK;
128             }
129              
130             # Push the final segment if there's a remainder
131 14 100       26 if ($bmask) {
132 4         12 push @tmp,
133             CHUNKMASK - ( ( 2**( CHUNK - $bmask ) ) - 1 );
134             }
135              
136             # Add zero'd chunks to fill it out
137 14         56 while ( @tmp < IPV6CHUNKS ) {
138 28         59 push @tmp, 0x0;
139             }
140              
141             # Finally, save the chunks
142 14         29 $bmask = [@tmp];
143              
144             } else {
145 0         0 $bmask = undef;
146             }
147              
148 14 50       34 if ( defined $bmask ) {
149              
150             # Apply the mask to the base address
151 14         36 foreach ( 0 .. ( IPV6CHUNKS - 1 ) ) {
152 56         90 $$bnet[$_] &= $$bmask[$_];
153             }
154              
155             # Calculate and save our broadcast address
156 14         24 @tmp = ();
157 14         29 foreach ( 0 .. ( IPV6CHUNKS - 1 ) ) {
158 56         92 $tmp[$_] =
159             $$bnet[$_] | ( $$bmask[$_] ^ CHUNKMASK );
160             }
161 14         30 push @rv, [@tmp];
162              
163             # Save our mask
164 14         28 push @rv, $bmask;
165              
166             } else {
167 0         0 pdebug( 'invalid netmask passed', PDLEVEL1 );
168             }
169             }
170              
171             } else {
172 0         0 pdebug( 'failed to convert IPv6 address', PDLEVEL1 );
173             }
174             } else {
175 11         24 pdebug( 'failed to extract an IPv6 address', PDLEVEL1 );
176             }
177              
178             } else {
179 0         0 pdebug( 'IPv6 support not present', PDLEVEL1 );
180             }
181              
182 43         113 pOut();
183 43         106 pdebug( 'leaving w/rv: %s', PDLEVEL1, @rv );
184              
185 43         112 return @rv;
186             }
187              
188             sub ipv6NetPacked {
189              
190             # Purpose: Wrapper script for ipv6NetConvert that repacks all of its
191             # 32bit chunks into opaque strings in network-byte order.
192             # Returns: Array
193             # Usage: @network = ipv6NetPacked($netAddr);
194              
195 1     1 1 5 my $netAddr = shift;
196 1         2 my @rv;
197              
198 1         4 pdebug( 'entering w/%s', PDLEVEL1, $netAddr );
199 1         4 pIn();
200              
201 1         3 @rv = ipv6NetConvert($netAddr);
202 1         4 foreach (@rv) {
203 1         6 $_ = pack 'NNNN', @$_;
204             }
205              
206 1         4 pOut();
207 1         3 pdebug( 'leaving w/%s', PDLEVEL1, @rv );
208              
209 1         4 return @rv;
210             }
211              
212             sub _cmpArrays {
213              
214             # Purpose: Compares IPv6 chunked address arrays
215             # Returns: -1: net1 < net 2
216             # 0: net1 == net2
217             # 1: net1 > net2
218             # Usage: $rv = _cmpArrays( $aref1, $aref2 );
219              
220 50     50   79 my $aref1 = shift;
221 50         66 my $aref2 = shift;
222 50         72 my $rv = 0;
223              
224 50         114 pdebug( 'entering w/%s, %s', PDLEVEL2, $aref1, $aref2 );
225 50         117 pIn();
226              
227 50         96 while ( scalar @$aref1 ) {
228 67 100       127 unless ( $$aref1[0] == $$aref2[0] ) {
229 46 100       83 $rv = $$aref1[0] > $$aref2[0] ? 1 : -1;
230 46         71 last;
231             }
232 21         32 shift @$aref1;
233 21         39 shift @$aref2;
234             }
235              
236 50         107 pOut();
237 50         107 pdebug( 'leaving w/rv: %s', PDLEVEL2, $rv );
238              
239 50         176 return $rv;
240             }
241              
242             sub ipv6NetIntersect {
243              
244             # Purpose: Tests whether network address ranges intersect
245             # Returns: Integer, denoting whether an intersection exists, and what
246             # kind:
247             #
248             # -1: destination range encompasses target range
249             # 0: both ranges do not intersect at all
250             # 1: target range encompasses destination range
251             #
252             # Usage: $rv = ipv6NetIntersect($net1, $net2);
253              
254 18     18 1 714 my $tgt = shift;
255 18         31 my $dest = shift;
256 18         27 my $rv = 0;
257 18         32 my ( @tnet, @dnet );
258              
259 18         53 pdebug( 'entering w/%s, %s', PDLEVEL1, $tgt, $dest );
260 18         47 pIn();
261              
262             # Bypas if one or both isn't defined -- obviously no intersection
263 18 50 33     81 unless ( !defined $tgt or !defined $dest ) {
264              
265             # Treat any array references as IPv6 addresses already translated into
266             # 32bit integer chunks
267 18 50       52 @tnet = ref($tgt) eq 'ARRAY' ? $tgt : ipv6NetConvert($tgt);
268 18 50       53 @dnet = ref($dest) eq 'ARRAY' ? $dest : ipv6NetConvert($dest);
269              
270             # insert bogus numbers for non IP-address info
271 18 100       46 @tnet = ( [ -1, 0, 0, 0 ] ) unless scalar @tnet;
272 18 100       43 @dnet = ( [ -2, 0, 0, 0 ] ) unless scalar @dnet;
273              
274             # Dummy up broadcast address for those single IPs passed (in lieu of
275             # network ranges)
276 18 100       39 if ( $#tnet == 0 ) {
277 14         25 $tnet[IPV6BRDCST] = $tnet[IPV6BASE];
278 14         26 $tnet[IPV6MASK] = [ CHUNKMASK, CHUNKMASK, CHUNKMASK, CHUNKMASK ];
279             }
280 18 100       51 if ( $#dnet == 0 ) {
281 10         16 $dnet[IPV6BRDCST] = $dnet[IPV6BASE];
282 10         21 $dnet[IPV6MASK] = [ CHUNKMASK, CHUNKMASK, CHUNKMASK, CHUNKMASK ];
283             }
284              
285 18 100 100     43 if ( _cmpArrays( $tnet[IPV6BASE], $dnet[IPV6BASE] ) <= 0
    100 100        
286             and _cmpArrays( $tnet[IPV6BRDCST], $dnet[IPV6BRDCST] ) >= 0 ) {
287              
288             # Target fully encapsulates dest
289 4         9 $rv = 1;
290              
291             } elsif ( _cmpArrays( $tnet[IPV6BASE], $dnet[IPV6BASE] ) >= 0
292             and _cmpArrays( $tnet[IPV6BRDCST], $dnet[IPV6BRDCST] ) <= 0 ) {
293              
294             # Dest fully encapsulates target
295 5         23 $rv = -1;
296              
297             }
298             }
299              
300 18         45 pOut();
301 18         64 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
302              
303 18         84 return $rv;
304             }
305              
306             {
307              
308 3     3   26 no strict 'refs';
  3         5  
  3         458  
309              
310             sub ipv6NumSort {
311              
312             # Purpose: Sorts IPv6 addresses represented in numeric form
313             # Returns: -1, 0, 1
314             # Usage: @sorted = sort &ipv6NumSort @ipv4;
315              
316 4     4 1 1980 my ($pkg) = caller;
317 4         8 my ( $i, $rv );
318              
319 4         10 foreach $i ( 0 .. 3 ) {
320 4         6 $rv = $${"${pkg}::a"}[$i] <=> $${"${pkg}::b"}[$i];
  4         10  
  4         10  
321 4 50       11 last if $rv;
322             }
323              
324 4         12 return $rv;
325             }
326              
327             sub ipv6PackedSort {
328              
329             # Purpose: Sorts IPv6 addresses represented by packed strings
330             # Returns: -1, 0, 1
331             # Usage: @sorted = sort &ipv6PackedSort @ipv6;
332              
333 3     3   37 no warnings 'once';
  3         20  
  3         1035  
334              
335 2     2 1 1952 my ($pkg) = caller;
336 2         5 $a = [ unpack 'NNNN', ${"${pkg}::a"} ];
  2         9  
337 2         4 $b = [ unpack 'NNNN', ${"${pkg}::b"} ];
  2         6  
338              
339 2         6 return ipv6NumSort();
340             }
341              
342             sub ipv6StrSort {
343              
344             # Purpose: Sorts IPv6 addresses represented in string form
345             # Returns: -1, 0, 1
346             # Usage: @sorted = sort &ipv4StrSort @ipv4;
347              
348 2     2 1 714 my ($pkg) = caller;
349 2         3 my $a1 = ${"${pkg}::a"};
  2         18  
350 2         4 my $b1 = ${"${pkg}::b"};
  2         4  
351 2         4 my ( $i, $rv );
352              
353 2         6 $a1 =~ s#/.+##s;
354 2         12 $a1 = [ unpack 'NNNN', inet_pton( AF_INET6(), $a1 ) ];
355 2         9 $b1 =~ s#/.+##s;
356 2         8 $b1 = [ unpack 'NNNN', inet_pton( AF_INET6(), $b1 ) ];
357              
358 2         6 foreach $i ( 0 .. 3 ) {
359 2         5 $rv = $$a1[$i] <=> $$b1[$i];
360 2 50       5 last if $rv;
361             }
362              
363 2         7 return $rv;
364             }
365             }
366              
367             1;
368              
369             __END__