File Coverage

blib/lib/Net/Hacky/Detect/IP.pm
Criterion Covered Total %
statement 20 108 18.5
branch 0 42 0.0
condition 0 36 0.0
subroutine 7 16 43.7
pod 6 6 100.0
total 33 208 15.8


line stmt bran cond sub pod time code
1             package Net::Hacky::Detect::IP;
2              
3             # Base modules
4 1     1   13751 use 5.006;
  1         4  
5 1     1   6 use strict;
  1         2  
  1         19  
6 1     1   4 use warnings;
  1         5  
  1         27  
7              
8 1     1   559 use File::Temp qw( tempfile );
  1         16595  
  1         54  
9 1     1   523 use IO::Socket::IP;
  1         20059  
  1         5  
10 1     1   969 use Try::Tiny;
  1         1638  
  1         48  
11 1     1   399 use Capture::Tiny ':all';
  1         4643  
  1         953  
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.022
20              
21             =cut
22              
23             our $VERSION = '0.022';
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             # Storage space for the execution returns
128 0           my ($merged, @result);
129              
130             # If this is a call for cscript, we need to act differently..
131 0 0         if ($cmd eq 'cscript') {
132             # Generate a path to a writable space
133 0           my $winScript = File::Temp::tempdir() . '\\' . 'findip.vbs';
134            
135             # Write our vbs to that location
136 0           open(my $fh,'>',$winScript);
137 0           print $fh $cscript;
138 0           close($fh);
139              
140             # Push the filepath into the arguments
141 0           push @args,$winScript;
142             }
143            
144             # If we are on unix we do not need to execute everything we can check the path exists.
145 0 0 0       next if ( $os eq 'unix' && !-e $fullpath );
146            
147             # Execute and collect;
148             try {
149 0     0     ($merged, @result) = capture_merged{ system($fullpath,@args) };
  0            
150 0           };
151              
152             # Execute and store output within the script
153 0           $dumps .= $merged ;
154             }
155             }
156              
157             # Check we found anything at all
158 0 0         if (length($dumps) < 10) { return [] }
  0            
159            
160             # Ok we did find something ...first extract remove all \n
161 0           ($dumps) =~ s#\n# #g;
162            
163             # Then convert into an array split into words
164 0           my @possibleIP = split(/\s+/,$dumps);
165            
166             # Make sure we only look at unique ips
167 0           my $unique;
168            
169             # Validate all the ips, for speed we will do a silly check first
170 0           foreach my $testIP (@possibleIP) {
171 0 0 0       if ( ($type == 4 || $type == 0) && $testIP =~ m#(\d+\.\d+\.\d+\.\d+)#) {
    0 0        
      0        
      0        
      0        
      0        
172             # Copy $1 to $IP because it looks prettier
173 0           my $IP = $1;
174            
175             # Check we have not already dealt with this ip and its valid
176 0 0         next if ($unique->{$IP});
177 0           $unique->{$IP} = 1;
178 0 0         if (!$self->_checkIP4($IP)) { next }
  0            
179              
180             # Push the valid ip into the return space
181 0           push @$return,$IP;
182             }
183             elsif ( ($type == 6 || $type == 0) && $testIP =~ m#:# && $testIP =~ m#[0-9]# && $testIP =~ m#^([a-f0-9:]+)$#) {
184             # Copy $1 to $IP because it looks prettier
185 0           my $IP = $1;
186              
187             # Check we have not already dealt with this ip and its valid
188 0 0         next if ($unique->{$IP});
189 0           $unique->{$IP} = 1;
190 0 0         if (!$self->_checkIP6($IP)) { next }
  0            
191              
192             # Push the valid ip into the return space
193 0           push @$return,$IP;
194             }
195             }
196 0           return $return;
197             }
198              
199             =head2 checkIP
200              
201             Check an IP is valid and usable, takes 1 mandatory argument;
202              
203             Argument 1 Should be an ip address (not hostname)
204              
205             Returns 1 on success 'valid' and 0 on failure 'invalid'
206              
207             =cut
208              
209             sub checkIP {
210 0     0 1   my ($self,$host) = @_;
211              
212 0 0         if ($host =~ m#\.#) {
    0          
213 0           return $self->_checkIP4($host);
214             }
215             elsif ($host =~ m#:#) {
216 0           return $self->_checkIP6($host);
217             }
218             else {
219 0           warn "$host does not look like an IPv6 nor IPv4, ignored.";
220             }
221             }
222              
223             =head1 UNINPLEMENTED
224              
225             These functions are not yet working.
226              
227             =head2 is_public
228              
229             Return 1 if an address is a publically routed address.
230              
231             Return 0 if it is not.
232              
233             =cut
234              
235             sub is_public {
236 0     0 1   my ($self,$ip) = @_;
237              
238            
239             }
240              
241             =head2 is_private
242              
243             Return 1 if an address is a privately routed 'LAN' address.
244              
245             Return 0 if it is not.
246              
247             =cut
248              
249             sub is_private {
250 0     0 1   my ($self,$ip) = @_;
251              
252            
253             }
254              
255             =head2 is_outbound
256              
257             Return 1 if an address can be bound to, to access the internet.
258              
259             Return 0 if it is not.
260              
261             =cut
262              
263             sub is_outbound {
264 0     0 1   my ($self,$host) = @_;
265            
266 0           my $return = 0;
267              
268              
269 0           return $return;
270             }
271              
272              
273             sub _checkIP4 {
274 0     0     my ($self,$host) = @_;
275            
276             # A few safety checks
277 0 0         if (!$host) {
278 0           warn "Incorrect number of arguments, returning fail";
279 0           return 0;
280             }
281              
282             # By default fail the bind
283 0           my $bindsuccess = 0;
284            
285             # Split the ip into relevent blocks
286 0           my @ip = split(/\./,$host);
287              
288             # Do a more precise check (This should rule out all netmasks and broadcasts)
289 0 0 0       return 0 if ($ip[0] <= 0 || $ip[0] >= 255);
290 0 0 0       return 0 if ($ip[1] < 0 || $ip[1] > 255);
291 0 0 0       return 0 if ($ip[2] < 0 || $ip[2] > 255);
292 0 0 0       return 0 if ($ip[3] <= 0 || $ip[3] >= 255);
293            
294             # Bind port 0 'Select the first one availible'
295 0           my $port = 0;
296 0           my $sock = IO::Socket::IP->new(
297             Domain => PF_INET,
298             LocalAddr => $host,
299             LocalPort => $port,
300             Proto => 'tcp',
301             ReuseAddr => 1
302             );
303 0 0         if ($sock) {
304 0           $bindsuccess=1;
305             }
306              
307 0           return $bindsuccess;
308             }
309              
310             sub _checkIP6 {
311 0     0     my ($self,$host) = @_;
312            
313             # A few safety checks
314 0 0         if (!$host) {
315 0           warn "Incorrect number of arguments, returning fail";
316 0           return 0;
317             }
318            
319             # By default fail the bind
320 0           my $bindsuccess = 0;
321              
322             # Bind port 0 'Select the first one availible'
323 0           my $port = 0;
324 0           my $sock = IO::Socket::IP->new(
325             Domain => PF_INET6,
326             LocalAddr => $host,
327             LocalPort => $port,
328             Proto => 'tcp',
329             ReuseAddr => 1
330             );
331 0 0         if ($sock) {
332 0           $bindsuccess=1;
333             }
334              
335 0           return $bindsuccess;
336             }
337              
338              
339             =head1 AUTHOR
340              
341             Paul G Webster, C<< >>
342              
343             =head1 BUGS
344              
345             Please report any bugs or feature requests through the authors code repository at C
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc Net::Hacky::Detect::IP
352              
353              
354             You can also look for information at:
355              
356             =over 4
357              
358             =item * GitLab: The authors gitlab page for this project
359              
360             L
361              
362             =item * AnnoCPAN: Annotated CPAN documentation
363              
364             L
365              
366             =item * CPAN Ratings
367              
368             L
369              
370             =item * Search CPAN
371              
372             L
373              
374             =back
375              
376              
377             =head1 ACKNOWLEDGEMENTS
378              
379             Thank you for all the continued help from irc.freenode.net #perl and irc.perl.org #perl
380              
381             =head1 LICENSE AND COPYRIGHT
382              
383             Copyright 2017 Paul G Webster.
384              
385             This program is distributed under the (Simplified) BSD License:
386             L
387              
388             Redistribution and use in source and binary forms, with or without
389             modification, are permitted provided that the following conditions
390             are met:
391              
392             * Redistributions of source code must retain the above copyright
393             notice, this list of conditions and the following disclaimer.
394              
395             * Redistributions in binary form must reproduce the above copyright
396             notice, this list of conditions and the following disclaimer in the
397             documentation and/or other materials provided with the distribution.
398              
399             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
400             "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
401             LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
402             A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
403             OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
404             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
405             LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
406             DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
407             THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
408             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
409             OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
410              
411              
412             =cut
413              
414             1; # End of Net::Hacky::Detect::IP