File Coverage

blib/lib/Sys/HostIP.pm
Criterion Covered Total %
statement 87 111 78.3
branch 22 46 47.8
condition 10 21 47.6
subroutine 17 18 94.4
pod 5 6 83.3
total 141 202 69.8


line stmt bran cond sub pod time code
1 3     3   52511 use strict;
  3         4  
  3         86  
2 3     3   12 use warnings;
  3         5  
  3         149  
3             package Sys::HostIP;
4             # ABSTRACT: Try extra hard to get IP address related info
5             $Sys::HostIP::VERSION = '2.000';
6 3     3   13 use Carp;
  3         3  
  3         241  
7 3     3   12 use Exporter;
  3         6  
  3         113  
8 3     3   11 use File::Basename 'dirname';
  3         4  
  3         216  
9 3     3   14 use vars qw( @ISA @EXPORT_OK );
  3         4  
  3         3913  
10              
11             @ISA = qw(Exporter);
12             @EXPORT_OK = qw( ip ips interfaces ifconfig );
13              
14             my $is_win = $^O =~ qr/(MSWin32|cygwin)/;
15              
16             sub new {
17 3   33 3 0 1778 my $class = shift || croak 'Cannot create new method in a functional way';
18 3         8 my %opts = @_;
19 3         11 my $self = bless {%opts}, $class;
20              
21             # only get ifconfig binary if it's not a windows
22 3 50 33     40 $self->{'ifconfig'} ||= $is_win ? '' : $self->_get_ifconfig_binary;
23 3   66     1126 $self->{'if_info'} ||= $self->_get_interface_info;
24              
25 3         24 return $self;
26             }
27              
28             sub ifconfig {
29 6     6 1 644 my $self = shift;
30 6         9 my $path = shift;
31              
32 6 100       18 if ( ! ref $self ) {
33 2         48 return $self->_get_ifconfig_binary;
34             }
35              
36             # set path
37 4 100       12 $path and $self->{'ifconfig'} = $path;
38              
39 4         13 return $self->{'ifconfig'};
40             }
41              
42             sub ip {
43 2   100 2 1 21 my $self = shift || 'Sys::HostIP';
44 2         4 my $if_info;
45              
46 2 100       7 if ( ! ref $self ) {
47 1         5 $if_info = $self->_get_interface_info;
48             } else {
49 1         4 $if_info = $self->if_info;
50             }
51              
52 2 50       13 if ($is_win) {
53 0         0 foreach my $key ( sort keys %{$if_info} ) {
  0         0  
54             # should this be the default?
55 0 0       0 if ( $key =~ /Local Area Connection/ ) {
56 0         0 return ( $if_info->{$key} );
57             }
58             }
59             } else {
60 2         63 my $lo_found;
61              
62 2         5 foreach my $key ( sort keys %{$if_info} ) {
  2         9  
63             # we don't want the loopback
64 0 0       0 if ( $if_info->{$key} eq '127.0.0.1' ) {
65 0         0 $lo_found++;
66 0         0 next;
67             }
68              
69             # now we return the first one that comes up
70 0         0 return ( $if_info->{$key} );
71             }
72              
73             # we get here if loopback is the only active device
74 2 50       10 $lo_found and return '127.0.0.1';
75              
76 2         13 return;
77             }
78             }
79              
80             sub ips {
81 1   50 1 1 2133 my $self = shift || 'Sys::HostIP';
82              
83 1 50       6 if ( ! ref $self ) {
84 0         0 return [ values %{ $self->_get_interface_info } ];
  0         0  
85             }
86              
87 1         3 return [ values %{ $self->if_info } ];
  1         3  
88             }
89              
90             sub interfaces {
91 1   50 1 1 1431 my $self = shift || 'Sys::HostIP';
92              
93 1 50       6 if ( ! ref $self ) {
94 0         0 return $self->_get_interface_info;
95             }
96              
97 1         4 return $self->if_info;
98             }
99              
100             sub if_info {
101 3     3 1 5 my $self = shift;
102              
103 3 50       6 if ( ! ref $self ) {
104 0         0 return $self->_get_ifconfig_binary;
105             }
106              
107 3         8 return $self->{'if_info'};
108             }
109              
110             sub _get_ifconfig_binary {
111 3     3   4 my $self = shift;
112 3         5 my $ifconfig = '/sbin/ifconfig -a';
113              
114 3 50       31 if ( $^O =~ /(?: linux|openbsd|freebsd|netbsd|solaris|darwin )/xi ) {
    0          
    0          
    0          
115 3         5 $ifconfig = '/sbin/ifconfig -a';
116             } elsif ( $^O eq 'aix' ) {
117 0         0 $ifconfig = '/usr/sbin/ifconfig -a';
118             } elsif ( $^O eq 'irix' ) {
119 0         0 $ifconfig = '/usr/etc/ifconfig';
120             } elsif ( $^O eq 'dec_osf' ) {
121 0         0 $ifconfig = '/sbin/ifconfig';
122             } else {
123 0         0 carp "Unknown system ($^O), guessing ifconfig is in /sbin/ifconfig " .
124             "(email xsawyerx\@cpan.org with the location of your ifconfig)\n";
125             }
126              
127 3         11 return $ifconfig;
128             }
129              
130             sub _get_interface_info {
131 3     3   5 my $self = shift;
132 3 50       11 my $if_info = $is_win ?
133             $self->_get_win32_interface_info() :
134             $self->_get_unix_interface_info();
135             }
136              
137             sub _clean_ifconfig_env {
138 3     3   3 my $self = shift;
139             # this is an attempt to fix tainting problems
140              
141             # removing $BASH_ENV, which exists if /bin/sh is your bash
142 3         13 delete $ENV{'BASH_ENV'};
143              
144             # now we set the local $ENV{'PATH'} to be only the path to ifconfig
145 3         8 my $ifconfig = $self->ifconfig;
146 3         237 $ENV{'PATH'} = dirname $ifconfig;
147              
148 3         6 return $ifconfig;
149             }
150              
151             sub _get_unix_interface_info {
152 3     3   6 my $self = shift;
153              
154             # localize the environment
155 3         16 local %ENV;
156              
157             # make sure nothing else has touched $/
158 3         8 local $/ = "\n";
159              
160 3         4 my ( $ip, $interface, %if_info );
161              
162             # clean environment for taint mode
163 3         9 my $ifconfig_bin = $self->_clean_ifconfig_env();
164 3         5053 my @ifconfig = `$ifconfig_bin`;
165              
166 3         36 foreach my $line (@ifconfig) {
167             # TODO: refactor this into tests
168             # output from 'ifconfig -a' looks something like this on every *nix i
169             # could get my hand on except linux (this one's actually from OpenBSD):
170             #
171             # gershiwin:~# /sbin/ifconfig -a
172             # lo0: flags=8009
173             # inet 127.0.0.1 netmask 0xff000000
174             # lo1: flags=8008
175             # xl0: flags=8843
176             # media: Ethernet autoselect (100baseTX full-duplex)
177             # status: active
178             # inet 10.0.0.2 netmask 0xfffffff0 broadcast 10.0.0.255
179             # sl0: flags=c010
180             # sl1: flags=c010
181             #
182             # in linux it's a little bit different:
183             #
184             # [jschatz@nooky Sys-IP]$ /sbin/ifconfig
185             # eth0 Link encap:Ethernet HWaddr 00:C0:4F:60:6F:C2
186             # inet addr:10.0.3.82 Bcast:10.0.255.255 Mask:255.255.0.0
187             # UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
188             # Interrupt:19 Base address:0xec00
189             # lo Link encap:Local Loopback
190             # inet addr:127.0.0.1 Mask:255.0.0.0
191             # UP LOOPBACK RUNNING MTU:3924 Metric:1
192             #
193             # so the regexen involved here have to deal with the following: 1)
194             # there's no ':' after an interface's name in linux 2) in linux, it's
195             # "inet addr:127.0.0.1" instead of "inet 127.0.0.1" hence the somewhat
196             # hairy regexen /(^\w+(?:\d)?(?:\:\d)?)/ (which also handles aliased ip
197             # addresses , ie eth0:1) and /inet(?:addr\:)?(\d+\.\d+\.\d+\.\d+)/
198             #
199             # so we parse through the list returned. if the line starts with some
200             # letters followed (possibly) by an number and a colon, then we've got an
201             # interface. if the line starts with a space, then it's the info from the
202             # interface that we just found, and we stick the contents into %if_info
203 0 0 0     0 if ( ($line =~/^\s+/) && ($interface) ) {
    0          
204 0         0 $if_info{$interface} .= $line;
205             }
206             # FIXME: refactor this regex
207             elsif (($interface) = ($line =~/(^\w+(?:\d)?(?:\.\d+)?(?:\:\d+)?)/)) {
208 0         0 $line =~s/\w+\d(\:)?\s+//;
209 0         0 $if_info{$interface} = $line;
210             }
211             }
212              
213 3         16 foreach my $key (keys %if_info) {
214             # now we want to get rid of all the other crap in the ifconfig
215             # output. we just want the ip address. perhaps a future version can
216             # return even more useful results (netmask, etc).....
217 0 0       0 if (my ($ip) = ($if_info{$key} =~/inet (?:addr\:)?(\d+(?:\.\d+){3})/)) {
218 0         0 $if_info{$key} = $ip;
219             }
220             else {
221             # ok, no ip address here, which means this interface isn't
222             # active. some os's (openbsd for instance) spit out ifconfig info for
223             # inactive devices. this is pretty much worthless for us, so we
224             # delete it from the hash
225 0         0 delete $if_info{$key};
226             }
227             }
228              
229             # now we do some cleanup by deleting keys that have no associated info
230             # (some os's like openbsd list inactive interfaces when 'ifconfig -a' is
231             # used, and we don't care about those
232 3         181 return \%if_info;
233             }
234              
235             sub _run_ipconfig {
236 0     0   0 return `ipconfig`;
237             }
238              
239             sub _get_win32_interface_info {
240 10     10   3962 my $self = shift;
241 10         56 my %regexes = (
242             address => qr/
243             \s+
244             IP(?:v4)? .*? :
245             \s+
246             (\d+ (?: \. \d+ ){3} )
247             /x,
248              
249             adapter => qr/
250             ^
251             (?:Ethernet(?:\s?|-)\w+|\w+\s+Ethernet)
252             \s+
253             (.*) :
254             /x,
255             );
256              
257 10         18 my @ipconfig = $self->_run_ipconfig();
258 10         4097 my ( $interface, %if_info );
259              
260 10         34 foreach my $line (@ipconfig) {
261 256         161 chomp($line);
262              
263 256 100 66     1053 if ( $line =~ /Windows/ ) {
    100          
    100          
    100          
264             # ignore the header
265 10         10 next;
266             } elsif ( $line =~/^\s$/ ) {
267 79         69 next;
268             } elsif ( ( $line =~ $regexes{'address'} ) and defined $interface ) {
269 14         29 $if_info{$interface} = $1;
270 14         12 $interface = undef;
271             } elsif ( $line =~ $regexes{'adapter'} ) {
272 16         26 $interface = $1;
273 16         10 chomp $interface;
274 16         36 $interface =~ s/\s+$//g; # remove trailing whitespace, if any
275             }
276             }
277              
278 10         54 return \%if_info;
279             }
280              
281             1;
282              
283             __END__