File Coverage

blib/lib/Net/Hacky/Detect/IP.pm
Criterion Covered Total %
statement 20 107 18.6
branch 0 46 0.0
condition 0 42 0.0
subroutine 7 13 53.8
pod 3 3 100.0
total 30 211 14.2


line stmt bran cond sub pod time code
1             package Net::Hacky::Detect::IP;
2              
3             # Base modules
4 1     1   55404 use 5.006;
  1         3  
5 1     1   5 use strict;
  1         2  
  1         25  
6 1     1   5 use warnings;
  1         1  
  1         45  
7              
8 1     1   623 use File::Temp qw( tempfile );
  1         17407  
  1         57  
9 1     1   640 use IO::Socket::IP;
  1         23123  
  1         6  
10 1     1   939 use Try::Tiny;
  1         1693  
  1         49  
11 1     1   428 use Capture::Tiny ':all';
  1         5135  
  1         1113  
12              
13             =head1 NAME
14              
15             Net::Hacky::Detect::IP - Hackily try different methods of attaining local system IPs
16              
17             =head1 VERSION
18              
19             Version 0.024
20              
21             =cut
22              
23             our $VERSION = '0.024';
24              
25             my $tools = {
26             unix => {
27             tools => [
28             [qw(netstat -an4)],
29             [qw(netstat -an6)],
30             [qw(ip addr show)],
31             [qw(ifconfig)],
32             [qw(sockstat -4)],
33             [qw(sockstat -6)],
34             [qw(arp -a)]
35             ],
36             paths => [qw(/bin/ /sbin/ /usr/sbin/ /usr/bin/)]
37             },
38             windows => {
39             tools => [
40             [qw(netstat -an)],
41             [qw(ipconfig)],
42             [qw(cscript)],
43             [qw(arp -a)]
44             ],
45             paths => [""]
46             }
47             };
48              
49             my $cscript = <<'EOF';
50              
51             On Error Resume Next
52             strComputer = "."
53             Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
54             Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_NetworkAdapterConfiguration")
55              
56             For Each objItem In colItems
57             For Each objValue In objItem.IPAddress
58             If objValue <> "" Then
59             WScript.Echo objValue
60             End If
61             Next
62             Next
63              
64             EOF
65              
66             =head1 SYNOPSIS
67              
68             use Net::Hacky::Detect::IP;
69             my $ip = Net::Hacky::Detect::IP->new();
70              
71             my @ips = @{ $ip->scan() };
72              
73             foreach my $host (@ips) {
74             print "Detected ip: $host\n";
75             if ($ip->checkIP($host)) {
76             print "IP is deffinetly usable! (Was tested twice as scan() auto checks)\n";
77             }
78             }
79              
80             =head1 DESCRIPTION
81              
82             Hackily concatenate output from multiple system commands then attempt to find valid ips from it, once found they are
83             tested for connectability then returned. This is not pretty nor very clever but extracting system ips is a nightmare
84             however you go about it and this method appears to be the more reliable.
85              
86             =head1 METHODS
87              
88             =head2 new
89              
90             Create a new object for interacting with Net-Hacky-Detect-IP
91              
92             =cut
93              
94             sub new {
95 0     0 1   my ($class) = @_;
96              
97             # Some private stuff for ourself
98 0           my $self = {
99             };
100              
101             # Go with god my son
102 0           bless $self, $class;
103 0           return $self;
104             }
105              
106              
107             =head2 scan
108              
109             Attempt to find local system ips, 1 optional argument, of '4' or '6' designating wether
110             to look for ipv4 or ipv6 addresses, if left blank search for both.
111              
112             Returns a list of local ips for the system, will return a blank [] list if nothing found.
113              
114             =cut
115              
116             sub scan {
117 0     0 1   my $self = shift;
118 0           my $type = shift;
119            
120 0 0 0       if (!$type || $type !~ m#^4|6$#) {
121 0           $type = 0;
122             }
123              
124 0           my $return = [];
125 0           my $os = 'unix';
126            
127 0 0         if ($^O =~ m#win#i) { $os = 'windows' }
  0            
128              
129             # Some short cuts and initial scalars for storing things in
130 0           my $dumps = "";
131 0           my $short = $tools->{$os};
132              
133             # Go searching for something we can use
134 0           foreach my $tool ( @{ $short->{tools} } ) {
  0            
135 0           my ($cmd,@args) = @{$tool};
  0            
136 0           foreach my $path ( @{ $short->{paths} } ) {
  0            
137             # Full path to the binary
138 0           my $fullpath = "$path$cmd";
139            
140             # If the arguments have 4 or 6 in them, there ipv4 or ipv6, we may not need one of them..
141 0 0 0       if ($args[0] && $type != 0) {
142 0           my ($flags) = $args[0] =~ m#(4|6)#;
143 0 0 0       if ($flags && $type !~ m#\Q$flags#) {
144 0           next;
145             }
146             }
147              
148             # Storage space for the execution returns
149 0           my ($merged, @result);
150              
151             # If this is a call for cscript, we need to act differently..
152 0 0         if ($cmd eq 'cscript') {
153             # Generate a path to a writable space
154 0           my $winScript = File::Temp::tempdir() . '\\' . 'findip.vbs';
155            
156             # Write our vbs to that location
157 0           open(my $fh,'>',$winScript);
158 0           print $fh $cscript;
159 0           close($fh);
160              
161             # Push the filepath into the arguments
162 0           push @args,$winScript;
163             }
164            
165             # If we are on unix we do not need to execute everything we can check the path exists.
166 0 0 0       next if ( $os eq 'unix' && !-e $fullpath );
167            
168             # Execute and collect;
169             try {
170 0     0     ($merged, @result) = capture_merged{ system($fullpath,@args) };
  0            
171 0           };
172              
173             # Execute and store output within the script
174 0           $dumps .= $merged ;
175             }
176             }
177              
178             # Check we found anything at all
179 0 0         if (length($dumps) < 10) { return [] }
  0            
180            
181             # Ok we did find something ...first extract remove all \n
182 0           ($dumps) =~ s#\n# #g;
183            
184             # Then convert into an array split into words
185 0           my @possibleIP = split(/\s+/,$dumps);
186            
187             # Make sure we only look at unique ips
188 0           my $unique;
189            
190             # Validate all the ips, for speed we will do a silly check first
191 0           foreach my $testIP (@possibleIP) {
192 0 0 0       if ( ($type == 4 || $type == 0) && $testIP =~ m#(\d+\.\d+\.\d+\.\d+)#) {
    0 0        
      0        
      0        
      0        
      0        
193             # Copy $1 to $IP because it looks prettier
194 0           my $IP = $1;
195            
196             # Check we have not already dealt with this ip and its valid
197 0 0         next if ($unique->{$IP});
198 0           $unique->{$IP} = 1;
199 0 0         if (!$self->_checkIP4($IP)) { next }
  0            
200              
201             # Push the valid ip into the return space
202 0           push @$return,$IP;
203             }
204             elsif ( ($type == 6 || $type == 0) && $testIP =~ m#:# && $testIP =~ m#[0-9]# && $testIP =~ m#^([a-f0-9:]+)$#) {
205             # Copy $1 to $IP because it looks prettier
206 0           my $IP = $1;
207              
208             # Check we have not already dealt with this ip and its valid
209 0 0         next if ($unique->{$IP});
210 0           $unique->{$IP} = 1;
211 0 0         if (!$self->_checkIP6($IP)) { next }
  0            
212              
213             # Push the valid ip into the return space
214 0           push @$return,$IP;
215             }
216             }
217 0           return $return;
218             }
219              
220             =head2 checkIP
221              
222             Check an IP is valid and usable, takes 1 mandatory argument;
223              
224             Argument 1 Should be an ip address (not hostname)
225              
226             Returns 1 on success 'valid' and 0 on failure 'invalid'
227              
228             =cut
229              
230             sub checkIP {
231 0     0 1   my ($self,$host) = @_;
232              
233 0 0         if ($host =~ m#\.#) {
    0          
234 0           return $self->_checkIP4($host);
235             }
236             elsif ($host =~ m#:#) {
237 0           return $self->_checkIP6($host);
238             }
239             else {
240 0           warn "$host does not look like an IPv6 nor IPv4, ignored.";
241             }
242             }
243              
244             sub _checkIP4 {
245 0     0     my ($self,$host) = @_;
246            
247             # A few safety checks
248 0 0         if (!$host) {
249 0           warn "Incorrect number of arguments, returning fail";
250 0           return 0;
251             }
252              
253             # By default fail the bind
254 0           my $bindsuccess = 0;
255            
256             # Split the ip into relevent blocks
257 0           my @ip = split(/\./,$host);
258              
259             # Do a more precise check (This should rule out all netmasks and broadcasts)
260 0 0 0       return 0 if ($ip[0] <= 0 || $ip[0] >= 255);
261 0 0 0       return 0 if ($ip[1] < 0 || $ip[1] > 255);
262 0 0 0       return 0 if ($ip[2] < 0 || $ip[2] > 255);
263 0 0 0       return 0 if ($ip[3] <= 0 || $ip[3] >= 255);
264            
265             # Bind port 0 'Select the first one availible'
266 0           my $port = 0;
267 0           my $sock = IO::Socket::IP->new(
268             Domain => PF_INET,
269             LocalAddr => $host,
270             LocalPort => $port,
271             Proto => 'tcp',
272             ReuseAddr => 1
273             );
274 0 0         if ($sock) {
275 0           $bindsuccess=1;
276             }
277              
278 0           return $bindsuccess;
279             }
280              
281             sub _checkIP6 {
282 0     0     my ($self,$host) = @_;
283            
284             # A few safety checks
285 0 0         if (!$host) {
286 0           warn "Incorrect number of arguments, returning fail";
287 0           return 0;
288             }
289            
290             # By default fail the bind
291 0           my $bindsuccess = 0;
292              
293             # Bind port 0 'Select the first one availible'
294 0           my $port = 0;
295 0           my $sock = IO::Socket::IP->new(
296             Domain => PF_INET6,
297             LocalAddr => $host,
298             LocalPort => $port,
299             Proto => 'tcp',
300             ReuseAddr => 1
301             );
302 0 0         if ($sock) {
303 0           $bindsuccess=1;
304             }
305              
306 0           return $bindsuccess;
307             }
308              
309              
310             =head1 AUTHOR
311              
312             Paul G Webster, C<< >>
313              
314             =head1 BUGS
315              
316             Please report any bugs or feature requests through the authors code repository at C
317              
318             =head1 SUPPORT
319              
320             You can find documentation for this module with the perldoc command.
321              
322             perldoc Net::Hacky::Detect::IP
323              
324              
325             You can also look for information at:
326              
327             =over 4
328              
329             =item * GitLab: The authors gitlab page for this project
330              
331             L
332              
333             =item * AnnoCPAN: Annotated CPAN documentation
334              
335             L
336              
337             =item * CPAN Ratings
338              
339             L
340              
341             =item * Search CPAN
342              
343             L
344              
345             =back
346              
347              
348             =head1 ACKNOWLEDGEMENTS
349              
350             Thank you for all the continued help from irc.freenode.net #perl and irc.perl.org #perl
351              
352             =head1 LICENSE AND COPYRIGHT
353              
354             Copyright 2017 Paul G Webster.
355              
356             This program is distributed under the (Simplified) BSD License:
357             L
358              
359             Redistribution and use in source and binary forms, with or without
360             modification, are permitted provided that the following conditions
361             are met:
362              
363             * Redistributions of source code must retain the above copyright
364             notice, this list of conditions and the following disclaimer.
365              
366             * Redistributions in binary form must reproduce the above copyright
367             notice, this list of conditions and the following disclaimer in the
368             documentation and/or other materials provided with the distribution.
369              
370             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
371             "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
372             LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
373             A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
374             OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
375             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
376             LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
377             DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
378             THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
379             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
380             OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
381              
382              
383             =cut
384              
385             1; # End of Net::Hacky::Detect::IP