File Coverage

blib/lib/Paranoid/Network/IPv6.pm
Criterion Covered Total %
statement 164 168 97.6
branch 35 44 79.5
condition 13 21 61.9
subroutine 27 27 100.0
pod 6 6 100.0
total 245 266 92.1


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.10 2022/03/08 00:01:04 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   657 use 5.008;
  3         11  
35              
36 3     3   14 use strict;
  3         3  
  3         55  
37 3     3   22 use warnings;
  3         6  
  3         101  
38 3     3   14 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  3         4  
  3         215  
39 3     3   17 use base qw(Exporter);
  3         5  
  3         239  
40 3     3   17 use Paranoid;
  3         3  
  3         196  
41 3     3   20 use Paranoid::Debug qw(:all);
  3         6  
  3         533  
42 3     3   450 use Paranoid::Network::Socket;
  3         8  
  3         1612  
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.10 $ =~ /(\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         5  
  3         426  
60 3         237 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   19 /six;
  3         5  
65 3         4 use constant IPV6CIDRRGX =>
66 3     3   18 qr#@{[ IPV6REGEX ]}/(?:1(?:[01]\d|2[0-8])|\d\d?)#s;
  3         6  
  3         398  
67 3     3   19 use constant IPV6BASE => 0;
  3         5  
  3         119  
68 3     3   14 use constant IPV6BRDCST => 1;
  3         4  
  3         138  
69 3     3   16 use constant IPV6MASK => 2;
  3         5  
  3         165  
70 3     3   18 use constant CHUNKMASK => 0xffffffff;
  3         4  
  3         147  
71 3     3   18 use constant CHUNK => 32;
  3         3  
  3         148  
72 3     3   14 use constant IPV6CHUNKS => 4;
  3         6  
  3         144  
73 3     3   16 use constant IPV6LENGTH => 16;
  3         5  
  3         2770  
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 5797 my $netAddr = shift;
93 43         62 my ( $bnet, $bmask, $t, @tmp, @rv );
94              
95 43         117 subPreamble( PDLEVEL1, '$', $netAddr );
96              
97 43 50 33     122 if ( has_ipv6() or $] >= 5.012 ) {
98              
99             # Extract net address, mask
100 43 100       90 if ( defined $netAddr ) {
101 42         75 ($t) =
102 42         76 ( $netAddr =~ m#^(@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$#s )
  42         750  
103             [0];
104 42 100       199 ( $bnet, $bmask ) = split m#/#s, $t if defined $t;
105             }
106              
107 43 100 66     132 if ( defined $bnet and length $bnet ) {
108              
109             # First, convert $bnet to see if we have a valid IP address
110 32         201 $bnet = [ unpack 'NNNN', inet_pton( AF_INET6(), $bnet ) ];
111              
112 32 50 33     132 if ( defined $bnet and length $bnet ) {
113              
114             # Save our network address
115 32         56 push @rv, $bnet;
116              
117 32 100 66     83 if ( defined $bmask and length $bmask ) {
118              
119             # Convert netmask
120 14 50       38 if ( $bmask <= MAXIPV6CIDR ) {
121              
122             # Add the mask in 32-bit chunks
123 14         24 @tmp = ();
124 14         40 while ( $bmask >= CHUNK ) {
125 24         35 push @tmp, CHUNKMASK;
126 24         41 $bmask -= CHUNK;
127             }
128              
129             # Push the final segment if there's a remainder
130 14 100       30 if ($bmask) {
131 4         11 push @tmp,
132             CHUNKMASK - ( ( 2**( CHUNK - $bmask ) ) - 1 );
133             }
134              
135             # Add zero'd chunks to fill it out
136 14         30 while ( @tmp < IPV6CHUNKS ) {
137 28         45 push @tmp, 0x0;
138             }
139              
140             # Finally, save the chunks
141 14         32 $bmask = [@tmp];
142              
143             } else {
144 0         0 $bmask = undef;
145             }
146              
147 14 50       30 if ( defined $bmask ) {
148              
149             # Apply the mask to the base address
150 14         36 foreach ( 0 .. ( IPV6CHUNKS - 1 ) ) {
151 56         86 $$bnet[$_] &= $$bmask[$_];
152             }
153              
154             # Calculate and save our broadcast address
155 14         28 @tmp = ();
156 14         22 foreach ( 0 .. ( IPV6CHUNKS - 1 ) ) {
157 56         94 $tmp[$_] =
158             $$bnet[$_] | ( $$bmask[$_] ^ CHUNKMASK );
159             }
160 14         27 push @rv, [@tmp];
161              
162             # Save our mask
163 14         26 push @rv, $bmask;
164              
165             } else {
166 0         0 pdebug( 'invalid netmask passed', PDLEVEL1 );
167             }
168             }
169              
170             } else {
171 0         0 pdebug( 'failed to convert IPv6 address', PDLEVEL1 );
172             }
173             } else {
174 11         28 pdebug( 'failed to extract an IPv6 address', PDLEVEL1 );
175             }
176              
177             } else {
178 0         0 pdebug( 'IPv6 support not present', PDLEVEL1 );
179             }
180              
181 43         114 subPostamble( PDLEVEL1, '@', @rv );
182              
183 43         105 return @rv;
184             }
185              
186             sub ipv6NetPacked {
187              
188             # Purpose: Wrapper script for ipv6NetConvert that repacks all of its
189             # 32bit chunks into opaque strings in network-byte order.
190             # Returns: Array
191             # Usage: @network = ipv6NetPacked($netAddr);
192              
193 1     1 1 7 my $netAddr = shift;
194 1         3 my @rv;
195              
196 1         4 subPreamble( PDLEVEL1, '$', $netAddr );
197              
198 1         59 @rv = ipv6NetConvert($netAddr);
199 1         38 foreach (@rv) {
200 1         11 $_ = pack 'NNNN', @$_;
201             }
202              
203 1         5 subPostamble( PDLEVEL1, '@', @rv );
204              
205 1         3 return @rv;
206             }
207              
208             sub _cmpArrays {
209              
210             # Purpose: Compares IPv6 chunked address arrays
211             # Returns: -1: net1 < net 2
212             # 0: net1 == net2
213             # 1: net1 > net2
214             # Usage: $rv = _cmpArrays( $aref1, $aref2 );
215              
216 50     50   78 my $aref1 = shift;
217 50         59 my $aref2 = shift;
218 50         57 my $rv = 0;
219              
220 50         116 subPreamble( PDLEVEL2, '\@\@', $aref1, $aref2 );
221              
222 50         118 while ( scalar @$aref1 ) {
223 67 100       120 unless ( $$aref1[0] == $$aref2[0] ) {
224 46 100       105 $rv = $$aref1[0] > $$aref2[0] ? 1 : -1;
225 46         70 last;
226             }
227 21         25 shift @$aref1;
228 21         35 shift @$aref2;
229             }
230              
231 50         116 subPostamble( PDLEVEL2, '$', $rv );
232              
233 50         161 return $rv;
234             }
235              
236             sub ipv6NetIntersect {
237              
238             # Purpose: Tests whether network address ranges intersect
239             # Returns: Integer, denoting whether an intersection exists, and what
240             # kind:
241             #
242             # -1: destination range encompasses target range
243             # 0: both ranges do not intersect at all
244             # 1: target range encompasses destination range
245             #
246             # Usage: $rv = ipv6NetIntersect($net1, $net2);
247              
248 18     18 1 477 my $tgt = shift;
249 18         31 my $dest = shift;
250 18         30 my $rv = 0;
251 18         33 my ( @tnet, @dnet );
252              
253 18         54 subPreamble( PDLEVEL1, '$$', $tgt, $dest );
254              
255             # Bypas if one or both isn't defined -- obviously no intersection
256 18 50 33     74 unless ( !defined $tgt or !defined $dest ) {
257              
258             # Treat any array references as IPv6 addresses already translated into
259             # 32bit integer chunks
260 18 50       56 @tnet = ref($tgt) eq 'ARRAY' ? $tgt : ipv6NetConvert($tgt);
261 18 50       64 @dnet = ref($dest) eq 'ARRAY' ? $dest : ipv6NetConvert($dest);
262              
263             # insert bogus numbers for non IP-address info
264 18 100       44 @tnet = ( [ -1, 0, 0, 0 ] ) unless scalar @tnet;
265 18 100       44 @dnet = ( [ -2, 0, 0, 0 ] ) unless scalar @dnet;
266              
267             # Dummy up broadcast address for those single IPs passed (in lieu of
268             # network ranges)
269 18 100       46 if ( $#tnet == 0 ) {
270 14         19 $tnet[IPV6BRDCST] = $tnet[IPV6BASE];
271 14         27 $tnet[IPV6MASK] = [ CHUNKMASK, CHUNKMASK, CHUNKMASK, CHUNKMASK ];
272             }
273 18 100       59 if ( $#dnet == 0 ) {
274 10         17 $dnet[IPV6BRDCST] = $dnet[IPV6BASE];
275 10         24 $dnet[IPV6MASK] = [ CHUNKMASK, CHUNKMASK, CHUNKMASK, CHUNKMASK ];
276             }
277              
278 18 100 100     154 if ( _cmpArrays( $tnet[IPV6BASE], $dnet[IPV6BASE] ) <= 0
    100 100        
279             and _cmpArrays( $tnet[IPV6BRDCST], $dnet[IPV6BRDCST] ) >= 0 ) {
280              
281             # Target fully encapsulates dest
282 4         10 $rv = 1;
283              
284             } elsif ( _cmpArrays( $tnet[IPV6BASE], $dnet[IPV6BASE] ) >= 0
285             and _cmpArrays( $tnet[IPV6BRDCST], $dnet[IPV6BRDCST] ) <= 0 ) {
286              
287             # Dest fully encapsulates target
288 5         8 $rv = -1;
289              
290             }
291             }
292              
293 18         59 subPostamble( PDLEVEL1, '$', $rv );
294              
295 18         106 return $rv;
296             }
297              
298             {
299              
300 3     3   22 no strict 'refs';
  3         6  
  3         396  
301              
302             sub ipv6NumSort {
303              
304             # Purpose: Sorts IPv6 addresses represented in numeric form
305             # Returns: -1, 0, 1
306             # Usage: @sorted = sort &ipv6NumSort @ipv4;
307              
308 4     4 1 1324 my ($pkg) = caller;
309 4         6 my ( $i, $rv );
310              
311 4         7 foreach $i ( 0 .. 3 ) {
312 4         6 $rv = $${"${pkg}::a"}[$i] <=> $${"${pkg}::b"}[$i];
  4         13  
  4         9  
313 4 50       9 last if $rv;
314             }
315              
316 4         12 return $rv;
317             }
318              
319             sub ipv6PackedSort {
320              
321             # Purpose: Sorts IPv6 addresses represented by packed strings
322             # Returns: -1, 0, 1
323             # Usage: @sorted = sort &ipv6PackedSort @ipv6;
324              
325 3     3   23 no warnings 'once';
  3         5  
  3         967  
326              
327 2     2 1 1411 my ($pkg) = caller;
328 2         5 $a = [ unpack 'NNNN', ${"${pkg}::a"} ];
  2         9  
329 2         4 $b = [ unpack 'NNNN', ${"${pkg}::b"} ];
  2         5  
330              
331 2         7 return ipv6NumSort();
332             }
333              
334             sub ipv6StrSort {
335              
336             # Purpose: Sorts IPv6 addresses represented in string form
337             # Returns: -1, 0, 1
338             # Usage: @sorted = sort &ipv4StrSort @ipv4;
339              
340 2     2 1 554 my ($pkg) = caller;
341 2         3 my $a1 = ${"${pkg}::a"};
  2         6  
342 2         3 my $b1 = ${"${pkg}::b"};
  2         5  
343 2         2 my ( $i, $rv );
344              
345 2         6 $a1 =~ s#/.+##s;
346 2         11 $a1 = [ unpack 'NNNN', inet_pton( AF_INET6(), $a1 ) ];
347 2         9 $b1 =~ s#/.+##s;
348 2         22 $b1 = [ unpack 'NNNN', inet_pton( AF_INET6(), $b1 ) ];
349              
350 2         7 foreach $i ( 0 .. 3 ) {
351 2         4 $rv = $$a1[$i] <=> $$b1[$i];
352 2 50       6 last if $rv;
353             }
354              
355 2         9 return $rv;
356             }
357             }
358              
359             1;
360              
361             __END__