File Coverage

blib/lib/Net/IP/AddrRanges.pm
Criterion Covered Total %
statement 109 120 90.8
branch 53 68 77.9
condition 12 15 80.0
subroutine 17 20 85.0
pod 4 5 80.0
total 195 228 85.5


line stmt bran cond sub pod time code
1             package Net::IP::AddrRanges;
2              
3 4     4   4937 use strict;
  4         8  
  4         226  
4 4     4   25 use warnings;
  4         8  
  4         9305  
5             our $VERSION = '0.01';
6              
7             my $BIN_THRESHOLD = 20;
8              
9             my @masks_ip4 = map pack('B*', '1' x ($_ + 96) . '0' x (32 - $_)), 0 .. 32;
10             my %masks_ip4 = map { join('.', unpack('CCCC' ,substr($_,12))) , $_ } @masks_ip4;
11             my @masks_ip6 = map pack('B*', '1' x $_ . '0' x (128 - $_)), 0 .. 128;
12              
13             # rule example:
14             #[
15             # # 0000 - 0003 are out
16             # '0004', # 0004 - 0018 are in
17             # '0019', # 0019 - f010 are out
18             # 'f011', # f011 - ffff are in
19             #]
20              
21             sub new {
22 52     52 1 912 my $class = shift;
23 52         151 my $self = bless [], $class;
24 52 100       139 $self->add(@_) if @_;
25 52         110 $self;
26             }
27              
28             sub list_ranges {
29 50     50 0 218 my($self) = @_;
30 50         46 my @list;
31 50         119 for(my $i = 0; $i < @$self; $i+=2) {
32 73         132 my $min = _unpack($self->[$i]);
33 73 100       229 my $max = exists $self->[$i + 1]
34             ? _unpack(_decr($self->[$i + 1]))
35             : 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff';
36              
37 73         289 push @list, $min . '-' . $max;
38             }
39 50         296 return @list;
40             }
41              
42             sub _dump {
43 0     0   0 my($self) = @_;
44 0         0 warn "[\n" . join('', map _unpack($_) . "\n", @$self) ."]\n";
45             }
46              
47             sub add {
48 101     101 1 1025 my $self = shift;
49 101         174 for(@_) {
50 127 50       224 my @range = _range($_) or next;
51 127         292 $self->_add(@range, 0);
52             }
53 101         227 return $self;
54             }
55              
56             sub subtract {
57 18     18 1 150 my $self = shift;
58 18         26 for(@_) {
59 18 50       27 my @range = _range($_) or next;
60 18         34 $self->_add(@range, 1);
61             }
62 18         38 return $self;
63             }
64              
65             sub _add {
66 145     145   216 my($self, $min, $max, $sub) = @_;
67            
68 145 100       334 if($max eq $masks_ip6[128]) {
69 3         3 my $i = 0;
70 3   100     22 $i++ while exists $self->[$i] && $self->[$i] lt $min;
71 3 0       15 splice @$self, $i, @$self-$i, $sub
    100          
    50          
72             ? ($i % 2 ? $min : ())
73             : ($i % 2 ? () : $min);
74 3         9 return;
75             }
76              
77 142         223 my $out = _incr($max);
78              
79 142 100       332 if(not @$self) { # if emtpy
80 51         109 @$self = ($min, $out);
81 51         147 return;
82             }
83              
84 91         107 my $i = 0;
85 91   100     2072 $i++ while exists $self->[$i] && $self->[$i] lt $min;
86 91         97 my $j = $i;
87 91   100     445 $j++ while exists $self->[$j] && $self->[$j] le $out;
88              
89 91 100       584 splice @$self, $i, $j-$i,
    100          
    100          
    100          
    100          
90             $sub
91             ? (
92             $i % 2 ? $min : (),
93             $j % 2 ? $out : ()
94             )
95             : (
96             $i % 2 ? () : $min,
97             $j % 2 ? () : $out
98             );
99             }
100              
101              
102             sub find {
103 36     36 1 67 my $self = shift;
104 36 50       84 return 0 if not @$self;
105            
106 36         55 my $addr = _pack(shift);
107              
108             # outside
109 36 100       103 return 0 if $addr lt $self->[0];
110 34 100       83 return @$self % 2 if $addr ge $self->[-1];
111              
112 32         40 my $i = 0;
113 32 100       63 if(@$self < $BIN_THRESHOLD) {
114 10   66     104 $i++ while exists $self->[$i] && $self->[$i] le $addr;
115             }
116             else {
117 22         32 my($l,$r)=(0, scalar @$self);
118              
119 22         50 while($l < $r) {
120 112         165 $i = int(($l + $r) / 2);
121 112 100       206 if($addr lt $self->[$i]) {
122 62 100       144 last if $self->[$i - 1] le $addr;
123 40         81 $r = $i;
124             }
125             else {
126 50         96 $l = $i;
127             }
128             }
129             }
130 32         153 return $i % 2;
131             }
132              
133             # Util functions below
134              
135             sub _pack {
136 317     317   450 my($addr) = @_;
137 317 100       787 $addr =~ /:/ ? _pack_ip6($addr) : _pack_ip4($addr);
138             }
139              
140             sub _pack_ip4 {
141 315     315   333 my $in = shift;
142 315 50       1321 return if not $in =~ /^\d{1,3}(?:\.\d{1,3}){3}$/;
143 315         385 my $str = "\x00" x 12; # 96bit padding
144 315         910 for(split /\./, $in) {
145 1260 50       2418 return if $_ > 255;
146 1260         2082 $str .= pack('C', $_);
147             }
148 315         1012 return $str;
149             }
150              
151             sub _pack_ip6 {
152 12     12   16 my $in = shift;
153 12 50       43 if($in =~ /:([12]\d\d(?:\.[12]\d\d){3})$/) {
154 0         0 return _pack_ip4($1);
155             }
156 12         22 $in =~ s{::}{':0' x (9-($in =~ tr/://))}e;
  5         18  
157 12         44 pack 'H32', join '', map {('0' x (4-length)) . $_} split /:/, $in, -1;
  96         205  
158             }
159              
160             sub _range {
161 145     145   235 my($in) = @_;
162              
163 145 100       381 if($in =~ /-/) {
    100          
164             # addr-addr
165 134         327 my($min, $max) = split /-/, $in, 2;
166 134         228 ($min, $max) = (_pack($min), _pack($max));
167 134 50 33     577 return if not (defined $min and defined $max);
168 134 50       608 return ($min le $max) ? ($min, $max) : ($max, $min);
169             }
170             elsif($in =~ /\//) {
171             # addr/mask
172 10         25 my($addr, $mask) = split /\//, $in, 2;
173 10 100       30 if($addr =~ /:/) {
174 5         9 $addr = _pack_ip6($addr);
175 5         13 $mask = $masks_ip6[$mask];
176             }
177             else {
178 5         11 $addr = _pack_ip4($addr);
179 5 50       31 $mask = $mask =~ /\./ ? $masks_ip4{$mask} : $masks_ip4[$mask] or return;
    50          
180             }
181 10         52 return $addr & $mask, $addr | ~$mask
182             }
183             else {
184             # addr
185 1         3 my $addr = _pack($in);
186 1         6 return $addr, $addr;
187             }
188             }
189              
190             sub _unpack {
191 155     155   173 my($addr) = @_;
192 155 100       439 ($addr & ~ $masks_ip4[0]) eq $addr
193             ? _unpack_ip4($addr)
194             : _unpack_ip6($addr)
195             ;
196             }
197              
198             sub _unpack_ip4 {
199 143     143   731 join '.', unpack 'CCCC' ,substr(shift,12);
200             }
201              
202             sub _unpack_ip6 {
203 12     12   99 my $v6 = join ':', unpack 'H4H4H4H4H4H4H4H4', shift;
204 12         138 $v6 =~ s/(^|:)0{1,3}/${1}/g; # omit leading zeroes
205 12         73 $v6 =~ s/(?:(?:^|:)0){2,}(?::)?/::/; # group of zeroes
206 12         41 $v6;
207             }
208              
209             sub _incr {
210 146     146   175 my($in) = @_;
211 146         235 my $p = length($in) * 8 - 1;
212 146         371 while(vec($in, $p^7, 1)) {
213 397         561 vec($in, $p--^7,1) = 0;
214 397 50       984 return $in if $p < 0;
215             }
216 146         333 vec($in, $p^7, 1) = 1;
217 146         315 return $in;
218             }
219              
220             sub _decr {
221 74     74   94 my($in) = @_;
222 74         103 my $p = length($in) * 8 - 1;
223 74         173 while(not vec($in, $p^7, 1)) {
224 218         309 vec($in, $p--^7,1) = 1;
225 218 50       558 return $in if $p < 0;
226             }
227 74         140 vec($in, $p^7, 1) = 0;
228 74         178 return $in;
229             }
230              
231             package Net::IP::AddrRanges::Spanner;
232              
233             sub new {
234 0     0     my $class = shift;
235 0           bless {@_}, $class;
236             }
237              
238             sub find {
239 0     0     my $self = shift;
240 0           my %result;
241 0           for my $addr (@_) {
242 0           while(my($k, $range) = each %$self) {
243 0           $result{$addr}{$k} = $range->find($addr);
244             }
245             }
246 0           \%result;
247             }
248              
249             1;
250             __END__