File Coverage

blib/lib/FusionInventory/Agent/Tools/Network.pm
Criterion Covered Total %
statement 95 109 87.1
branch 29 44 65.9
condition 9 21 42.8
subroutine 18 19 94.7
pod 11 11 100.0
total 162 204 79.4


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Tools::Network;
2              
3 64     64   13612170 use strict;
  64         88  
  64         1446  
4 64     64   212 use warnings;
  64         70  
  64         1354  
5 64     64   194 use base 'Exporter';
  64         119  
  64         3914  
6              
7 64     64   630 use English qw(-no_match_vars);
  64         3061  
  64         293  
8 64     64   54801 use Net::IP qw(:PROC);
  64         1841489  
  64         21897  
9 64     64   150419 use Net::hostent;
  64         86612  
  64         254  
10 64     64   9313 use Socket;
  64         34988  
  64         32184  
11              
12 64     64   1465 use FusionInventory::Agent::Tools;
  64         94  
  64         71053  
13              
14             our @EXPORT = qw(
15             $mac_address_pattern
16             $ib_mac_address_pattern
17             $any_mac_address_pattern
18             $ip_address_pattern
19             $alt_mac_address_pattern
20             $hex_ip_address_pattern
21             $network_pattern
22             getSubnetAddress
23             getSubnetAddressIPv6
24             getNetworkMask
25             getNetworkMaskIPv6
26             isSameNetwork
27             isSameNetworkIPv6
28             hex2canonical
29             alt2canonical
30             resolve
31             compile
32             isPartOf
33             );
34              
35             my $dec_byte = qr/[0-9]{1,3}/;
36             my $hex_byte = qr/[0-9A-F]{1,2}/i;
37             my $padded_hex_byte = qr/[0-9A-F]{2}/i;
38              
39             our $mac_address_pattern = qr/
40             $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte
41             /x;
42              
43             our $ib_mac_address_pattern = qr/
44             $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte :
45             $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte :
46             $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte : $hex_byte :
47             $hex_byte : $hex_byte
48             /x;
49              
50             our $any_mac_address_pattern = qr/
51             (?:$ib_mac_address_pattern|$mac_address_pattern)
52             /x;
53              
54             our $ip_address_pattern = qr/
55             $dec_byte \. $dec_byte \. $dec_byte \. $dec_byte
56             /x;
57              
58             our $alt_mac_address_pattern = qr/
59             $padded_hex_byte
60             $padded_hex_byte
61             $padded_hex_byte
62             $padded_hex_byte
63             $padded_hex_byte
64             $padded_hex_byte
65             /x;
66              
67             our $hex_ip_address_pattern = qr/
68             $padded_hex_byte
69             $padded_hex_byte
70             $padded_hex_byte
71             $padded_hex_byte
72             /x;
73              
74             our $network_pattern = qr/
75             $dec_byte (?:\. $dec_byte (?:\. $dec_byte (?:\. $dec_byte)?)?)? \/ \d{1,2}
76             /x;
77              
78             sub getSubnetAddress {
79 30     30 1 39 my ($address, $mask) = @_;
80              
81 30 100 100     123 return undef unless $address && $mask; ## no critic (ExplicitReturnUndef)
82              
83 27         60 my $binaddress = ip_iptobin($address, 4);
84 27         280 my $binmask = ip_iptobin($mask, 4);
85 27         186 my $binsubnet = $binaddress & $binmask; ## no critic (ProhibitBitwise)
86              
87 27         46 return ip_bintoip($binsubnet, 4);
88             }
89              
90             sub getSubnetAddressIPv6 {
91 16     16 1 29 my ($address, $mask) = @_;
92              
93 16 50 33     71 return undef unless $address && $mask; ## no critic (ExplicitReturnUndef)
94              
95 16         31 my $binaddress = ip_iptobin(ip_expand_address($address, 6), 6);
96 16         754 my $binmask = ip_iptobin(ip_expand_address($mask, 6), 6);
97 16         664 my $binsubnet = $binaddress & $binmask; ## no critic (ProhibitBitwise)
98              
99 16         31 return ip_compress_address(ip_bintoip($binsubnet, 6), 6);
100             }
101              
102             sub isSameNetwork {
103 2     2 1 2112 my ($address1, $address2, $mask) = @_;
104              
105             ## no critic (ExplicitReturnUndef)
106 2 50 33     16 return undef unless $address1 && $address2 && $mask;
      33        
107              
108 2         8 my $binaddress1 = ip_iptobin($address1, 4);
109 2         31 my $binaddress2 = ip_iptobin($address2, 4);
110 2         13 my $binmask = ip_iptobin($mask, 4);
111              
112             ## no critic (ProhibitBitwise)
113 2         20 return ($binaddress1 & $binmask) eq ($binaddress2 & $binmask);
114             }
115              
116             sub isSameNetworkIPv6 {
117 0     0 1 0 my ($address1, $address2, $mask) = @_;
118              
119             ## no critic (ExplicitReturnUndef)
120 0 0 0     0 return undef unless $address1 && $address2 && $mask;
      0        
121              
122 0         0 my $binaddress1 = ip_iptobin(ip_expand_address($address1, 6), 6);
123 0         0 my $binaddress2 = ip_iptobin(ip_expand_address($address2, 6), 6);
124 0         0 my $binmask = ip_iptobin(ip_expand_address($mask, 6), 6);
125              
126             ## no critic (ProhibitBitwise)
127 0         0 return ($binaddress1 & $binmask) eq ($binaddress2 & $binmask);
128             }
129              
130             sub hex2canonical {
131 31     31 1 1386 my ($address) = @_;
132 31 50       54 return unless $address;
133              
134 31         113 my @bytes = $address =~ /^(?:0x)?(..)(..)(..)(..)$/;
135 31         42 return join('.', map { hex($_) } @bytes);
  124         220  
136             }
137              
138             sub alt2canonical {
139 116     116 1 461 my ($address) = @_;
140 116 50       132 return unless $address;
141              
142 116         339 my @bytes = $address =~ /^(?:0x)?(..)(..)(..)(..)(..)(..)$/;
143 116         594 return join(':', @bytes);
144             }
145              
146             sub getNetworkMask {
147 17     17 1 724 my ($prefix) = @_;
148              
149 17 100       39 return undef unless $prefix; ## no critic (ExplicitReturnUndef)
150              
151 15         50 return ip_bintoip(ip_get_mask($prefix, 4), 4);
152             }
153              
154             sub getNetworkMaskIPv6 {
155 16     16 1 25 my ($prefix) = @_;
156              
157 16 50       30 return undef unless $prefix; ## no critic (ExplicitReturnUndef)
158              
159 16         36 return ip_compress_address(ip_bintoip(ip_get_mask($prefix, 6), 6), 6);
160             }
161              
162             sub resolve {
163 6     6 1 9 my ($name, $logger) = @_;
164              
165 6         3 my @addresses;
166             my @errors;
167              
168 6 50       33 if ($Socket::VERSION >= 1.94) {
169             # IPv6 compatible version
170 6         34806 my ($error, @results) = Socket::getaddrinfo(
171             $name, undef, {
172             family => Socket::AF_UNSPEC(),
173             protocol => Socket::IPPROTO_TCP()
174             }
175             );
176 6 100       57 if ($error) {
177 3         33 push @errors, "unable to get address for '$name': $error";
178 3         12 @results = ();
179             }
180              
181             # and push all of their addresses in the list
182 6         24 foreach my $result (@results) {
183             my ($error, $address) = Socket::getnameinfo(
184 6         2106 $result->{addr}, Socket::NI_NUMERICHOST()
185             );
186 6 50       21 if ($error) {
187 0         0 push @errors,
188             "unable to translate binary address for '$name': $error";
189 0         0 next;
190             }
191              
192             # Drop the zone index, as Net::IP does not support it
193 6         18 $address =~ s/%.*$//;
194              
195 6         18 push @addresses, $address;
196             }
197             }
198              
199             # If needed, try also legacy resolving and only report previous errors from here
200 6 100       18 unless (@addresses) {
201             # IPv4-only version
202 3         51 my $result = gethostbyname($name);
203 3 50       14976 if (!$result) {
204 3         36 push @errors,
205             "unable to get address for '$name': $ERRNO";
206 3 50       12 map { $logger->error($_) } @errors
  0         0  
207             if $logger;
208 3         42 return;
209             }
210 0         0 foreach my $packed_address (@{$result->addr_list()}) {
  0         0  
211 0         0 push @addresses, inet_ntoa($packed_address);
212             }
213             }
214              
215 3         12 return map { Net::IP->new($_) } @addresses;
  6         2217  
216             }
217              
218             sub compile {
219 15     15 1 18 my ($string, $logger) = @_;
220              
221 15 50       21 return unless $string;
222              
223             # that's already an IP address, just convert it
224 15 100       246 return Net::IP->new($string)
225             if $string =~ /^$ip_address_pattern/;
226              
227             # otherwise resolve the name
228 6         12 return resolve($string, $logger);
229             }
230              
231             sub isPartOf {
232 18     18 1 24 my ($string, $ranges, $logger) = @_;
233              
234 18 50       57 return unless $string;
235 18 50       27 return unless $ranges;
236              
237 18         45 my $address = Net::IP->new($string);
238              
239 18 50       7137 if (!$address) {
240 0         0 $logger->error("Not well formatted source IP: $string");
241 0         0 return;
242             }
243              
244 18         66 foreach my $range (@{$ranges}) {
  18         36  
245 21         48 my $result = $address->overlaps($range);
246              
247 21 100 100     1422 if (!$result && Net::IP::Error()) {
248 6         30 $logger->debug("Server: ".Net::IP::Error());
249 6         12 next;
250             }
251              
252             # included in trusted range
253 15 100       69 return 1 if $result == $IP_A_IN_B_OVERLAP;
254              
255             # equals trusted address
256 12 100       66 return 1 if $result == $IP_IDENTICAL;
257             }
258              
259 9         66 return 0;
260             }
261              
262             1;
263             __END__