File Coverage

blib/lib/Sys/HostIP.pm
Criterion Covered Total %
statement 98 108 90.7
branch 32 46 69.5
condition 15 18 83.3
subroutine 17 18 94.4
pod 5 6 83.3
total 167 196 85.2


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