File Coverage

blib/lib/Sys/HostAddr.pm
Criterion Covered Total %
statement 104 182 57.1
branch 43 112 38.3
condition 21 63 33.3
subroutine 14 18 77.7
pod 6 8 75.0
total 188 383 49.0


line stmt bran cond sub pod time code
1             # Sys::HostAddr.pm
2             # $Id: HostAddr.pm,v 0.992 2014/04/28 21:34:52 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   28657 use strict;
  1         2  
  1         50  
9 1     1   6 use warnings;
  1         2  
  1         39  
10 1     1   4852 use IO::Socket::INET;
  1         36776  
  1         11  
11 1     1   13181 use Sys::Hostname;
  1         1718  
  1         3913  
12              
13             our ($VERSION) = q$Revision: 0.992 $ =~ /(\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 13 my $class = shift;
23 1         2 my %args;
24 1 50       5 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         4 my $self = bless(\%args, $class);
33            
34 1         8 $self->{class} = $class;
35 1 50       6 $self->{ipv} = 4 unless( $self->{ipv} );
36              
37 1         6 $ipv = $self->_mkipv();
38              
39 1         4 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; MSIE 8.0; ${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 while(<$sock>){
68 0 0       0 if(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
69 0         0 $public = $1;
70 0         0 last;
71             }
72             }
73 0         0 close $sock;
74 0         0 alarm(0);
75             };
76 0         0 alarm(0);
77 0 0       0 warn $@ if $@;
78              
79 0         0 return( $public );
80             }
81              
82             sub interfaces {
83 1     1 1 1158 my $self = shift;
84              
85 1         8 my $cfg_aref = $self->ifconfig();
86 1         9 my @interfaces;
87 1         10 for (@{$cfg_aref}){
  1         14  
88 18 50 66     139 if(/^\s+Description[\s\.]+:\s+([^\r\n]+)/){
    100 66        
89 0         0 push @interfaces, $1;
90             }elsif(/^([a-z0-9]+(?::[0-9]+)?):?\s+/ && $^O ne 'MSWin32' && $^O ne 'cygwin'){
91 2         15 push @interfaces, $1;
92             }
93             }
94 1         18 return( \@interfaces );
95             }
96              
97             sub addresses {
98 1     1 1 1785 my $self = shift;
99 1   33     12 my $getint = shift || $self->{interface};
100              
101 1         4 my $cfg_aref = $self->ifconfig( $getint );
102 1         17 my @addrs;
103 1         9 for (@{$cfg_aref}){
  1         9  
104 18 100       246 if(/^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
    50          
    50          
    50          
105 2         18 push @addrs, $1; # unix
106             }elsif(/^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
107 0         0 push @addrs, $1; # win7
108             }elsif(/^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
109 0 0       0 push @addrs, $1 if($self->{ipv} eq '4'); # winxp ipv4
110             }elsif(/^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
111 0 0       0 push @addrs, $1 if($self->{ipv} eq '6'); # winxp ipv6
112             }
113             }
114 1         24 return( \@addrs );
115             }
116              
117             sub ip {
118 1     1 1 695 my $self = shift;
119 1   33     18 my $getint = shift || $self->{interface};
120              
121 1         7 my $cfg_aref = $self->ifconfig( $getint );
122 1         13 my %data;
123 1         8 my ($interface,$addr,$netmask);
124 1         7 for my $line (@{$cfg_aref}){
  1         10  
125 18 100 66     358 if($line =~ /^([a-z0-9]+(?::[0-9]+)?):?\s+/ && $^O ne 'MSWin32' && $^O ne 'cygwin'){
    100 66        
    50          
    50          
    50          
    50          
    50          
126 2         20 $interface = $1;
127             }elsif($line =~ /^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
128 2         8 my $addr = $1;
129 2 50 0     37 if($line =~ /netmask\s+(?:0x)?([a-f0-9]{8})\s/){
    50          
    50          
    0          
130 0         0 my $hexed = $1;
131 0         0 my @hnm = $hexed =~ /^(..)(..)(..)(..)$/;
132 0         0 $netmask = join('.', map { hex $_ } @hnm);
  0         0  
133             }elsif($line =~ /netmask\s+(\S+)/){
134 0         0 $netmask = $1;
135             }elsif($line =~ /Mask:(\S+)/){
136 2         11 $netmask = $1;
137             }elsif($self->{ipv} eq '6' && $line =~ m#(/\d{1,3})$#){
138 0         0 $netmask = $1;
139             }else{
140 0         0 die "unknown netmask for $addr on $interface\n";
141             }
142 2         3 push @{$data{$interface}}, { address => $addr, netmask => $netmask };
  2         31  
143             }elsif($line =~ /^\s+Description[\s\.]+:\s([^\r\n]+)/){
144 0         0 $interface = $1;
145             }elsif($line =~ /^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
146 0         0 $addr = $1; # win7
147             }elsif($line =~ /^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
148 0 0       0 $addr = $1 if($self->{ipv} eq '4'); # winXP IPv4
149             }elsif($line =~ /^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
150 0 0       0 $addr = $1 if($self->{ipv} eq '6'); # winXP IPv6
151             }elsif($line =~ /^\s+Subnet Mask[\s\.]+:\s+(\S+)/){
152 0         0 $netmask = $1;
153             #this handles multiple ip addrs on same interface (tested on XP, anyway)
154 0         0 push @{$data{$interface}}, { address => $addr, netmask => $netmask };
  0         0  
155             }
156             }
157 1         20 return \%data;
158             }
159              
160             sub first_ip {
161 1     1 1 286 my $self = shift;
162 1   33     8 my $getint = shift || $self->{interface};
163              
164 1         14 my $cfg_aref = $self->ifconfig( $getint );
165              
166 1         7 for (@{$cfg_aref}){
  1         12  
167 2         73 my $addr;
168 2 100       175 if(/^\s+${ipv}\s+(?:addr:)?(\S+)\s/){
    50          
    50          
    50          
169 1         15 $addr = $1; # unix
170             }elsif(/^\s+${ipv}[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
171 0         0 $addr = $1; # windows 7 win32
172             }elsif(/^\s+IP Address[\s\.]+:\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/){
173 0 0       0 $addr = $1 if($self->{ipv} eq '4'); # winxp ipv4
174             }elsif(/^\s+IP Address[\s\.]+:\s+([a-f0-9:\.]{3,40})/){
175 0 0       0 $addr = $1 if($self->{ipv} eq '6'); # winxp ipv6
176             }
177 2 100       12 if($addr){
178 1 50       17 next if($addr =~ /^(?:127\.|::1)/); # never say ln is first
179 1         19 return( $addr );
180             }
181             }
182              
183 0         0 die "couldnt find first $ipv IP Address\n";
184             }
185              
186             sub ifconfig {
187 4     4 0 9 my $self = shift;
188 4   33     46 my $getint = shift || $self->{interface};
189              
190 4         6 my ($cmd,$param);
191 4 50 33     44 if($^O eq 'MSWin32' || $^O eq 'cygwin'){
192 0         0 $cmd = 'ipconfig';
193 0         0 $param = '/all';
194             }else{
195 4         8 $cmd = 'ifconfig';
196 4   50     19 $param = $getint || '-a';
197 4 50 33     26 $param .= ' inet6' if($self->{ipv} eq '6' && $^O eq 'solaris');
198             }
199 4         18 my @config = $self->_get_stdout($cmd, $param);
200            
201 4         59 return( \@config );
202             }
203              
204             sub main_ip {
205 1     1 1 615 my $self = shift;
206 1   50     6 my $method = shift || 'auto';
207              
208 1 0 0     4 if( $method eq 'preferred' && ($^O ne 'MSWin32' && $^O ne 'cygwin') ){
      33        
209 0         0 die "'preferred' method to main_ip available on MSWin32/cygwin only.\n";
210             }
211 1 50       7 unless($method =~ /^(?:dns|route|preferred|auto)$/){
212 0         0 die "invalid method given to main_ip\n";
213             }
214            
215 1 50 33     9 if($method eq 'dns' || $method eq 'auto'){
216 1         3 my $addr;
217 1         7 my $hostname = hostname();
218 1         18 $self->_debug( "attempting hostname lookup in main_ip: $hostname" );
219 1         1 eval {
220 1     0   37 local $SIG{ALRM} = sub { die "timeout on $hostname\n" };
  0         0  
221 1         12 alarm(3);
222 1         755 my @x = ( gethostbyname($hostname) )[4];
223 1         7 alarm(0);
224            
225 1 50       5 verbose( "multiple ip addrs found for $hostname" ) if(@x > 1);
226 1         23 $addr = join( '.', unpack('C4', $x[0]) );
227             };
228 1         5 alarm(0);
229 1 50       5 if($@){
230 0         0 $self->_warn($@);
231             }
232 1 50       3 if( $addr ){
233 1 50       10 return $addr unless($addr =~ /^(?:127\.|::1)/); # never say lo is main
234             }
235 0         0 $self->_debug( "DNS lookup did not yield an IP addr." );
236             }
237              
238 0 0 0     0 if($method eq 'route' || $method eq 'auto'){
239             # if dns method failed us, check for default route, find local ip
240             # addr(s) in same subnet -"first" one listed will be called "main"
241            
242 0         0 my ($cmd,$param);
243 0 0       0 if($^O eq 'solaris'){
244 0         0 $cmd = 'route';
245 0         0 $param = 'get 0.0.0.0';
246             }else{
247 0         0 $cmd = 'netstat'; # works with MSWin32, too
248 0         0 $param = '-nr';
249             }
250            
251 0         0 my @data = $self->_get_stdout($cmd, $param);
252 0         0 for my $line (@data){
253 0         0 chomp $line;
254 0 0       0 if($line =~ /^\s+0\.0\.0\.0\s+0\.0\.0\.0\s+\S+\s+(\S+)\s+/){
    0          
    0          
255 0         0 return( $1 ); # mswin32
256             }elsif($line =~ /^(?:0\.0\.0\.0|default)\s.*\s(\S+)$/){
257             # 0.0.0.0 = debian linux, default = freebsd
258 0         0 return( $self->first_ip($1) );
259             }elsif($line =~ /^\s+interface:\s+(\S+)$/){
260 0         0 return( $self->first_ip($1) ); # solaris
261             }
262             }
263             }
264              
265 0 0 0     0 if($^O eq 'MSWin32' || $^O eq 'cygwin'){
266 0 0 0     0 if($method eq 'preferred' || $method eq 'auto'){
267 0         0 my $cfg_aref = $self->ifconfig();
268 0         0 foreach (@{$cfg_aref}){
  0         0  
269 0 0       0 if(/^\s+${ipv}[\s\.]+:\s+(\S+)\(Preferred\)/){
270 0         0 return($1);
271             }
272             }
273             }
274             }
275            
276 0         0 die "could not determine main ip address\n"; # we dont pick one at random
277             }
278              
279             sub _mkipv {
280 1     1   3 my $self = shift;
281              
282 1 50 33     28 return ( ($^O eq 'MSWin32' || $^O eq 'cygwin') && $self->{ipv} eq '6' ) ? 'IPv6 Address' :
    50 33        
    50          
283             ($^O eq 'MSWin32' || $^O eq 'cygwin') ? 'IPv4 Address' :
284             ($self->{ipv} eq '6') ? 'inet6' :
285             'inet';
286             }
287              
288             sub _get_stdout {
289 4     4   12 my $self = shift;
290 4   50     12 my $cmd = shift || die "get_stdout syntax error1\n";
291 4         11 my $params = join(' ', @_);
292              
293 4         22 $self->_debug( "running cmd: [$cmd] params: [$params]" );
294              
295 4 50       43078 open(my $fh, "$cmd $params |") || die "cannot fork $cmd: $!\n"; # -| is 5.8+
296 4         6833 my @data = <$fh>;
297 4         185 close $fh;
298              
299 4         266 return( @data );
300             }
301              
302             sub _warn {
303 0     0   0 my $self = shift;
304 0         0 my $msg = join('', @_);
305              
306 0         0 warn "$self->{class}: $msg\n";
307             }
308              
309             sub _debug {
310 5     5   8 my $self = shift;
311              
312 5 50       22 $self->_warn(@_) if($self->{debug});
313             }
314              
315              
316             1;
317              
318             __END__