File Coverage

blib/lib/Net/CIDR/Lookup/IPv6.pm
Criterion Covered Total %
statement 119 134 88.8
branch 35 56 62.5
condition 22 35 62.8
subroutine 23 28 82.1
pod 11 11 100.0
total 210 264 79.5


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