File Coverage

blib/lib/Net/CIDR/Lookup/IPv6.pm
Criterion Covered Total %
statement 116 131 88.5
branch 35 56 62.5
condition 22 35 62.8
subroutine 22 27 81.4
pod 11 11 100.0
total 206 260 79.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Net::CIDR::Lookup::IPv6
4              
5             =head1 DESCRIPTION
6              
7             This is the IPv6 version of L. It generally provides the
8             same methods, with the distinction that the C/C methods
9             that accept an IPv4 address as an integer have been split in two to accommodate
10             different representations for an IPv6 address:
11              
12             =over 1
13              
14             =item C/C accepts a 128-bit L object for an address
15              
16             =item C/C takes a packed string as returned by C
17              
18             =back
19              
20             For all other methods, see L.
21              
22             This module requires an IPv6-enabled L. As there is no way to ask for this using ExtUtils::MakeMaker, do make sure you have it.
23              
24             =cut
25              
26             #=head1 SYNOPSIS
27             # TODO flesh this out
28             #
29             # use Net::CIDR::Lookup::IPv6;
30             #
31             # $cidr = Net::CIDR::Lookup::IPv6->new;
32              
33             =head1 VERSION HISTORY
34              
35             See L
36              
37             =head1 METHODS
38              
39             =cut
40              
41             package Net::CIDR::Lookup::IPv6;
42 2     2   2365 use strict;
  2         2  
  2         50  
43 2     2   6 use warnings;
  2         2  
  2         45  
44 2     2   9 use Carp;
  2         2  
  2         128  
45             $Carp::Verbose=1;
46 2     2   7 use Socket qw/ inet_ntop inet_pton AF_INET6 /;
  2         2  
  2         83  
47 2     2   374 use Bit::Vector;
  2         689  
  2         72  
48 2     2   376 use parent 'Net::CIDR::Lookup';
  2         210  
  2         9  
49              
50             our $VERSION = '0.55';
51              
52             =head2 add
53              
54             Arguments: C<$cidr>, C<$value>
55              
56             Return Value: none; dies on error
57              
58             Adds VALUE to the tree under the key CIDR. CIDR must be a string containing an
59             IPv6 address followed by a slash and a number of network bits. Bits to the
60             right of this mask will be ignored.
61              
62             =cut
63              
64             sub add {
65 2     2 1 102 my ($self, $cidr, $val) = @_;
66              
67 2 50       7 defined $val or croak "can't store an undef";
68 2         9 my ($net, $bits) = $cidr =~ m{ ^ (.+) / (\d+) $ }ox;
69 2 50 33     12 defined $net and defined $bits or croak 'CIDR syntax error: use
/';
70 2         3 $net = _parse_address($net);
71 2         4 $self->_add($net, $bits, $val);
72             }
73              
74             =head2 add_range
75              
76             Arguments: C<$range>, C<$value>
77              
78             Return Value: none; dies on error
79              
80             Adds VALUE to the tree for each address included in RANGE which must be a
81             hyphenated range of IPv6 addresses and with the first address being numerically
82             smaller the second. This range will be split up into as many CIDR blocks as
83             necessary (algorithm adapted from a script by Dr. Liviu Daia).
84              
85             =cut
86              
87             sub add_range {
88 6     6 1 376 my ($self, $range, $val) = @_;
89              
90 6 50       12 defined $val or croak "can't store an undef";
91 6         51 my ($start, $end, $crud) = split /\s*-\s*/, $range;
92 6 50 33     31 croak 'must have exactly one hyphen in range'
93             if(defined $crud or not defined $end);
94 6         16 $self->add_vec_range(_parse_address($start), _parse_address($end), $val);
95             }
96              
97             =head2 add_vec
98              
99             Arguments: C<$address>, C<$bits>, C<$value>
100              
101             Return Value: none; dies on error
102              
103             Like C but accepts an address as a Bit::Vector object and the network
104             bits as a separate integer instead of all in one string.
105              
106             =cut
107              
108             sub add_vec { ## no critic (Subroutines::RequireArgUnpacking)
109             # my ($self, $ip, $bits, $val) = @_;
110             # Just call the recursive adder for now but allow for changes in object
111             # representation ($self != $n)
112 80 50   80 1 100 defined $_[3] or croak "can't store an undef";
113 80         82 _add(@_);
114             }
115              
116             =head2 add_str
117              
118             Arguments: C<$address>, C<$bits>, C<$value>
119              
120             Return Value: none; dies on error
121              
122             Like C but accepts an address as a packed string as returned by
123             C.
124              
125             =cut
126              
127             sub add_str { ## no critic (Subroutines::RequireArgUnpacking)
128             # my ($self, $ip, $bits, $val) = @_;
129 0     0 1 0 shift->_add_vec(_str2vec($_[0]), _str2vec($_[1]), $_[2]);
130             }
131              
132              
133             =head2 add_vec_range
134              
135             Arguments: C<$start>, C<$end>, C<$value>
136              
137             Return Value: none; dies on error
138              
139             Like C but accepts addresses as separate Bit::Vector objects
140             instead of a range string.
141              
142             =cut
143              
144             sub add_vec_range {
145 6     6 1 9 my ($self, $start, $end, $val) = @_;
146 6         5 my @chunks;
147              
148 6 50       22 1 == $start->Lexicompare($end)
149             and croak sprintf "start > end in range %s--%s", _addr2print($start), _addr2print($end);
150              
151 6         12 _do_chunk(\@chunks, $start, $end, 127, 0);
152 6         21 $self->add_vec(@$_, $val) for(@chunks);
153             }
154              
155             =head2 add_str_range
156              
157             Arguments: C<$start>, C<$end>, C<$value>
158              
159             Return Value: true for successful completion; dies on error
160              
161             Like C but accepts addresses as packed strings as returned by
162             Socket::unpack_sockaddr_in6.
163              
164             =cut
165              
166             sub add_str_range { ## no critic (Subroutines::RequireArgUnpacking)
167             # my ($self, $start, $end, $val) = @_;
168 0     0 1 0 shift->add_vec_range(_str2vec($_[0]), _str2vec($_[1]), $_[2]);
169             }
170              
171             =head2 lookup
172              
173             Arguments: C<$address>
174              
175             Return Value: value assoiated with this address or C
176              
177             Looks up an IPv6 address specified as a string and returns the value associated
178             with the network containing it. So far there is no way to tell which network
179             that is though.
180              
181             =cut
182              
183             sub lookup {
184 6     6 1 18 my ($self, $addr) = @_;
185              
186             # Make sure there is no network spec tacked onto $addr
187 6         11 $addr =~ s!/.*!!;
188 6         8 $self->_lookup(_parse_address($addr));
189             }
190              
191              
192             =head2 lookup_vec
193              
194             Arguments: C<$address>
195              
196             Return Value: value assoiated with this address or C
197              
198             Like C but accepts the address as a Bit::Vector object.
199              
200             =cut
201              
202 3     3 1 24 sub lookup_vec { shift->_lookup($_[0]->Clone) } ## no critic (Subroutines::RequireArgUnpacking)
203              
204             =head2 lookup_str
205              
206             Arguments: C<$address>
207              
208             Return Value: value assoiated with this address or C
209              
210             Like C but accepts the address as a packed string as returned by
211             C.
212              
213             =cut
214              
215 3     3 1 604 sub lookup_str { shift->_lookup(_str2vec($_[0])) } ## no critic (Subroutines::RequireArgUnpacking)
216              
217             =head2 to_hash
218              
219             Arguments: none
220              
221             Return Value: C<$hashref>
222              
223             Returns a hash representation of the tree with keys being CIDR-style network
224             addresses.
225              
226             =cut
227              
228             sub to_hash {
229 2     2 1 7 my ($self) = @_;
230 2         3 my %result;
231             $self->_walk(Bit::Vector->new(128), 0, sub {
232 41     41   40 my $net = _addr2print($_[0]) . '/' . $_[1];
233 41 50       56 if(defined $result{$net}) {
234 0         0 confess "internal error: network $net mapped to $result{$net} already!";
235             } else {
236 41         90 $result{$net} = $_[2];
237             }
238             }
239 2         18 );
240 2         22 \%result;
241             }
242              
243             =head2 walk
244              
245             Arguments: C<$coderef> to call for each tree entry. Callback arguments are:
246              
247             =over 1
248              
249             =item C<$address>
250              
251             The network address as a Bit::Vector object. The callback must not change this
252             object's contents, use $addr->Clone if in doubt!
253              
254             =item C<$bits>
255              
256             The current CIDR block's number of network bits
257              
258             =item C<$value>
259              
260             The value associated with this block
261              
262             =back
263              
264             Return Value: nothing useful
265              
266             =cut
267              
268 0     0 1 0 sub walk { $_[0]->_walk(Bit::Vector->new(128), 0, $_[1]) } ## no critic (Subroutines::RequireArgUnpacking)
269              
270             =head1 BUGS
271              
272             =over 1
273              
274             =item The IPv6 version hasn't seen any real-world testing and the unit tests
275             are still rather scarce, so there will probably be more bugs than listed here.
276              
277             =item I didn't need deletions yet and deleting parts of a CIDR block is a bit more
278             complicated than anything this class does so far, so it's not implemented.
279              
280             =item Storing an C value does not work and yields an error. This would be
281             relatively easy to fix at the cost of some memory so that's more a design
282             decision.
283              
284             =item A consequence of the same design is also that a /0 block can't be formed.
285             Although it doesn't make much sense, this might happen if your input is that
286             weird.
287              
288             =back
289              
290             =head1 AUTHORS, COPYRIGHTS & LICENSE
291              
292             Matthias Bethke
293              
294             Licensed unter the Artistic License 2.0
295              
296             =head1 SEE ALSO
297              
298             This module's methods are based even more loosely on L than those of L.
299              
300             =cut
301              
302             # Walk through a subtree and insert a network
303             sub _add {
304 82     82   74 my ($node, $addr, $nbits, $val) = @_;
305 82         47 my ($bit, $checksub);
306 0         0 my @node_stack;
307              
308             DESCEND:
309 82         50 while(1) {
310 6803         6805 $bit = $addr->shift_left(0);
311              
312 6803 50       7755 if(__PACKAGE__ ne ref $node) {
313 0 0       0 return 1 if $val eq $node; # Compatible entry (tried to add a subnet of one already in the tree)
314 0         0 croak "incompatible entry, found `$node' trying to add `$val'";
315             }
316 6803 100       6847 last DESCEND unless --$nbits;
317 6721 100       5762 if(defined $node->[$bit]) {
318 5884         3426 $checksub = 1;
319             } else {
320 837   50     1929 $node->[$bit] ||= bless([], __PACKAGE__);
321 837         610 $checksub = 0;
322             }
323 6721         4553 push @node_stack, \$node->[$bit];
324 6721         4236 $node = $node->[$bit];
325             }
326            
327             $checksub
328 82 50 66     169 and defined $node->[$bit]
      33        
329             and __PACKAGE__ eq ref $node->[$bit]
330             and _add_check_subtree($node->[$bit], $val);
331              
332 82         66 $node->[$bit] = $val;
333              
334             # Take care of potential mergers into the previous node (if $node[0] == $node[1])
335             # TODO recursively check upwards
336             not @node_stack
337 82 0 33     118 and defined $node->[$bit ^ 1]
      33        
338             and $node->[$bit ^ 1] eq $val
339             and croak 'merging two /1 blocks is not supported yet';
340 82         66 while(1) {
341 82         54 $node = pop @node_stack;
342 82 50       95 last MERGECHECK unless defined $node;
343 82 50 100     619 last unless(defined $$node->[0] and defined $$node->[1] and $$node->[0] eq $$node->[1]);
      66        
344 0         0 $$node = $val;
345             }
346             }
347              
348             # Check an existing subtree for incompatible values. Returns false and sets the
349             # package-global error string if there was a problem.
350             sub _add_check_subtree {
351 0     0   0 my ($root, $val) = @_;
352              
353             eval {
354             $root->_walk(Bit::Vector->new(128), 0, sub {
355 0     0   0 my $oldval = $_[2];
356 0 0       0 $val == $oldval or die $oldval; ## no critic (ErrorHandling::RequireCarping)
357             }
358 0         0 );
359 0         0 1;
360 0 0       0 } or do {
361 0 0       0 $@ and croak "incompatible entry, found `$@' trying to add `$val'";
362             };
363             }
364              
365             sub _lookup {
366 12     12   14 my ($node, $addr) = @_;
367 12         6 my $bit;
368             #printf "_lookup($node, %s)\n", $addr->to_Hex;
369              
370 12         9 while(1) {
371 492         462 $bit = $addr->shift_left(0);
372 492 100       552 defined $node->[$bit] or return;
373 488 100       592 __PACKAGE__ ne ref $node->[$bit] and return $node->[$bit];
374 480         285 $node = $node->[$bit];
375             }
376             }
377              
378             # Convert a packed IPv6 address to a Bit::Vector object
379             sub _str2vec { ## no critic (Subroutines::RequireArgUnpacking)
380 3     3   7 my $b = Bit::Vector->new(128);
381 3         9 $b->Chunk_List_Store(32, reverse unpack 'N4', $_[0]);
382 3         9 return $b;
383             }
384              
385             # Parse an IPv6 address and return a Bit::Vector object
386             sub _parse_address { ## no critic (Subroutines::RequireArgUnpacking)
387 20     20   62 my $b = Bit::Vector->new(128);
388 20         124 $b->Chunk_List_Store(32, reverse unpack 'N4', inet_pton(AF_INET6, shift));
389 20         41 return $b;
390             }
391              
392             # Convert a Bit::Vector object holding an IPv6 address to a printable string
393 41     41   181 sub _addr2print { inet_ntop(AF_INET6, pack('N4', reverse $_[0]->Chunk_List_Read(32))) } ## no critic (Subroutines::RequireArgUnpacking)
394              
395             # Walk the tree in depth-first LTR order
396             sub _walk {
397 2     2   4 my ($node, $addr, $bits, $cb) = @_;
398 2         2 my ($l, $r, $rightflag);
399 2         5 my @node_stack = ($node, 0, $bits);
400             #print "================== WALK ==================: ", join(':',caller),"\n";
401 2         6 while(@node_stack) {
402 528         426 ($node, $rightflag, $bits) = splice @node_stack, -3; # pop 3 elems
403             #print "LOOP: stack size ".@node_stack."\n";
404              
405 528 100       594 $addr->Bit_On(128-$bits) if $rightflag;
406              
407 528 100       478 if(__PACKAGE__ eq ref $node) {
408 513         379 ($l, $r) = @$node;
409             #printf "Popped [%s, %s]:%s/%d\n",
410             # ($l//'') =~ /^Net::CIDR::Lookup::IPv6=/ ? '' : $l//'',
411             # ($r//'') =~ /^Net::CIDR::Lookup::IPv6=/ ? '' : $r//'',
412             # _addr2print($addr), $bits;
413 513         298 ++$bits;
414              
415             # Check left side
416 513         530 $addr->Bit_Off(128 - $bits);
417 513 100       473 if(__PACKAGE__ eq ref $l) {
418             #defined $r and print "L: pushing right node=$r, bits=$bits\n";
419 452 100       467 defined $r and push @node_stack, ($r, 1, $bits);
420             #defined $r and print "L: pushing left node=$l, bits=$bits\n";
421 452         352 push @node_stack, ($l, 0, $bits);
422             #printf "L: addr=%032b (%s)\n", $addr, _addr2print($addr);
423 452         515 next; # Short-circuit back to loop w/o checking $r!
424             } else {
425             #defined $l and printf "L: CALLBACK (%s/%d) => %s\n", _addr2print($addr), $bits, $l;
426 61 100       85 defined $l and $cb->($addr, $bits, $l);
427             }
428             } else {
429             # There was a right-side leaf node on the stack that will end up in
430             # the "else" branch below
431             #print "Found leftover right leaf $node\n";
432 15         11 $r = $node;
433             }
434              
435             # Check right side
436 76         84 $addr->Bit_On(128 - $bits);
437 76 100       77 if(__PACKAGE__ eq ref $r) {
438             #print "R: pushing right node=$r, bits=$bits\n";
439 55         77 push @node_stack, ($r, 1, $bits);
440             #printf "R: addr=%032b (%s)\n", $addr, _addr2print($addr);
441             } else {
442             #defined $r and printf "R: CALLBACK (%s/%d) => %s\n", _addr2print($addr), $bits, $r;
443 21 100       36 defined $r and $cb->($addr, $bits, $r);
444             }
445             }
446             }
447              
448             # Split a chunk into a minimal number of CIDR blocks.
449             sub _do_chunk {
450 154     154   121 my ($chunks, $start, $end, $ix1, $ix2) = @_;
451 154         307 my ($xor, $prefix, $tmp_prefix) = Bit::Vector->new(128, 3);
452              
453             # Find common prefix. After that, the bit indicated by $ix1 is 0 for $start
454             # and 1 for $end. A split a this point guarantees the longest suffix.
455 154         213 $xor->Xor($start, $end);
456             #print STDERR "--------------------------------------------------------------------------------\n";
457             #print STDERR "Start : ",$start->to_Hex,"\n";
458             #print STDERR "End : ",$end->to_Hex,"\n";
459             #print STDERR "XOR : ",$xor->to_Hex,"\n";
460 154   100     2233 --$ix1 until(-1 == $ix1 or $xor->bit_test($ix1));
461 154         196 $prefix->Interval_Fill($ix1+1, 127);
462 154         194 $prefix->And($prefix, $start);
463              
464 154   100     8710 $ix2++ while($ix2 <= $ix1
      100        
465             and not $start->bit_test($ix2)
466             and $end->bit_test($ix2));
467              
468             #print STDERR "After loop: ix1=$ix1, ix2=$ix2, ";
469             #print STDERR "Prefix: ",$prefix->to_Hex,"\n";
470              
471 154 100       157 if ($ix2 <= $ix1) {
472             #print STDERR "Recursing with $ix1 lowbits=1 in end\n";
473 74         92 $tmp_prefix->Copy($prefix);
474 74         78 $tmp_prefix->Interval_Fill(0, $ix1-1);
475 74         121 _do_chunk($chunks, $start, $tmp_prefix, $ix1, $ix2);
476              
477             #print STDERR "Recursing with $ix1 lowbits=0 in start\n";
478 74         102 $tmp_prefix->Copy($prefix);
479 74         73 $tmp_prefix->Bit_On($ix1);
480 74         69 _do_chunk($chunks, $tmp_prefix, $end, $ix1, $ix2);
481             } else {
482 80         328 push @$chunks, [ $prefix, 127-$ix1 ];
483             #printf STDERR "Result: %s/%d\n", $chunks->[-1][0]->to_Hex, $chunks->[-1][1];
484             }
485             }
486              
487             1;