File Coverage

blib/lib/Net/CIDR/Lookup.pm
Criterion Covered Total %
statement 126 130 96.9
branch 46 62 74.1
condition 29 41 70.7
subroutine 26 27 96.3
pod 10 10 100.0
total 237 270 87.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Net::CIDR::Lookup
4              
5             =head1 DESCRIPTION
6              
7             This class implements a lookup table indexed by IPv4 networks or hosts.
8              
9             =over 1
10              
11             =item Addresses are accepted in numeric form (integer with separate netbits argument),
12             as strings in CIDR notation or as IP address ranges
13              
14             =item Overlapping or adjacent networks are automatically coalesced if their
15             associated values are equal.
16              
17             =item The table is implemented as a binary tree so lookup and insertion take O(log n)
18             time.
19              
20             =back
21              
22             Since V0.5, errors are signalled by an exception so method calls should generally by wrapped in an C.
23              
24             =head1 SYNOPSIS
25              
26             use Net::CIDR::Lookup;
27              
28             $cidr = Net::CIDR::Lookup->new;
29             $cidr->add("192.168.42.0/24",1); # Add first network, value 1
30             $cidr->add_num(167772448,27,2); # 10.0.1.32/27 => 2
31             $cidr->add("192.168.43.0/24",1); # Automatic coalescing to a /23
32             $cidr->add("192.168.41.0/24",2); # Stays separate due to different value
33             $cidr->add("192.168.42.128/25",2); # Error: overlaps with different value
34              
35             $val = $cidr->lookup("192.168.41.123"); # => 2
36              
37             $h = $cidr->to_hash; # Convert tree to a hash
38             print "$k => $v\n" while(($k,$v) = each %$h);
39              
40             # Output (order may vary):
41             # 192.168.42.0/23 => 1
42             # 10.0.1.32/27 => 2
43             # 192.168.41.0/24 => 2
44              
45             $cidr->walk(sub {
46             my ($addr, $bits, $val) = @_;
47             print join('.', unpack 'C*', pack 'N', $addr), "/$bits => $val\n"
48             }
49             );
50              
51             # Output (fixed order):
52             # 10.0.1.32/27 => 2
53             # 192.168.41.0/24 => 2
54             # 192.168.42.0/23 => 1
55              
56             $cidr->clear; # Remove all entries
57             $cidr->add_range('1.2.3.11 - 1.2.4.234', 42); # Add a range of addresses,
58             # automatically split into CIDR blocks
59             $h = $cidr->to_hash;
60             print "$k => $v\n" while(($k,$v) = each %$h);
61              
62             # Output (order may vary):
63             # 1.2.4.128/26 => 42
64             # 1.2.3.32/27 => 42
65             # 1.2.3.64/26 => 42
66             # 1.2.4.234/32 => 42
67             # 1.2.4.0/25 => 42
68             # 1.2.3.12/30 => 42
69             # 1.2.3.128/25 => 42
70             # 1.2.3.16/28 => 42
71             # 1.2.4.224/29 => 42
72             # 1.2.4.232/31 => 42
73             # 1.2.3.11/32 => 42
74             # 1.2.4.192/27 => 42
75              
76             =head1 METHODS
77              
78             =cut
79              
80             package Net::CIDR::Lookup;
81 3     3   40380 use strict;
  3         6  
  3         103  
82 3     3   87 use 5.008008;
  3         9  
83 3     3   1664 use integer;
  3         36  
  3         16  
84 3     3   104 use Carp;
  3         6  
  3         268  
85 3     3   1045 use Socket qw/ inet_ntop inet_pton AF_INET /;
  3         7954  
  3         428  
86              
87 3     3   1739 use version 0.77; our $VERSION = version->declare('v1.0.0');
  3         6077  
  3         41  
88              
89             BEGIN {
90             # IPv4 address from dotted-quad to integer
91             # Choose manual implementation on Windows where inet_pton() is not available
92 3 100   3   951 if('MSWin32' eq $^O) {
93             *_dq2int = sub { ## no critic (Subroutines::RequireArgUnpacking)
94 1     1   10 my @oct = split /\./, $_[0];
95 1 50       6 4 == @oct or croak "address must be in dotted-quad form, is `$_[0]'";
96 1         4 my $ip = 0;
97 1         4 foreach(@oct) {
98 4 50 33     30 $_ <= 255 and $_ >= 0
99             or croak "invalid component `$_' in address `$_[0]'";
100 4         10 $ip = $ip<<8 | $_;
101             }
102 1         7 return $ip;
103 1         1332 };
104             } else {
105 2     47   3781 *_dq2int = sub { unpack 'N', inet_pton(AF_INET, shift) };
  47         241  
106             }
107             }
108              
109             =head2 new
110              
111             Arguments: none
112              
113             Return Value: new object
114              
115             =cut
116              
117 25     25 1 26836 sub new { bless [], shift }
118              
119             =head2 add
120              
121             Arguments: C<$cidr>, C<$value>
122              
123             Return Value: none
124              
125             Adds VALUE to the tree under the key CIDR. CIDR must be a string containing an
126             IPv4 address followed by a slash and a number of network bits. Bits to the
127             right of this mask will be ignored.
128              
129             =cut
130              
131             sub add {
132 31     31 1 1986 my ($self, $cidr, $val) = @_;
133              
134 31 50       69 defined $val or croak "can't store an undef";
135              
136 31         187 my ($net, $bits) = $cidr =~ m{ ^ ([.[:digit:]]+) / (\d+) $ }ox;
137 31 50 33     147 defined $net and defined $bits or croak 'CIDR syntax error: use
/';
138 31 50       55 my $intnet = _dq2int($net) or return;
139 31         75 $self->_add($intnet,$bits,$val);
140             }
141              
142             =head2 add_range
143              
144             Arguments: C<$range>, C<$value>
145              
146             Return Value: none
147              
148             Adds VALUE to the tree for each address included in RANGE which must be a
149             hyphenated range of IP addresses in dotted-quad format (e.g.
150             "192.168.0.150-192.168.10.1") and with the first address being numerically
151             smaller the second. This range will be split up into as many CIDR blocks as
152             necessary (algorithm adapted from a script by Dr. Liviu Daia).
153              
154             =cut
155              
156             sub add_range {
157 4     4 1 311 my ($self, $range, $val) = @_;
158              
159 4 50       19 defined $val or croak "can't store an undef";
160              
161 4         29 my ($start, $end, $crud) = split /\s*-\s*/, $range;
162 4 50 33     20 croak 'must have exactly one hyphen in range'
163             if(defined $crud or not defined $end);
164              
165 4         11 $self->add_num_range(_dq2int($start), _dq2int($end), $val);
166             }
167              
168             =head2 add_num
169              
170             Arguments: C<$address>, C<$bits>, C<$value>
171              
172             Return Value: none
173              
174             Like C but accepts address and bits as separate integer arguments
175             instead of a string.
176              
177             =cut
178              
179             sub add_num { ## no critic (Subroutines::RequireArgUnpacking)
180             # my ($self,$ip,$bits,$val) = @_;
181             # Just call the recursive adder for now but allow for changes in object
182             # representation ($self != $n)
183 21 50   21 1 38 defined $_[3] or croak "can't store an undef";
184 21         28 _add(@_);
185             }
186              
187             =head2 add_num_range
188              
189             Arguments: C<$start>, C<$end>, C<$value>
190              
191             Return Value: none
192              
193             Like C but accepts addresses as separate integer arguments instead
194             of a range string.
195              
196             =cut
197              
198             sub add_num_range {
199 4     4 1 5 my ($self, $start, $end, $val) = @_;
200 4         5 my @chunks;
201              
202 4 50       8 $start > $end
203             and croak sprintf "start > end in range %s--%s", _int2dq($start), _int2dq($end);
204              
205 4         12 _do_chunk(\@chunks, $start, $end, 31, 0);
206 4         16 $self->add_num(@$_, $val) for(@chunks);
207             }
208              
209             =head2 lookup
210              
211             Arguments: C<$address>
212              
213             Return Value: value assoiated with this address or C
214              
215             Looks up an address and returns the value associated with the network
216             containing it. So far there is no way to tell which network that is though.
217              
218             =cut
219              
220             sub lookup {
221 9     9 1 19 my ($self, $addr) = @_;
222              
223             # Make sure there is no network spec tacked onto $addr
224 9         15 $addr =~ s!/.*!!;
225 9         16 $self->_lookup(_dq2int($addr));
226             }
227              
228              
229             =head2 lookup_num
230              
231             Arguments: C<$address>
232              
233             Return Value: value assoiated with this address or C
234              
235             Like C but accepts the address in integer form.
236              
237             =cut
238              
239 2     2 1 33 sub lookup_num { shift->_lookup($_[0]) } ## no critic (Subroutines::RequireArgUnpacking)
240              
241             =head2 to_hash
242              
243             Arguments: none
244              
245             Return Value: C<$hashref>
246              
247             Returns a hash representation of the tree with keys being CIDR-style network
248             addresses.
249              
250             =cut
251              
252             sub to_hash {
253 11     11 1 34 my ($self) = @_;
254 11         11 my %result;
255             $self->_walk(0, 0, sub {
256 30     30   45 my $net = _int2dq($_[0]) . '/' . $_[1];
257 30 50       72 if(defined $result{$net}) {
258 0         0 confess("internal error: network $net mapped to $result{$net} already!\n");
259             } else {
260 30         83 $result{$net} = $_[2];
261             }
262             }
263 11         49 );
264 11         74 return \%result;
265             }
266              
267             =head2 walk
268              
269             Arguments: C<$coderef> to call for each tree entry. Callback arguments are:
270              
271             =over 1
272              
273             =item C<$address>
274              
275             The network address in integer form
276              
277             =item C<$bits>
278              
279             The current CIDR block's number of network bits
280              
281             =item C<$value>
282              
283             The value associated with this block
284              
285             =back
286              
287             Return Value: none
288              
289             =cut
290              
291 0     0 1 0 sub walk { $_[0]->_walk(0, 0, $_[1]) } ## no critic (Subroutines::RequireArgUnpacking)
292              
293              
294             =head2 clear
295              
296             Arguments: none
297              
298             Return Value: none
299              
300             Remove all entries from the tree.
301              
302             =cut
303              
304             sub clear {
305 1     1 1 5 my $self = shift;
306 1         5 undef @$self;
307             }
308              
309             =head1 BUGS
310              
311             =over 1
312              
313             =item
314              
315             I didn't need deletions yet and deleting parts of a CIDR block is a bit more
316             complicated than anything this class does so far, so it's not implemented.
317              
318             =item
319              
320             Storing an C value does not work and yields an error. This would be
321             relatively easy to fix at the cost of some memory so that's more a design
322             decision.
323              
324             =back
325              
326             =head1 AUTHORS, COPYRIGHTS & LICENSE
327              
328             Matthias Bethke
329             while working for 1&1 Internet AG
330              
331             Licensed unter the Artistic License 2.0
332              
333             =head1 SEE ALSO
334              
335             This module's methods are based loosely on those of C
336              
337             =cut
338              
339             # Walk through a subtree and insert a network
340             sub _add {
341 52     52   63 my ($node, $addr, $nbits, $val) = @_;
342 52         50 my ($bit, $checksub);
343 0         0 my @node_stack;
344              
345             DESCEND:
346 52         46 while(1) {
347 1267 100       1464 $bit = $addr & 0x80000000 ? 1 : 0;
348 1267         857 $addr <<= 1;
349              
350 1267 100       1860 if(__PACKAGE__ ne ref $node) {
351 4 100       24 return 1 if($val eq $node); # Compatible entry (tried to add a subnet of one already in the tree)
352 1         28 croak "incompatible entry, found `$node' trying to add `$val'";
353             }
354 1263 100       1656 last DESCEND unless --$nbits;
355 1215 100       1327 if(defined $node->[$bit]) {
356 661         513 $checksub = 1;
357             } else {
358 554   50     1468 $node->[$bit] ||= bless([], __PACKAGE__);
359 554         469 $checksub = 0;
360             }
361 1215         1032 push @node_stack, \$node->[$bit];
362 1215         951 $node = $node->[$bit];
363             }
364            
365             $checksub
366 48 100 100     154 and defined $node->[$bit]
      100        
367             and __PACKAGE__ eq ref $node->[$bit]
368             and _add_check_subtree($node->[$bit], $val);
369              
370 48         52 $node->[$bit] = $val;
371              
372             # Take care of potential mergers into the previous node (if $node[0] == $node[1])
373             not @node_stack
374 48 0 33     98 and defined $node->[$bit ^ 1]
      33        
375             and $node->[$bit ^ 1] eq $val
376             and croak 'merging two /1 blocks is not supported yet';
377 48         49 while(1) {
378 52         52 $node = pop @node_stack;
379             last unless(
380 52 100 66     441 defined $node
      100        
      100        
381             and defined $$node->[0]
382             and defined $$node->[1]
383             and $$node->[0] eq $$node->[1]
384             );
385 4         7 $$node = $val;
386             }
387             }
388              
389             # Check an existing subtree for incompatible values. Returns false and sets the
390             # package-global error string if there was a problem.
391             sub _add_check_subtree {
392 2     2   3 my ($root, $val) = @_;
393              
394             eval {
395             $root->_walk(0, 0, sub {
396 2     2   2 my $oldval = $_[2];
397 2 50       10 $val == $oldval or die $oldval; ## no critic (ErrorHandling::RequireCarping)
398             }
399 2         9 );
400 2         10 1;
401 2 50       4 } or do {
402 0 0       0 $@ and croak "incompatible entry, found `$@' trying to add `$val'";
403             };
404 2         3 return 1;
405             }
406              
407             sub _lookup {
408 11     11   14 my ($node, $addr) = @_;
409 11         8 my $bit;
410              
411 11         10 while(1) {
412 202         146 $bit = ($addr & 0x80000000) >> 31;
413 202 100       277 defined $node->[$bit] or return;
414 199 100       308 __PACKAGE__ ne ref $node->[$bit] and return $node->[$bit];
415 191         137 $node = $node->[$bit];
416 191         135 $addr <<= 1;
417             }
418             }
419              
420             # IPv4 address from integer to dotted-quad
421 30     30   159 sub _int2dq { inet_ntop(AF_INET, pack 'N', shift) }
422              
423             # Convert a CIDR block ($addr, $bits) into a range of addresses ($lo, $hi)
424             # sub _cidr2rng { ( $_[0], $_[0] | ((1 << $_[1]) - 1) ) }
425              
426             # Walk the tree in depth-first LTR order
427             sub _walk {
428 13     13   16 my ($node, $addr, $bits, $cb) = @_;
429 13         9 my ($l, $r);
430 13         25 my @node_stack = ($node, $addr, $bits);
431             #print "================== WALK ==================: ", join(':',caller),"\n";
432 13         29 while(@node_stack) {
433 343         374 ($node, $addr, $bits) = splice @node_stack, -3; # pop 3 elems
434             #print "LOOP: stack size ".(@node_stack/3)."\n";
435 343 100       432 if(__PACKAGE__ eq ref $node) {
436 328         318 ($l, $r) = @$node;
437             #printf "Popped [%s, %s]:%s/%d\n",
438             # ($l//'') =~ /^Net::CIDR::Lookup=/ ? '' : $l//'',
439             # ($r//'') =~ /^Net::CIDR::Lookup=/ ? '' : $r//'',
440             # _int2dq($addr), $bits;
441 328         254 ++$bits;
442              
443             # Check left side
444             #$addr &= ~(1 << 31-$bits);
445 328 100       351 if(__PACKAGE__ eq ref $l) {
446             #defined $r and print "L: pushing right node=$r, bits=$bits\n";
447 236 100       316 defined $r and push @node_stack, ($r, $addr | 1 << 32-$bits, $bits);
448             #print "L: pushing left node=$l, bits=$bits\n";
449 236         244 push @node_stack, ($l, $addr, $bits);
450             #printf "L: addr=%032b (%s)\n", $addr, _int2dq($addr);
451 236         327 next; # Short-circuit back to loop w/o checking $r!
452             } else {
453             #defined $l and printf "L: CALLBACK (%s/%d) => %s\n", _int2dq($addr), $bits, $l;
454 92 100       140 defined $l and $cb->($addr, $bits, $l);
455             }
456             } else {
457             # There was a right-side leaf node on the stack that will end up in
458             # the "else" branch below
459             #print "Found leftover right leaf $node\n";
460 15         12 $r = $node;
461             }
462              
463             # Check right side
464 107         91 $addr |= 1 << 32-$bits;
465 107 100       127 if(__PACKAGE__ eq ref $r) {
466             #print "R: pushing right node=$r, bits=$bits\n";
467 75         135 push @node_stack, ($r, $addr, $bits);
468             #printf "R: addr=%032b (%s)\n", $addr, _int2dq($addr);
469             } else {
470             #defined $r and printf "R: CALLBACK (%s/%d) => %s\n", _int2dq($addr), $bits, $r;
471 32 100       83 defined $r and $cb->($addr, $bits, $r);
472             }
473             }
474             }
475              
476             # Split a chunk into a minimal number of CIDR blocks.
477             sub _do_chunk {
478 38     38   38 my ($chunks, $start, $end, $ix1, $ix2) = @_;
479 38         24 my ($prefix, $xor);
480              
481             # Find common prefix. After that, the bit indicated by $ix1 is 0 for $start
482             # and 1 for $end. A split a this point guarantees the longest suffix.
483 38         34 $xor = $start ^ $end;
484 38   100     394 --$ix1 until($xor & 1 << $ix1 or -1 == $ix1);
485 38         39 $prefix = $start & ~((1 << ($ix1+1)) - 1);
486              
487 38   100     600 $ix2++ while($ix2 <= $ix1
      100        
488             and not ($start & 1 << $ix2)
489             and ($end & 1 << $ix2));
490              
491             # Split if $fbits and $lbits disagree on the length of the chunk.
492 38 100       49 if ($ix2 <= $ix1) {
493 17         57 _do_chunk($chunks, $start, $prefix | ((1<<$ix1) - 1), $ix1, $ix2);
494 17         31 _do_chunk($chunks, $prefix | (1<<$ix1), $end, $ix1, $ix2);
495             } else {
496 21         43 push @$chunks, [ $prefix, 31-$ix1 ];
497             }
498             }
499              
500             =head1 SEE ALSO
501              
502             L, L
503              
504             =head1 AUTHOR
505              
506             Matthias Bethke, Ematthias@towiski.deE
507              
508             =head1 COPYRIGHT AND LICENSE
509              
510             Copyright (C) 2008-2016 by Matthias Bethke.
511              
512             This library is free software; you can redistribute it and/or modify
513             it under the same terms as Perl itself, either Perl version 5.20.2 or,
514             at your option, any later version of Perl 5 you may have available.
515              
516             =cut
517              
518             1;