File Coverage

lib/Net/IPAddress/Filter.pm
Criterion Covered Total %
statement 49 51 96.0
branch 12 18 66.6
condition 1 2 50.0
subroutine 11 11 100.0
pod 5 5 100.0
total 78 87 89.6


line stmt bran cond sub pod time code
1             package Net::IPAddress::Filter;
2              
3 1     1   792 use strict;
  1         3  
  1         35  
4 1     1   5 use warnings;
  1         2  
  1         44  
5              
6             # ABSTRACT: A compact and fast IP Address range filter
7             our $VERSION = '20140113'; # VERSION
8              
9              
10 1     1   1003 use Net::CIDR::Lite;
  1         4510  
  1         30  
11 1     1   768 use Set::IntervalTree; # XS module.
  1         7750  
  1         699  
12              
13             our $CIDR_REGEX = qr{/ \d+ \z}xms;
14              
15              
16             sub new {
17 9     9 1 4842 my $class = shift;
18              
19 9         78 my $self = { filter => Set::IntervalTree->new(), };
20              
21 9         46 return bless $self, $class;
22             }
23              
24              
25             sub add_range {
26 9     9 1 41 my ( $self, $start_ip, $end_ip ) = @_;
27              
28 9         27 my ($start_num, $end_num) = _get_start_and_end_numbers($start_ip, $end_ip);
29              
30             # Different versions of Set::IntervalTree use different-closed intervals,
31             # so need to allow for that.
32 9 50       23 if ( $Set::IntervalTree::VERSION < 0.08 ) {
33 0 0       0 $self->{filter}->insert($start_ip . ($end_ip ? ",$end_ip" : ''), $start_num - 1, $end_num + 1 );
34             }
35             else {
36 9 100       69 $self->{filter}->insert($start_ip . ($end_ip ? ",$end_ip" : ''), $start_num, $end_num + 1 );
37             }
38              
39 9         58 return 1;
40             }
41              
42              
43             sub add_range_with_value {
44 2     2 1 10 my ( $self, $value, $start_ip, $end_ip ) = @_;
45              
46 2         7 my ($start_num, $end_num) = _get_start_and_end_numbers($start_ip, $end_ip);
47              
48             # Different versions of Set::IntervalTree use different-closed intervals,
49             # so need to allow for that.
50 2 50       10 if ( $Set::IntervalTree::VERSION < 0.08 ) {
51 0         0 $self->{filter}->insert( $value, $start_num - 1, $end_num + 1 );
52             }
53             else {
54 2         22 $self->{filter}->insert( $value, $start_num, $end_num + 1 );
55             }
56              
57 2         9 return 1;
58             }
59              
60              
61             sub in_filter {
62 31     31 1 71 my ( $self, $test_ip ) = @_;
63              
64 31         55 my $test_num = _ip_address_to_number($test_ip);
65 31 50       97 my $end_num = $Set::IntervalTree::VERSION < 0.08 ? $test_num : $test_num + 1;
66              
67 31   50     271 my $found = $self->{filter}->fetch( $test_num, $end_num ) || return 0;
68              
69 31         159 return scalar @$found;
70             }
71              
72              
73             sub get_matches {
74 2     2 1 6 my ( $self, $test_ip ) = @_;
75              
76 2         5 my $test_num = _ip_address_to_number($test_ip);
77 2 50       39 my $end_num = $Set::IntervalTree::VERSION < 0.08 ? $test_num : $test_num + 1;
78              
79 2         22 return $self->{filter}->fetch( $test_num, $end_num );
80              
81             }
82              
83              
84             sub _get_start_and_end_numbers {
85 11     11   19 my ( $start_ip, $end_ip ) = @_;
86              
87 11         12 my ($start_num, $end_num);
88              
89 11 100       107 if ( $start_ip =~ $CIDR_REGEX ) {
90 3         18 my $cidr = Net::CIDR::Lite->new;
91 3         57 $cidr->add($start_ip);
92 3         504 my ( $start_cidr, $end_cidr ) = split /-/, @{ $cidr->list_range() }[0];
  3         12  
93 3         301 $start_num = _ip_address_to_number($start_cidr);
94 3         10 $end_num = _ip_address_to_number($end_cidr);
95             }
96             else {
97 8         17 $start_num = _ip_address_to_number($start_ip);
98 8 100       30 $end_num = $end_ip ? _ip_address_to_number($end_ip) : $start_num;
99             }
100              
101             # Guarantee that the start <= end
102 11 100       34 if ( $end_num < $start_num ) {
103 1         3 ( $start_num, $end_num ) = ( $end_num, $start_num );
104             }
105              
106 11         26 return ( $start_num, $end_num );
107             }
108              
109              
110             sub _ip_address_to_number {
111              
112 54     54   463 return unpack 'N', pack 'C4', split '\.', shift;
113             }
114              
115             1;
116              
117             __END__