File Coverage

blib/lib/Sys/HostIP.pm
Criterion Covered Total %
statement 103 111 92.7
branch 37 50 74.0
condition 15 18 83.3
subroutine 17 18 94.4
pod 5 6 83.3
total 177 203 87.1


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