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.08 2020/12/31 12:10:06 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   1455 use 5.008;
  2         7  
35              
36 2     2   12 use strict;
  2         4  
  2         40  
37 2     2   10 use warnings;
  2         3  
  2         61  
38 2     2   11 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         4  
  2         120  
39 2     2   11 use base qw(Exporter);
  2         4  
  2         150  
40 2     2   14 use Paranoid;
  2         4  
  2         110  
41 2     2   468 use Paranoid::Debug qw(:all);
  2         4  
  2         368  
42 2     2   938 use Paranoid::Network::Socket;
  2         5  
  2         946  
43 2     2   1024 use Paranoid::Network::IPv4 qw(:all);
  2         5  
  2         298  
44 2     2   1001 use Paranoid::Network::IPv6 qw(:all);
  2         5  
  2         452  
45              
46             ($VERSION) = ( q$Revision: 2.08 $ =~ /(\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         2303 use constant HOSTNAME_REGEX =>
53 2     2   16 qr#(?:[a-z0-9][a-z0-9\-]*)(?:\.[a-z0-9][a-z0-9\-]*)*\.?#s;
  2         5  
54              
55             #####################################################################
56             #
57             # Module code follows
58             #
59             #####################################################################
60              
61             {
62              
63             my $lmatch;
64              
65             sub NETMATCH : lvalue {
66 33     33 1 67 $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 53 my $ip = shift;
79 15         33 my @networks = grep {defined} @_;
  21         66  
80 15         19 my $rv = 0;
81 15         27 my ( $family, @tmp );
82              
83 15         52 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $ip, @networks );
84 15         39 pIn();
85              
86 15         33 NETMATCH = undef;
87              
88             # Validate arguments
89 15 50       34 if ( defined $ip ) {
90              
91             # Extract IPv4 address from IPv6 encoding
92 15         47 $ip =~ s/^::ffff:(@{[ IPV4REGEX ]})$/$1/sio;
  1         68  
93              
94             # Check for IPv6 support
95 15 50 33     43 if ( has_ipv6() or $] >= 5.012 ) {
96              
97 15         39 pdebug( 'Found IPv4/IPv6 support', PDLEVEL2 );
98 15 100       83 $family =
    100          
99 1         62 $ip =~ m/^@{[ IPV4REGEX ]}$/so ? AF_INET()
100 1         89 : $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     71 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         130  
116 1         4 ? m#^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$#so
  1         115  
117 1         3 : m#^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$#so
  1         130  
118             } @networks;
119              
120 14         45 pdebug( 'networks to compare: %s', PDLEVEL2, @networks );
121              
122             # Start comparisons
123 14         30 foreach (@networks) {
124 13 100       50 if ($family == AF_INET()
    100          
125             ? ipv4NetIntersect( $ip, $_ )
126             : ipv6NetIntersect( $ip, $_ )
127             ) {
128 10         19 NETMATCH = $_;
129 10         13 $rv = 1;
130 10         20 last;
131             }
132             }
133             }
134              
135 15         35 pOut();
136 15         33 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
137              
138 15         79 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 15 my $host = shift;
148 5         11 my @domains = @_;
149 5         10 my $rv = 0;
150 5         7 my $domain;
151              
152 5         17 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $host, @domains );
153 5         14 pIn();
154              
155 5         11 NETMATCH = undef;
156              
157 5 100 66     45 if ( defined $host and $host =~ /^@{[ HOSTNAME_REGEX ]}$/so ) {
  1         35  
158              
159             # Filter out non-domains
160             @domains =
161 4 50       10 grep { defined $_ && m/^@{[ HOSTNAME_REGEX ]}$/so } @domains;
  5         29  
  1         28  
162              
163             # Start the comparison
164 4 100       10 if (@domains) {
165 3         7 foreach $domain (@domains) {
166 4 100       265 if ( $host =~ /^(?:[\w\-]+\.)*\Q$domain\E$/si ) {
167 3         13 NETMATCH = $domain;
168 3         6 $rv = 1;
169 3         7 last;
170             }
171             }
172             }
173             }
174              
175 5         20 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 19 my @strings = @_;
188 8         17 my ( $string, @ips, $ip, @tmp, @rv );
189              
190 8         27 pdebug( 'entering w/%d strings', PDLEVEL1, scalar @strings );
191 8         24 pIn();
192              
193 8         15 foreach $string (@strings) {
194 10 50       22 next unless defined $string;
195              
196             # Look for IPv4 addresses
197 10         2251 @ips = ( $string =~ /(@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})/sog );
  1         3  
  1         289  
198              
199             # Validate them by filtering through inet_aton
200 10         25 foreach $ip (@ips) {
201 28         65 @tmp = split m#/#s, $ip;
202 28 50       103 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         2385 @ips = ( $string =~
210 1         3 m/(@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})/sogix );
  1         318  
211              
212             # Filter out addresses with more than one ::
213 10         25 @ips = grep { scalar(m/(::)/sg) <= 1 } @ips;
  45         169  
214              
215             # Validate remaining addresses with inet_pton
216 10         22 foreach $ip (@ips) {
217 45         88 @tmp = split m#/#s, $ip;
218 45 100       153 push @rv, $ip
219             if defined inet_pton( AF_INET6(), $tmp[0] );
220             }
221             }
222             }
223              
224 8         22 pOut();
225 8         22 pdebug( 'leaving w/rv: %s', PDLEVEL1, @rv );
226              
227 8         60 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 578 my $target = shift;
243 6         9 my $dest = shift;
244 6         9 my $rv = 0;
245              
246 6         21 pdebug( 'entering w/(%s)(%s)', PDLEVEL1, $target, $dest );
247 6         16 pIn();
248              
249 6 50 33     26 if ( defined $target and defined $dest ) {
250 6 100       12 if ( $target =~ m/^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$/s ) {
  6 100       13  
  6         161  
251 3         17 $rv = ipv4NetIntersect( $target, $dest );
252 3         10 } elsif ( $target =~ m/^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$/si )
  3         188  
253             {
254 2 50 33     9 $rv = ipv6NetIntersect( $target, $dest )
255             if has_ipv6()
256             or $] >= 5.012;
257             } else {
258 1         4 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         27 pOut();
269 6         18 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
270              
271 6         33 return $rv;
272             }
273              
274             1;
275              
276             __END__