File Coverage

blib/lib/Paranoid/Network.pm
Criterion Covered Total %
statement 119 123 96.7
branch 32 42 76.1
condition 8 18 44.4
subroutine 16 16 100.0
pod 5 5 100.0
total 180 204 88.2


line stmt bran cond sub pod time code
1             # Paranoid::Network -- Network functions for paranoid programs
2             #
3             # $Id: lib/Paranoid/Network.pm, 2.10 2022/03/08 00:01:04 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   1102 use 5.008;
  2         6  
35              
36 2     2   11 use strict;
  2         4  
  2         35  
37 2     2   7 use warnings;
  2         5  
  2         45  
38 2     2   9 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  2         3  
  2         91  
39 2     2   10 use base qw(Exporter);
  2         3  
  2         128  
40 2     2   11 use Paranoid;
  2         3  
  2         90  
41 2     2   432 use Paranoid::Debug qw(:all);
  2         4  
  2         316  
42 2     2   760 use Paranoid::Network::Socket;
  2         5  
  2         806  
43 2     2   783 use Paranoid::Network::IPv4 qw(:all);
  2         4  
  2         229  
44 2     2   784 use Paranoid::Network::IPv6 qw(:all);
  2         4  
  2         363  
45              
46             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\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         1957 use constant HOSTNAME_REGEX =>
53 2     2   12 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 47 $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 38 my $ip = shift;
79 15         28 my @networks = grep {defined} @_;
  21         51  
80 15         22 my $rv = 0;
81 15         17 my ( $family, @tmp );
82              
83 15         47 subPreamble( PDLEVEL1, '$@', $ip, @networks );
84              
85 15         28 NETMATCH = undef;
86              
87             # Validate arguments
88 15 50       28 if ( defined $ip ) {
89              
90             # Extract IPv4 address from IPv6 encoding
91 15         45 $ip =~ s/^::ffff:(@{[ IPV4REGEX ]})$/$1/sio;
  1         51  
92              
93             # Check for IPv6 support
94 15 50 33     36 if ( has_ipv6() or $] >= 5.012 ) {
95              
96 15         32 pdebug( 'Found IPv4/IPv6 support', PDLEVEL2 );
97 15 100       69 $family =
    100          
98 1         53 $ip =~ m/^@{[ IPV4REGEX ]}$/so ? AF_INET()
99 1         68 : $ip =~ m/^@{[ IPV6REGEX ]}$/so ? AF_INET6()
100             : undef;
101              
102             } else {
103              
104 0         0 pdebug( 'Found only IPv4 support', PDLEVEL2 );
105 0 0       0 $family = AF_INET()
106 0         0 if $ip =~ m/^@{[ IPV4REGEX ]}$/so;
107             }
108             }
109              
110 15 100 66     53 if ( defined $ip and defined $family ) {
111              
112             # Filter out non-family data from @networks
113             @networks = grep {
114 14 100       27 $family == AF_INET()
  20         106  
115 1         13 ? m#^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$#so
  1         98  
116 1         3 : m#^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$#so
  1         130  
117             } @networks;
118              
119 14         42 pdebug( 'networks to compare: %s', PDLEVEL2, @networks );
120              
121             # Start comparisons
122 14         26 foreach (@networks) {
123 13 100       39 if ($family == AF_INET()
    100          
124             ? ipv4NetIntersect( $ip, $_ )
125             : ipv6NetIntersect( $ip, $_ )
126             ) {
127 10         14 NETMATCH = $_;
128 10         13 $rv = 1;
129 10         11 last;
130             }
131             }
132             }
133              
134 15         40 subPostamble( PDLEVEL1, '$', $rv );
135              
136 15         71 return $rv;
137             }
138              
139             sub hostInDomains {
140              
141             # Purpose: Checks to see if the host occurs in the list of domains
142             # Returns: True (1) if the host occurs, False (0) otherwise
143             # Usage: $rv = hostInDomains($hostname, @domains);
144              
145 5     5 1 11 my $host = shift;
146 5         11 my @domains = @_;
147 5         6 my $rv = 0;
148 5         40 my $domain;
149              
150 5         18 subPreamble( PDLEVEL1, '$@', $host, @domains );
151              
152 5         11 NETMATCH = undef;
153              
154 5 100 66     25 if ( defined $host and $host =~ /^@{[ HOSTNAME_REGEX ]}$/so ) {
  1         30  
155              
156             # Filter out non-domains
157             @domains =
158 4 50       9 grep { defined $_ && m/^@{[ HOSTNAME_REGEX ]}$/so } @domains;
  5         23  
  1         21  
159              
160             # Start the comparison
161 4 100       9 if (@domains) {
162 3         6 foreach $domain (@domains) {
163 4 100       229 if ( $host =~ /^(?:[\w\-]+\.)*\Q$domain\E$/si ) {
164 3         10 NETMATCH = $domain;
165 3         3 $rv = 1;
166 3         5 last;
167             }
168             }
169             }
170             }
171              
172 5         18 subPostamble( PDLEVEL1, '$', $rv );
173              
174 5         24 return $rv;
175             }
176              
177             sub extractIPs {
178              
179             # Purpose: Extracts IPv4/IPv6 addresses from arbitrary text.
180             # Returns: List containing extracted IP addresses
181             # Usage: @ips = extractIPs($string1, $string2);
182              
183 8     8 1 19 my @strings = @_;
184 8         12 my ( $string, @ips, $ip, @tmp, @rv );
185              
186 8         24 subPreamble( PDLEVEL1, '@', @strings );
187              
188 8         14 foreach $string (@strings) {
189 10 50       19 next unless defined $string;
190              
191             # Look for IPv4 addresses
192 10         1847 @ips = ( $string =~ /(@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})/sog );
  1         2  
  1         234  
193              
194             # Validate them by filtering through inet_aton
195 10         22 foreach $ip (@ips) {
196 28         53 @tmp = split m#/#s, $ip;
197 28 50       86 push @rv, $ip if defined inet_aton( $tmp[0] );
198             }
199              
200             # If Socket6 is present or we have Perl 5.14 or higher we'll check
201             # for IPv6 addresses
202 10 50 33     21 if ( has_ipv6() or $] >= 5.012 ) {
203              
204 10         1979 @ips =
205 1         2 ( $string =~ m/(@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})/sogix );
  1         262  
206              
207             # Filter out addresses with more than one ::
208 10         24 @ips = grep { scalar(m/(::)/sg) <= 1 } @ips;
  45         105  
209              
210             # Validate remaining addresses with inet_pton
211 10         18 foreach $ip (@ips) {
212 45         84 @tmp = split m#/#s, $ip;
213 45 100       122 push @rv, $ip
214             if defined inet_pton( AF_INET6(), $tmp[0] );
215             }
216             }
217             }
218              
219 8         23 subPostamble( PDLEVEL1, '@', @rv );
220              
221 8         58 return @rv;
222             }
223              
224             sub netIntersect {
225              
226             # Purpose: Tests whether network address ranges intersect
227             # Returns: Integer, denoting whether an intersection exists, and what
228             # kind:
229             #
230             # -1: destination range encompasses target range
231             # 0: both ranges do not intersect at all
232             # 1: target range encompasses destination range
233             #
234             # Usage: $rv = netIntersect( $cidr1, $cidr2 );
235              
236 6     6 1 553 my $target = shift;
237 6         9 my $dest = shift;
238 6         7 my $rv = 0;
239              
240 6         18 subPreamble( PDLEVEL1, '$$', $target, $dest );
241              
242 6 50 33     25 if ( defined $target and defined $dest ) {
243 6 100       8 if ( $target =~ m/^(?:@{[ IPV4CIDRRGX ]}|@{[ IPV4REGEX ]})$/s ) {
  6 100       10  
  6         160  
244 3         11 $rv = ipv4NetIntersect( $target, $dest );
245 3         7 } elsif ( $target =~ m/^(?:@{[ IPV6CIDRRGX ]}|@{[ IPV6REGEX ]})$/si )
  3         148  
246             {
247 2 50 33     9 $rv = ipv6NetIntersect( $target, $dest )
248             if has_ipv6()
249             or $] >= 5.012;
250             } else {
251 1         4 pdebug(
252             'target string (%s) doesn\'t seem to match '
253             . 'an IP/network address',
254             PDLEVEL1, $target
255             );
256             }
257             } else {
258 0         0 pdebug( 'one or both arguments are not defined', PDLEVEL1 );
259             }
260              
261 6         21 subPostamble( PDLEVEL1, '$', $rv );
262              
263 6         26 return $rv;
264             }
265              
266             1;
267              
268             __END__