File Coverage

blib/lib/Net/CIDR/Lookup/IPv6.pm
Criterion Covered Total %
statement 115 130 88.4
branch 34 54 62.9
condition 23 37 62.1
subroutine 22 27 81.4
pod 11 11 100.0
total 205 259 79.1


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   3744 use strict;
  2         3  
  2         45  
43 2     2   6 use warnings;
  2         2  
  2         37  
44 2     2   10 use Carp;
  2         2  
  2         111  
45             $Carp::Verbose=1;
46 2     2   6 use Socket qw/ inet_ntop inet_pton AF_INET6 /;
  2         2  
  2         85  
47 2     2   393 use Bit::Vector;
  2         696  
  2         64  
48 2     2   401 use parent 'Net::CIDR::Lookup';
  2         211  
  2         7  
49              
50             our $VERSION = '0.51';
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 100 my ($self, $cidr, $val) = @_;
66              
67 2 50       5 defined $val or croak "can't store an undef";
68 2         8 my ($net, $bits) = $cidr =~ m{ ^ (.+) / (\d+) $ }ox;
69 2 50 33     20 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 358 my ($self, $range, $val) = @_;
89              
90 6 50       11 defined $val or croak "can't store an undef";
91 6         35 my ($start, $end, $crud) = split /\s*-\s*/, $range;
92 6 50 33     25 croak 'must have exactly one hyphen in range'
93             if(defined $crud or not defined $end);
94 6         10 $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 114 defined $_[3] or croak "can't store an undef";
113 80         80 _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 7 my ($self, $start, $end, $val) = @_;
146 6         5 my @chunks;
147              
148 6 50       16 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         17 $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 14 my ($self, $addr) = @_;
185              
186             # Make sure there is no network spec tacked onto $addr
187 6         9 $addr =~ s!/.*!!;
188 6         10 $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 18 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 1042 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         1 my %result;
231             $self->_walk(Bit::Vector->new(128), 0, sub {
232 41     41   39 my $net = _addr2print($_[0]) . '/' . $_[1];
233 41 50       70 if(defined $result{$net}) {
234 0         0 confess "internal error: network $net mapped to $result{$net} already!";
235             } else {
236 41         95 $result{$net} = $_[2];
237             }
238             }
239 2         14 );
240 2         15 \%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   61 my ($node, $addr, $nbits, $val) = @_;
305 82         49 my ($bit, $checksub);
306 0         0 my @node_stack;
307              
308             DESCEND:
309 82         40 while(1) {
310 6803         6447 $bit = $addr->shift_left(0);
311              
312 6803 50       7309 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       6665 last DESCEND unless --$nbits;
317 6721 100       5671 if(defined $node->[$bit]) {
318 5884         3702 $checksub = 1;
319             } else {
320 837   50     1771 $node->[$bit] ||= bless([], __PACKAGE__);
321 837         570 $checksub = 0;
322             }
323 6721         4337 push @node_stack, \$node->[$bit];
324 6721         4260 $node = $node->[$bit];
325             }
326            
327             $checksub
328 82 50 66     155 and defined $node->[$bit]
      33        
329             and __PACKAGE__ eq ref $node->[$bit]
330             and _add_check_subtree($node->[$bit], $val);
331              
332 82         62 $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     116 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         54 while(1) {
341 82   50     99 $node = pop @node_stack // last MERGECHECK;
342 82 50 100     536 last unless(defined $$node->[0] and defined $$node->[1] and $$node->[0] eq $$node->[1]);
      66        
343 0         0 $$node = $val;
344             }
345             }
346              
347             # Check an existing subtree for incompatible values. Returns false and sets the
348             # package-global error string if there was a problem.
349             sub _add_check_subtree {
350 0     0   0 my ($root, $val) = @_;
351              
352             eval {
353             $root->_walk(Bit::Vector->new(128), 0, sub {
354 0     0   0 my $oldval = $_[2];
355 0 0       0 $val == $oldval or die $oldval; ## no critic (ErrorHandling::RequireCarping)
356             }
357 0         0 );
358 0         0 1;
359 0 0       0 } or do {
360 0 0       0 $@ and croak "incompatible entry, found `$@' trying to add `$val'";
361             };
362             }
363              
364             sub _lookup {
365 12     12   9 my ($node, $addr) = @_;
366 12         9 my $bit;
367             #printf "_lookup($node, %s)\n", $addr->to_Hex;
368              
369 12         12 while(1) {
370 492         464 $bit = $addr->shift_left(0);
371 492 100       557 defined $node->[$bit] or return;
372 488 100       586 __PACKAGE__ ne ref $node->[$bit] and return $node->[$bit];
373 480         308 $node = $node->[$bit];
374             }
375             }
376              
377             # Convert a packed IPv6 address to a Bit::Vector object
378             sub _str2vec { ## no critic (Subroutines::RequireArgUnpacking)
379 3     3   7 my $b = Bit::Vector->new(128);
380 3         8 $b->Chunk_List_Store(32, reverse unpack 'N4', $_[0]);
381 3         7 return $b;
382             }
383              
384             # Parse an IPv6 address and return a Bit::Vector object
385             sub _parse_address { ## no critic (Subroutines::RequireArgUnpacking)
386 20     20   47 my $b = Bit::Vector->new(128);
387 20         103 $b->Chunk_List_Store(32, reverse unpack 'N4', inet_pton(AF_INET6, shift));
388 20         39 return $b;
389             }
390              
391             # Convert a Bit::Vector object holding an IPv6 address to a printable string
392 41     41   203 sub _addr2print { inet_ntop(AF_INET6, pack('N4', reverse $_[0]->Chunk_List_Read(32))) } ## no critic (Subroutines::RequireArgUnpacking)
393              
394             # Walk the tree in depth-first LTR order
395             sub _walk {
396 2     2   3 my ($node, $addr, $bits, $cb) = @_;
397 2         3 my ($l, $r, $rightflag);
398 2         5 my @node_stack = ($node, 0, $bits);
399             #print "================== WALK ==================: ", join(':',caller),"\n";
400 2         4 while(@node_stack) {
401 528         414 ($node, $rightflag, $bits) = splice @node_stack, -3; # pop 3 elems
402             #print "LOOP: stack size ".@node_stack."\n";
403              
404 528 100       619 $addr->Bit_On(128-$bits) if $rightflag;
405              
406 528 100       495 if(__PACKAGE__ eq ref $node) {
407 513         395 ($l, $r) = @$node;
408             #printf "Popped [%s, %s]:%s/%d\n",
409             # ($l//'') =~ /^Net::CIDR::Lookup::IPv6=/ ? '' : $l//'',
410             # ($r//'') =~ /^Net::CIDR::Lookup::IPv6=/ ? '' : $r//'',
411             # _addr2print($addr), $bits;
412 513         290 ++$bits;
413              
414             # Check left side
415 513         530 $addr->Bit_Off(128 - $bits);
416 513 100       516 if(__PACKAGE__ eq ref $l) {
417             #defined $r and print "L: pushing right node=$r, bits=$bits\n";
418 452 100       491 defined $r and push @node_stack, ($r, 1, $bits);
419             #defined $r and print "L: pushing left node=$l, bits=$bits\n";
420 452         333 push @node_stack, ($l, 0, $bits);
421             #printf "L: addr=%032b (%s)\n", $addr, _addr2print($addr);
422 452         504 next; # Short-circuit back to loop w/o checking $r!
423             } else {
424             #defined $l and printf "L: CALLBACK (%s/%d) => %s\n", _addr2print($addr), $bits, $l;
425 61 100       89 defined $l and $cb->($addr, $bits, $l);
426             }
427             } else {
428             # There was a right-side leaf node on the stack that will end up in
429             # the "else" branch below
430             #print "Found leftover right leaf $node\n";
431 15         10 $r = $node;
432             }
433              
434             # Check right side
435 76         87 $addr->Bit_On(128 - $bits);
436 76 100       80 if(__PACKAGE__ eq ref $r) {
437             #print "R: pushing right node=$r, bits=$bits\n";
438 55         87 push @node_stack, ($r, 1, $bits);
439             #printf "R: addr=%032b (%s)\n", $addr, _addr2print($addr);
440             } else {
441             #defined $r and printf "R: CALLBACK (%s/%d) => %s\n", _addr2print($addr), $bits, $r;
442 21 100       38 defined $r and $cb->($addr, $bits, $r);
443             }
444             }
445             }
446              
447             # Split a chunk into a minimal number of CIDR blocks.
448             sub _do_chunk {
449 154     154   129 my ($chunks, $start, $end, $ix1, $ix2) = @_;
450 154         303 my ($xor, $prefix, $tmp_prefix) = Bit::Vector->new(128, 3);
451              
452             # Find common prefix. After that, the bit indicated by $ix1 is 0 for $start
453             # and 1 for $end. A split a this point guarantees the longest suffix.
454 154         206 $xor->Xor($start, $end);
455             #print STDERR "--------------------------------------------------------------------------------\n";
456             #print STDERR "Start : ",$start->to_Hex,"\n";
457             #print STDERR "End : ",$end->to_Hex,"\n";
458             #print STDERR "XOR : ",$xor->to_Hex,"\n";
459 154   100     2250 --$ix1 until(-1 == $ix1 or $xor->bit_test($ix1));
460 154         196 $prefix->Interval_Fill($ix1+1, 127);
461 154         192 $prefix->And($prefix, $start);
462              
463 154   100     8404 $ix2++ while($ix2 <= $ix1
      100        
464             and not $start->bit_test($ix2)
465             and $end->bit_test($ix2));
466              
467             #print STDERR "After loop: ix1=$ix1, ix2=$ix2, ";
468             #print STDERR "Prefix: ",$prefix->to_Hex,"\n";
469              
470 154 100       159 if ($ix2 <= $ix1) {
471             #print STDERR "Recursing with $ix1 lowbits=1 in end\n";
472 74         91 $tmp_prefix->Copy($prefix);
473 74         95 $tmp_prefix->Interval_Fill(0, $ix1-1);
474 74         110 _do_chunk($chunks, $start, $tmp_prefix, $ix1, $ix2);
475              
476             #print STDERR "Recursing with $ix1 lowbits=0 in start\n";
477 74         87 $tmp_prefix->Copy($prefix);
478 74         77 $tmp_prefix->Bit_On($ix1);
479 74         67 _do_chunk($chunks, $tmp_prefix, $end, $ix1, $ix2);
480             } else {
481 80         282 push @$chunks, [ $prefix, 127-$ix1 ];
482             #printf STDERR "Result: %s/%d\n", $chunks->[-1][0]->to_Hex, $chunks->[-1][1];
483             }
484             }
485              
486             1;