File Coverage

blib/lib/Sys/HostAddr.pm
Criterion Covered Total %
statement 104 187 55.6
branch 43 116 37.0
condition 21 63 33.3
subroutine 14 18 77.7
pod 6 8 75.0
total 188 392 47.9


line stmt bran cond sub pod time code
1             # Sys::HostAddr.pm
2             # $Id: HostAddr.pm,v 0.993 2014/09/06 00:53:19 jkister Exp $
3             # Copyright (c) 2010-2014 Jeremy Kister.
4             # Released under Perl's Artistic License.
5              
6             package Sys::HostAddr;
7              
8 1     1   20301 use strict;
  1         3  
  1         90  
9 1     1   6 use warnings;
  1         3  
  1         35  
10 1     1   1271 use IO::Socket::INET;
  1         93989  
  1         10  
11 1     1   1824 use Sys::Hostname;
  1         1123  
  1         2712  
12              
13             our ($VERSION) = q$Revision: 0.993 $ =~ /(\d+\.\d+)/;
14             my $ipv;
15              
16              
17             $ENV{PATH} = ($^O eq 'MSWin32') ?
18             'C:\Windows\system32;C:\Windows;C:\strawberry\c\bin;C:\strawberry\perl\bin;' . $ENV{PATH} :
19             "/usr/sbin:/sbin:/usr/etc:$ENV{PATH}"; # silly centos not having ifconfig in path of non-root
20              
21             sub new {
22 1     1 0 14 my $class = shift;
23 1         2 my %args;
24 1 50       6 if(@_ % 2){
25 0         0 my $interface = shift;
26 0         0 %args = @_;
27 0         0 $args{interface} = $interface;
28             }else{
29 1         4 %args = @_;
30             }
31              
32 1         2 my $self = bless(\%args, $class);
33            
34 1         7 $self->{class} = $class;
35 1 50       24 $self->{ipv} = 4 unless( $self->{ipv} );
36              
37 1         7 $ipv = $self->_mkipv();
38              
39 1         3 return($self);
40             }
41              
42             sub public {
43 0     0 1 0 my $self = shift;
44              
45 0 0       0 unless( $self->{ipv} == 4 ){
46 0         0 warn "public method not supported on IPv $self->{ipv}\n";
47 0         0 return;
48             }
49              
50 0         0 my $sock = IO::Socket::INET->new(Proto => 'tcp',
51             PeerAddr => 'www.dnsbyweb.com',
52             PeerPort => 80,
53             Timeout => 3);
54            
55 0         0 my $platform = ucfirst($^O);
56 0         0 my $public;
57 0         0 eval {
58 0     0   0 local $SIG{ALRM} = sub { die "timeout during GET\n" };
  0         0  
59 0         0 alarm(3);
60 0         0 print $sock "GET /mip.mpl HTTP/1.1\r\n",
61             "Host: www.dnsbyweb.com\r\n",
62             "User-Agent: Sys::HostAddr/$VERSION (compatible; ${platform}; Perl $])\r\n",
63             "Accept: text/html; q=0.5, text/plain\r\n",
64             "Connection: close\r\n",
65             "\r\n";
66              
67 0         0 my $dh; # done header
68 0         0 while(<$sock>){
69 0 0       0 if( /^\r\n$/ ){
70 0         0 $dh=1;
71 0         0 next;
72             }
73 0 0       0 next unless $dh;
74              
75 0 0       0 if(/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
76 0         0 $public = $1;
77 0         0 last;
78             }
79             }
80 0         0 close $sock;
81 0         0 alarm(0);
82             };
83 0         0 alarm(0);
84 0 0       0 warn $@ if $@;
85              
86 0         0 return( $public );
87             }
88              
89             sub interfaces {
90 1     1 1 1301 my $self = shift;
91              
92 1         5 my $cfg_aref = $self->ifconfig();
93 1         13 my @interfaces;
94 1         8 for (@{$cfg_aref}){
  1         19  
95 18 50 66     305 if(/^\s+Description[\s\.]+:\s+([^\r\n]+)/){
    100 66        
96 0         0 push @interfaces, $1;
97             }elsif(/^([a-z0-9]+(?::[0-9]+)?):?\s+/ && $^O ne 'MSWin32' && $^O ne 'cygwin'){
98 2         22 push @interfaces, $1;
99             }
100             }
101 1         22 return( \@interfaces );
102             }
103              
104             sub addresses {
105 1     1 1 1351 my $self = shift;
106 1   33     17 my $getint = shift || $self->{interface};
107              
108 1         4 my $cfg_aref = $self->ifconfig( $getint );
109 1         26 my @addrs;
110 1         13 for (@{$cfg_aref}){
  1         9  
111 18 100       573 if(/^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
    50          
    50          
    50          
112 2         20 push @addrs, $1; # unix
113             }elsif(/^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
114 0         0 push @addrs, $1; # win7
115             }elsif(/^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
116 0 0       0 push @addrs, $1 if($self->{ipv} eq '4'); # winxp ipv4
117             }elsif(/^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
118 0 0       0 push @addrs, $1 if($self->{ipv} eq '6'); # winxp ipv6
119             }
120             }
121 1         35 return( \@addrs );
122             }
123              
124             sub ip {
125 1     1 1 1503 my $self = shift;
126 1   33     16 my $getint = shift || $self->{interface};
127              
128 1         10 my $cfg_aref = $self->ifconfig( $getint );
129 1         9 my %data;
130 1         5 my ($interface,$addr,$netmask);
131 1         10 for my $line (@{$cfg_aref}){
  1         8  
132 18 100 66     382 if($line =~ /^([a-z0-9]+(?::[0-9]+)?):?\s+/ && $^O ne 'MSWin32' && $^O ne 'cygwin'){
    100 66        
    50          
    50          
    50          
    50          
    50          
133 2         13 $interface = $1;
134             }elsif($line =~ /^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
135 2         27 my $addr = $1;
136 2 50 0     38 if($line =~ /netmask\s+(?:0x)?([a-f0-9]{8})\s/){
    50          
    50          
    0          
137 0         0 my $hexed = $1;
138 0         0 my @hnm = $hexed =~ /^(..)(..)(..)(..)$/;
139 0         0 $netmask = join('.', map { hex $_ } @hnm);
  0         0  
140             }elsif($line =~ /netmask\s+(\S+)/){
141 0         0 $netmask = $1;
142             }elsif($line =~ /Mask:(\S+)/){
143 2         5 $netmask = $1;
144             }elsif($self->{ipv} eq '6' && $line =~ m#(/\d{1,3})$#){
145 0         0 $netmask = $1;
146             }else{
147 0         0 die "unknown netmask for $addr on $interface\n";
148             }
149 2         6 push @{$data{$interface}}, { address => $addr, netmask => $netmask };
  2         27  
150             }elsif($line =~ /^\s+Description[\s\.]+:\s([^\r\n]+)/){
151 0         0 $interface = $1;
152             }elsif($line =~ /^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
153 0         0 $addr = $1; # win7
154             }elsif($line =~ /^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
155 0 0       0 $addr = $1 if($self->{ipv} eq '4'); # winXP IPv4
156             }elsif($line =~ /^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
157 0 0       0 $addr = $1 if($self->{ipv} eq '6'); # winXP IPv6
158             }elsif($line =~ /^\s+Subnet Mask[\s\.]+:\s+(\S+)/){
159 0         0 $netmask = $1;
160             #this handles multiple ip addrs on same interface (tested on XP, anyway)
161 0         0 push @{$data{$interface}}, { address => $addr, netmask => $netmask };
  0         0  
162             }
163             }
164 1         15 return \%data;
165             }
166              
167             sub first_ip {
168 1     1 1 270 my $self = shift;
169 1   33     16 my $getint = shift || $self->{interface};
170              
171 1         10 my $cfg_aref = $self->ifconfig( $getint );
172              
173 1         8 for (@{$cfg_aref}){
  1         13  
174 2         75 my $addr;
175 2 100       604 if(/^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
    50          
    50          
    50          
176 1         11 $addr = $1; # unix
177             }elsif(/^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
178 0         0 $addr = $1; # windows 7 win32
179             }elsif(/^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
180 0 0       0 $addr = $1 if($self->{ipv} eq '4'); # winxp ipv4
181             }elsif(/^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
182 0 0       0 $addr = $1 if($self->{ipv} eq '6'); # winxp ipv6
183             }
184 2 100       23 if($addr){
185 1 50       24 next if($addr =~ /^(?:127\.|::1)/); # never say ln is first
186 1         20 return( $addr );
187             }
188             }
189              
190 0         0 die "couldnt find first $ipv IP Address\n";
191             }
192              
193             sub ifconfig {
194 4     4 0 15 my $self = shift;
195 4   33     28 my $getint = shift || $self->{interface};
196              
197 4         7 my ($cmd,$param);
198 4 50 33     39 if($^O eq 'MSWin32' || $^O eq 'cygwin'){
199 0         0 $cmd = 'ipconfig';
200 0         0 $param = '/all';
201             }else{
202 4         9 $cmd = 'ifconfig';
203 4   50     30 $param = $getint || '-a';
204 4 50 33     19 $param .= ' inet6' if($self->{ipv} eq '6' && $^O eq 'solaris');
205             }
206 4         14 my @config = $self->_get_stdout($cmd, $param);
207            
208 4         78 return( \@config );
209             }
210              
211             sub main_ip {
212 1     1 1 354 my $self = shift;
213 1   50     6 my $method = shift || 'auto';
214              
215 1 0 0     3 if( $method eq 'preferred' && ($^O ne 'MSWin32' && $^O ne 'cygwin') ){
      33        
216 0         0 die "'preferred' method to main_ip available on MSWin32/cygwin only.\n";
217             }
218 1 50       7 unless($method =~ /^(?:dns|route|preferred|auto)$/){
219 0         0 die "invalid method given to main_ip\n";
220             }
221            
222 1 50 33     8 if($method eq 'dns' || $method eq 'auto'){
223 1         1 my $addr;
224 1         6 my $hostname = hostname();
225 1         16 $self->_debug( "attempting hostname lookup in main_ip: $hostname" );
226 1         2 eval {
227 1     0   31 local $SIG{ALRM} = sub { die "timeout on $hostname\n" };
  0         0  
228 1         13 alarm(3);
229 1         789 my @x = ( gethostbyname($hostname) )[4];
230 1         7 alarm(0);
231            
232 1 50       5 verbose( "multiple ip addrs found for $hostname" ) if(@x > 1);
233 1         25 $addr = join( '.', unpack('C4', $x[0]) );
234             };
235 1         6 alarm(0);
236 1 50       4 if($@){
237 0         0 $self->_warn($@);
238             }
239 1 50       4 if( $addr ){
240 1 50       593 return $addr unless($addr =~ /^(?:127\.|::1)/); # never say lo is main
241             }
242 0         0 $self->_debug( "DNS lookup did not yield an IP addr." );
243             }
244              
245 0 0 0     0 if($method eq 'route' || $method eq 'auto'){
246             # if dns method failed us, check for default route, find local ip
247             # addr(s) in same subnet -"first" one listed will be called "main"
248            
249 0         0 my ($cmd,$param);
250 0 0       0 if($^O eq 'solaris'){
251 0         0 $cmd = 'route';
252 0         0 $param = 'get 0.0.0.0';
253             }else{
254 0         0 $cmd = 'netstat'; # works with MSWin32, too
255 0         0 $param = '-nr';
256             }
257            
258 0         0 my @data = $self->_get_stdout($cmd, $param);
259 0         0 for my $line (@data){
260 0         0 chomp $line;
261 0 0       0 if($line =~ /^\s+0\.0\.0\.0\s+0\.0\.0\.0\s+\S+\s+(\S+)\s+/){
    0          
    0          
262 0         0 return( $1 ); # mswin32
263             }elsif($line =~ /^(?:0\.0\.0\.0|default)\s.*\s(\S+)$/){
264             # 0.0.0.0 = debian linux, default = freebsd
265 0         0 return( $self->first_ip($1) );
266             }elsif($line =~ /^\s+interface:\s+(\S+)$/){
267 0         0 return( $self->first_ip($1) ); # solaris
268             }
269             }
270             }
271              
272 0 0 0     0 if($^O eq 'MSWin32' || $^O eq 'cygwin'){
273 0 0 0     0 if($method eq 'preferred' || $method eq 'auto'){
274 0         0 my $cfg_aref = $self->ifconfig();
275 0         0 foreach (@{$cfg_aref}){
  0         0  
276 0 0       0 if(/^\s+${ipv}[\s\.]+:\s+(\S+)\(Preferred\)/){
277 0         0 return($1);
278             }
279             }
280             }
281             }
282            
283 0         0 die "could not determine main ip address\n"; # we dont pick one at random
284             }
285              
286             sub _mkipv {
287 1     1   2 my $self = shift;
288              
289 1 50 33     23 return ( ($^O eq 'MSWin32' || $^O eq 'cygwin') && $self->{ipv} eq '6' ) ? 'IPv6 Address' :
    50 33        
    50          
290             ($^O eq 'MSWin32' || $^O eq 'cygwin') ? 'IPv4 Address' :
291             ($self->{ipv} eq '6') ? 'inet6' :
292             'inet';
293             }
294              
295             sub _get_stdout {
296 4     4   7 my $self = shift;
297 4   50     12 my $cmd = shift || die "get_stdout syntax error1\n";
298 4         19 my $params = join(' ', @_);
299              
300 4         27 $self->_debug( "running cmd: [$cmd] params: [$params]" );
301              
302 4 50       24269 open(my $fh, "$cmd $params |") || die "cannot fork $cmd: $!\n"; # -| is 5.8+
303 4         2229 my @data = <$fh>;
304 4         166 close $fh;
305              
306 4         316 return( @data );
307             }
308              
309             sub _warn {
310 0     0   0 my $self = shift;
311 0         0 my $msg = join('', @_);
312              
313 0         0 warn "$self->{class}: $msg\n";
314             }
315              
316             sub _debug {
317 5     5   10 my $self = shift;
318              
319 5 50       20 $self->_warn(@_) if($self->{debug});
320             }
321              
322              
323             1;
324              
325             __END__