File Coverage

blib/lib/Paranoid/Network.pm
Criterion Covered Total %
statement 127 131 96.9
branch 32 42 76.1
condition 8 18 44.4
subroutine 16 16 100.0
pod 5 5 100.0
total 188 212 88.6


line stmt bran cond sub pod time code
1             # Paranoid::Network -- Network functions for paranoid programs
2             #
3             # $Id: lib/Paranoid/Network.pm, 2.09 2021/12/28 15:46:49 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::Network;
33              
34 2     2   1364 use 5.008;
  2         7  
35              
36 2     2   10 use strict;
  2         5  
  2         38  
37 2     2   9 use warnings;
  2         3  
  2         67  
38 2     2   12 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         5  
  2         115  
39 2     2   11 use base qw(Exporter);
  2         4  
  2         120  
40 2     2   11 use Paranoid;
  2         3  
  2         84  
41 2     2   467 use Paranoid::Debug qw(:all);
  2         15  
  2         365  
42 2     2   1020 use Paranoid::Network::Socket;
  2         6  
  2         938  
43 2     2   1018 use Paranoid::Network::IPv4 qw(:all);
  2         4  
  2         281  
44 2     2   968 use Paranoid::Network::IPv6 qw(:all);
  2         9  
  2         445  
45              
46             ($VERSION) = ( q$Revision: 2.09 $ =~ /(\d+(?:\.\d+)+)/sm );
47              
48             @EXPORT = qw(ipInNetworks hostInDomains extractIPs netIntersect);
49             @EXPORT_OK = ( @EXPORT, qw(NETMATCH HOSTNAME_REGEX) );
50             %EXPORT_TAGS = ( all => [@EXPORT_OK], );
51              
52 2         2447 use constant HOSTNAME_REGEX =>
53 2     2   14 qr#(?:[a-z0-9][a-z0-9\-]*)(?:\.[a-z0-9][a-z0-9\-]*)*\.?#s;
  2         4  
54              
55             #####################################################################
56             #
57             # Module code follows
58             #
59             #####################################################################
60              
61             {
62              
63             my $lmatch;
64              
65             sub NETMATCH : lvalue {
66 33     33 1 60 $lmatch;
67             }
68              
69             }
70              
71             sub ipInNetworks {
72              
73             # Purpose: Checks to see if the IP occurs in the passed list of IPs and
74             # networks
75             # Returns: True (1) if the IP occurs, False (0) otherwise
76             # Usage: $rv = ipInNetworks($ip, @networks);
77              
78 15     15 1 47 my $ip = shift;
79 15         32 my @networks = grep {defined} @_;
  21         66  
80 15         20 my $rv = 0;
81 15         27 my ( $family, @tmp );
82              
83 15         47 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $ip, @networks );
84 15         51 pIn();
85              
86 15         26 NETMATCH = undef;
87              
88             # Validate arguments
89 15 50       29 if ( defined $ip ) {
90              
91             # Extract IPv4 address from IPv6 encoding
92 15         44 $ip =~ s/^::ffff:(@{[ IPV4REGEX ]})$/$1/sio;
  1         61  
93              
94             # Check for IPv6 support
95 15 50 33     43 if ( has_ipv6() or $] >= 5.012 ) {
96              
97 15         35 pdebug( 'Found IPv4/IPv6 support', PDLEVEL2 );
98 15 100       86 $family =
    100          
99 1         62 $ip =~ m/^@{[ IPV4REGEX ]}$/so ? AF_INET()
100 1         93 : $ip =~ m/^@{[ IPV6REGEX ]}$/so ? AF_INET6()
101             : undef;
102              
103             } else {
104              
105 0         0 pdebug( 'Found only IPv4 support', PDLEVEL2 );
106 0 0       0 $family = AF_INET()
107 0         0 if $ip =~ m/^@{[ IPV4REGEX ]}$/so;
108             }
109             }
110              
111 15 100 66     70 if ( defined $ip and defined $family ) {
112              
113             # Filter out non-family data from @networks
114             @networks = grep {
115 14 100       115 $family == AF_INET()
  20         140  
116 1         3 ? m#^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$#so
  1         116  
117 1         3 : m#^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$#so
  1         132  
118             } @networks;
119              
120 14         48 pdebug( 'networks to compare: %s', PDLEVEL2, @networks );
121              
122             # Start comparisons
123 14         31 foreach (@networks) {
124 13 100       48 if ($family == AF_INET()
    100          
125             ? ipv4NetIntersect( $ip, $_ )
126             : ipv6NetIntersect( $ip, $_ )
127             ) {
128 10         18 NETMATCH = $_;
129 10         13 $rv = 1;
130 10         20 last;
131             }
132             }
133             }
134              
135 15         37 pOut();
136 15         36 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
137              
138 15         70 return $rv;
139             }
140              
141             sub hostInDomains {
142              
143             # Purpose: Checks to see if the host occurs in the list of domains
144             # Returns: True (1) if the host occurs, False (0) otherwise
145             # Usage: $rv = hostInDomains($hostname, @domains);
146              
147 5     5 1 14 my $host = shift;
148 5         10 my @domains = @_;
149 5         9 my $rv = 0;
150 5         7 my $domain;
151              
152 5         16 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $host, @domains );
153 5         12 pIn();
154              
155 5         10 NETMATCH = undef;
156              
157 5 100 66     49 if ( defined $host and $host =~ /^@{[ HOSTNAME_REGEX ]}$/so ) {
  1         36  
158              
159             # Filter out non-domains
160             @domains =
161 4 50       10 grep { defined $_ && m/^@{[ HOSTNAME_REGEX ]}$/so } @domains;
  5         30  
  1         27  
162              
163             # Start the comparison
164 4 100       12 if (@domains) {
165 3         6 foreach $domain (@domains) {
166 4 100       281 if ( $host =~ /^(?:[\w\-]+\.)*\Q$domain\E$/si ) {
167 3         12 NETMATCH = $domain;
168 3         4 $rv = 1;
169 3         7 last;
170             }
171             }
172             }
173             }
174              
175 5         18 pOut();
176 5         12 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
177              
178 5         31 return $rv;
179             }
180              
181             sub extractIPs {
182              
183             # Purpose: Extracts IPv4/IPv6 addresses from arbitrary text.
184             # Returns: List containing extracted IP addresses
185             # Usage: @ips = extractIPs($string1, $string2);
186              
187 8     8 1 24 my @strings = @_;
188 8         13 my ( $string, @ips, $ip, @tmp, @rv );
189              
190 8         26 pdebug( 'entering w/%d strings', PDLEVEL1, scalar @strings );
191 8         23 pIn();
192              
193 8         17 foreach $string (@strings) {
194 10 50       20 next unless defined $string;
195              
196             # Look for IPv4 addresses
197 10         2281 @ips = ( $string =~ /(@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})/sog );
  1         2  
  1         290  
198              
199             # Validate them by filtering through inet_aton
200 10         26 foreach $ip (@ips) {
201 28         62 @tmp = split m#/#s, $ip;
202 28 50       102 push @rv, $ip if defined inet_aton( $tmp[0] );
203             }
204              
205             # If Socket6 is present or we have Perl 5.14 or higher we'll check
206             # for IPv6 addresses
207 10 50 33     29 if ( has_ipv6() or $] >= 5.012 ) {
208              
209 10         2438 @ips = ( $string =~
210 1         3 m/(@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})/sogix );
  1         323  
211              
212             # Filter out addresses with more than one ::
213 10         24 @ips = grep { scalar(m/(::)/sg) <= 1 } @ips;
  45         136  
214              
215             # Validate remaining addresses with inet_pton
216 10         22 foreach $ip (@ips) {
217 45         83 @tmp = split m#/#s, $ip;
218 45 100       137 push @rv, $ip
219             if defined inet_pton( AF_INET6(), $tmp[0] );
220             }
221             }
222             }
223              
224 8         24 pOut();
225 8         23 pdebug( 'leaving w/rv: %s', PDLEVEL1, @rv );
226              
227 8         56 return @rv;
228             }
229              
230             sub netIntersect {
231              
232             # Purpose: Tests whether network address ranges intersect
233             # Returns: Integer, denoting whether an intersection exists, and what
234             # kind:
235             #
236             # -1: destination range encompasses target range
237             # 0: both ranges do not intersect at all
238             # 1: target range encompasses destination range
239             #
240             # Usage: $rv = netIntersect( $cidr1, $cidr2 );
241              
242 6     6 1 699 my $target = shift;
243 6         9 my $dest = shift;
244 6         11 my $rv = 0;
245              
246 6         17 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $target, $dest );
247 6         18 pIn();
248              
249 6 50 33     26 if ( defined $target and defined $dest ) {
250 6 100       13 if ( $target =~ m/^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$/s ) {
  6 100       12  
  6         165  
251 3         13 $rv = ipv4NetIntersect( $target, $dest );
252 3         9 } elsif ( $target =~ m/^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$/si )
  3         150  
253             {
254 2 50 33     9 $rv = ipv6NetIntersect( $target, $dest )
255             if has_ipv6()
256             or $] >= 5.012;
257             } else {
258 1         5 pdebug(
259             'target string (%s) doesn\'t seem to match '
260             . 'an IP/network address',
261             PDLEVEL1, $target
262             );
263             }
264             } else {
265 0         0 pdebug( 'one or both arguments are not defined', PDLEVEL1 );
266             }
267              
268 6         22 pOut();
269 6         15 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
270              
271 6         27 return $rv;
272             }
273              
274             1;
275              
276             __END__