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