File Coverage

blib/lib/Net/Ping/Network.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             #Copyright 2007-2009, Bastian Angerstein. All rights reserved. This program is free
3             #software; you can redistribute it and/or modify it under the same terms as
4             #PERL itself.
5             package Net::Ping::Network;
6            
7             # infrastructure requirements
8 1     1   8055 use strict;
  1         2  
  1         34  
9 1     1   5 use warnings;
  1         3  
  1         28  
10 1     1   17 use 5.008008;
  1         7  
  1         48  
11             our $VERSION = '1.62';
12            
13 1     1   894 use threads;
  0            
  0            
14             use threads::shared;
15             use Thread::Queue;
16            
17             require Exporter;
18             use base qw ( Exporter );
19            
20             our @EXPORT_OK = qw ( new &doping calchosts listAllHost results );
21             our %EXPORT_TAGS = ( all => [ qw ( new doping calchosts listAllHost results ) ],
22             min => [ qw (new doping results )] );
23            
24             use Config;
25             $Config{useithreads} or die "Recompile Perl with threads to run this program.\n";
26            
27             use Net::Ping::External qw(ping);
28            
29             our %REGISTRY;
30             my $verbose = 0;
31            
32             my $DataQueue = Thread::Queue->new; # a shared Queue Object
33             my %results : shared; # a shared $hash
34             my %process_time : shared; # a shared $hash
35            
36            
37             sub new {
38            
39             # this is the constructor of net::ping::networks.
40             my $class = shift; # read the Name of our Class
41             my $net = undef; # initialize a var for our net
42             my $mask = undef; # initialize a var for our mask
43             my @hostlist = (); # initialize an array to contain a list of user given hosts
44            
45             if (ref $_[0]) { # if we where called with a ref, we expect this to be an ref of array containing ips
46             @hostlist = @{$_[0]}; # a user specified list of all hosts to ping given as reference to an array
47             } else { # if we don´t get a ref, we expect regular usage with a given netwrok and a mask
48             $net = shift;
49             $mask = shift;
50             }
51            
52             my $timeout = shift; #expect an optional timeout in seconds
53             $timeout = defined($timeout)?$timeout:3; # no timeout specified? default to 3
54             my $retries = shift;
55             $retries = defined($retries)?$retries:3; # no retries specified? default to 3
56             my $threads = shift;
57             $threads = defined($threads)?$threads:10; #no amount of threads specified? default to 10
58             my $bsize = shift;
59             $bsize = defined($bsize)?$bsize:56; #no amount of threads specified? default to 56 + icmpheader = 64
60            
61             my ($self) = { # Building our Objecthash
62             NET => $net, # Base Adress
63             MASK => $mask, # Netmask
64             TIMEOUT => $timeout, #Max. Timeout ins seconds
65             RETRIES => $retries, #Max. Retries
66             TC => $threads, #Max. Threads
67             SIZE => $bsize, #Size of ICMP Payload
68             TJ => 0, #Joinable Threads
69             TR => 0, #Running Threads
70             #VERBOSE => 0, # Debugging
71             HOSTS => 0, # Number of Hosts
72             SUMOFHOSTS => 0, # Sum of all Hosts
73             RESULTS => 0,
74             CONF_PING => \&conf_ping, # A Code-Ref need for threading
75             };
76            
77             if ( @hostlist ){ #if we received a list of hosts from the user
78             @{ $self->{ 'HOSTLIST' } } = @hostlist;
79             } else {
80             @{ $self->{ 'HOSTLIST' } } = ();
81             }
82             $self->{ 'ALLHOSTS' } = (); # for a autogenerated list of all hosts to ping
83            
84             $REGISTRY{$self} = $self;
85             bless ($self, $class);
86             return ($self);
87             }
88            
89             ################################################################################
90            
91             sub verbose { # Only a poor Debugging Sub
92             my @output = shift;
93             print @output if ( $verbose );
94             return (1);
95             }
96            
97             ################################################################################
98            
99             sub setHosts{ # Hand a List of Hosts by Yourself.
100             my ($self) = shift;
101             @{$self->{'HOSTLIST'}} = @_;
102             print @_;
103             return ($self);
104             }
105            
106             ################################################################################
107             sub calchosts { # Berechnet anhand der Maske die Anzahl der Möglichen Hosts in einem Netz.
108             #Die Broadcastadress ist kein möglicher Hosts.
109             #Die Netzbasisadresse wird ebenso entfernt.
110             my ($self) = shift;
111             my $lmask; #get the mask
112             my $pO2=0;
113             if ( ref ($self) ) { # Am I a Ref?
114             if ( ${ $self->{'HOSTLIST'} }[0] ) { # if there is a userdefined list of hosts, return the amount of hosts found
115             return scalar ( @{ $self->{'HOSTLIST'} } );
116             }
117             $lmask = $self->{MASK};
118             } elsif ($self) { # Am I a true interger value?
119             if ($self >= 0 && $self <= 32) { # is mask a valif value?
120             $lmask = $self; # copy mask for better readability
121             } else {
122             die "No useable netmask found: $self is not a netmask.\n";
123             }
124             } else { # Is no parameter given?
125             print STDERR "A parameter is missing.";
126             }
127            
128             # Implementing RFC3021 /31 Net has 2 Hosts
129             if ($lmask == 31) {
130             $pO2=2;
131             } elsif ($lmask == 32) {
132             $pO2=1;
133             } else { # if no fancy ip stuff is going on
134             my $bits = 32 - $lmask; # Calculate the amount of bits in the host section of the mask
135             $pO2 = (2 ** $bits) -2; # substract net and broadcast address
136             if ($pO2 < 1) {
137             $pO2=1;
138             }
139             }
140             if (ref $self ){ # how should I return the data
141             $self->{'HOSTS'} = $pO2; # adding it to the object
142             } else {
143             return $pO2; # returning it as a integer value
144             }
145             }
146            
147             ################################################################################
148             sub listAllHost { # List all possible host of a net or all host received from user.
149             # expects a network address and a mask
150             # or expects that net::ping::networks has received a list of hosts
151             my ($self) = shift;
152            
153             my $net = undef; #Net like 127.0.0.0
154             my $mask = undef; #Mask like 24.
155            
156             if ( ref ($self) ) { # Am I a ref?
157             if ( ${ $self->{'HOSTLIST'} }[0] ) {
158             return wantarray ? @{ $self->{'HOSTLIST'} }: join(" ",@{ $self->{'HOSTLIST'} }); # Retrun an Array in list context, return a whitespace seperated string in scalar context
159             }
160             $mask = $self->{'MASK'}; # configure the object
161             $net = $self->{'NET'}; # configure the object
162             } else { # no ref? then calculate all possiblie hosts by given mask and net
163             $net = $self;
164             $mask = shift;
165             }
166            
167             die "Missing parameters listAllHost\n" unless ( defined $net && defined $mask);
168            
169             my @allHosts; # an Array for the list of all hosts
170             my @net_p = split(/\./, $net ); # Split the IP
171            
172             my $sumOfHosts = calchosts( $mask ); #Calculate the amount of possible hosts.
173            
174             if ( ref ($self) ) { # if we have a object
175             $self->{'SUMOFHOSTS'} = $sumOfHosts; # add another field
176             }
177            
178             my $i = 1; #Counter/Itterator
179             while ($i <= $sumOfHosts ) { # Solange wie Counter kleiner Anzahl der Hosts ist
180             $net_p[3]++; # Inkrementiere letzten Abschnitt der IP
181             if ($net_p[3] > 255){ # Wenn der letzte Abschnitt nun eine höhreren Wert hat als 255
182             $net_p[2]++; # Inkrementiere den vorletzten Abschitt.
183             $net_p[3] = 0; # und setze den vierten Abschnitt auf 0
184             }
185             if ($net_p[2] > 255){ # Wenn der dritte Abschnitt nun größer ist als 255
186             $net_p[1]++; # inkrementiere den zweiten Abschnitten
187             $net_p[2] = 0; # und setze den dritte Abschnitt auf 0
188             }
189             if ($net_p[1] > 255){ # Wenn der zweite Abschnitt...
190             $net_p[0]++;
191             $net_p[1] = 0;
192             }
193             if ($net_p[0] > 255){ # Wenn der erste Abschnitt größer als 255 ist
194             die "Out of IP-Range"; # Sterbe und gebe Out of IP-Range.
195             }
196             my $ip = join(".",@net_p); #füge die Abschnitte zu einem String zusammen.
197             push (@allHosts,$ip); # Sammle alle Strings
198             $i++; #inkrementiere Counter
199             } #while
200            
201             if ( ref ($self) ) {
202             $self->{'ALLHOSTS'} = @allHosts;
203             }
204            
205             return wantarray ? @allHosts : join(" ",@allHosts); #if wantarray 1 then @
206             #if wantarray 0 dann $
207             }
208            
209             ################################################################################
210             sub conf_ping {
211             # Thread-Sub which does the pinging
212             my ($self) = shift;
213             use Time::HiRes qw(gettimeofday tv_interval);
214            
215             verbose ( $self . " thread\n" );
216             my $thr = threads->self; #Der thread selbst
217             my $tid = $thr->tid; # Die ID des Threads
218            
219             verbose "$tid has started.\n"; # Thread-ID Status mit.
220            
221             while ( my $host = $DataQueue->dequeue_nb ) { # nonblocking dequeuen of an address.
222             verbose( "$tid is working.\n" ); #Debugging
223             my $t0 = [gettimeofday];
224             if( ping ( host => "$host", count => $self->{RETRIES}, timeout => $self->{TIMEOUT}, size => $self->{SIZE} )){ # Den Host pingen
225             my $t1 = [gettimeofday];
226             verbose ("$host is alive.\n");
227             $results{$host} = 1; # Good
228             $process_time{$host} = tv_interval $t0, $t1;
229             } else {
230             my $t1 = [gettimeofday];
231             verbose ( "$host is unreachable!\n" );
232             $results{$host} = 0; #Bad
233             $process_time{$host} = tv_interval $t0, $t1;
234             }
235             $thr->yield; # Be gentle
236             }
237             verbose ("$tid is done.\n");
238            
239             return(1);
240             }
241            
242             ################################################################################
243            
244             sub doping {
245             # This Subroutine does the Pings.
246             my ($self) = shift;
247             %results = ();
248             %process_time = ();
249             verbose ( @{ $self->{ 'HOSTLIST' } } );
250            
251             if ( @{ $self->{ 'HOSTLIST' } } ){ # If User provides a List of Hosts
252             $DataQueue->enqueue ( @{ $self->{ 'HOSTLIST' } } );
253             } else {
254             $DataQueue->enqueue ( listAllHost($self->{'NET'}, $self->{'MASK'}) ); # Build and Enqueue a list of hosts to ping.
255             }
256             verbose ( "Main: StartingUp" . $self->{'TC'} . "Threads.\n" );
257             for (my $i=0; $i < $self->{'TC'}; $i++){
258             $self->{ $i } = 0;
259             $self->{ $i } = threads->new({'context' => 'list'}, $self->{CONF_PING}, $self); ##############
260             select(undef, undef, undef, 0.02); # take a napp
261             if ($self->{ $i }->error) {
262             print "Main: Error:" . $self->{ $i }->error . "\n";
263             }
264             verbose ("Main: $i Threads have been initialized.\n");
265             }
266             verbose ( "Main: StartUp-Sequence of" . $self->{'TC'} . "Threads completed.\n");
267            
268             while ( threads->list(threads::running) or threads->list(threads::joinable ) ) {
269             my @joinable = threads->list(threads::joinable); #Check for finished Threads
270             $self->{'TJ'} = scalar (@joinable); #Get Amount of Finished Threads
271             $self->{'TR'} = threads->list(threads::running); #Check for running Threads
272             verbose ( "Main: Queued Items = " . $DataQueue->pending . ".\nJoinable Threads = " . $self->{'TJ'} . " Running Threads = " . $self->{'TR'} . ".\n"); #Give a Process Status
273             foreach my $t (@joinable) {
274             $t->join;
275             }
276             select(undef, undef, undef, 0.02); # be gentle
277             }
278             verbose ( %results );
279             $self->{RESULTS} = \%results;
280             $self->{MSEC} = \%process_time;
281             return (\%results, \%process_time);
282             }
283            
284             ################################################################################
285             sub results {
286             my ($self) = shift;
287             return $self->{RESULTS};
288             }
289            
290             sub process_time {
291             my ($self) = shift;
292             return $self->{MSEC};
293             }
294             1;
295            
296             =head1 NAME
297             Net::Ping::Network - A modul to ICMP-request nodes in a network (or list) very fast
298            
299             =head1 SYNOPSIS
300            
301             Import Net-Ping-Network and use the original Interface.
302             Simply give a network address and a mask to the constructor new().
303            
304             use Net::Ping::Network;
305             my $net = Net::Ping::Network->new("127.0.0.0", 29);
306            
307            
308             Optionally the timeout in seconds (3), the amount of retries (3),
309             the number of threads utilized (10) and the size in byte (56) of icmp-load can be specified.
310            
311             my $net = Net::Ping::Network->new("127.0.0.0", 29, $timeout, $retries, $threads, $size);
312            
313            
314             To ping the hosts in the network use the doping() methode of your Net::Ping::Network methode.
315             When Net::Ping::Network is done, you can get the results as hashref using the methode results().
316            
317             $net->doping();
318             my $results = $net->results();
319            
320             #Since Version 1.62 you can simply
321             my ($results,$process_time = $net->doping();
322            
323             The hashkey of $results hash_ref is the ip, the value is 1 for reachable, 0 for unreachable.
324             The hashkey of $process_time is the ip, the value is a value in microseconds needed to process the ping.
325             (It is the roundtrip-time of the ping. If no response is received its a value near the given timeout.)
326            
327             The hash is not sorted in anyway, to sort a hash is useless.
328             If you need sorted results try this:
329            
330             1. get the Keys from the returned hashref (ips).
331            
332             my @unsorted_keys = keys %$results;
333            
334             2. using a sort over the packed data. This is much fast then sort by every field.
335            
336             my @keys = sort { # sort list of ips accending
337             pack('C4' => $a =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/)
338             cmp pack('C4' => $b =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) } @unsorted_keys;
339            
340            
341             foreach my $key ( @keys ) {
342             print "$key" . " is ";
343             if ( $$results{"$key"} ) {
344             print "alive.\n";
345             } else {
346             print "unreachable!\n";
347             }
348             }
349            
350             A list of all hosts to ping, can be gathered from the methode listAllHost()
351            
352             my @all = $net->listAllHost();
353             my $list = $net->listAllHost();
354            
355             In list context listAllHost returns an array containing all hosts to ping.
356             In scalar context this methode returns a whitespace separeted string of all IPs.
357            
358             If you need the number of Host for a given netmask use
359             my $x = $net->calchosts();
360             or
361             my $y = calchost(22);
362            
363             calchosts() calculates the max. number of host in a network with the given mask.
364             The broadcast address is not a possible host, the network base address ist not a possible host.
365            
366            
367            
368             =head2 DESCRIPTION
369            
370            
371             The existing ping moduls are slow and can only handle one ping at a time.
372             Net::Ping::Networks (Net::Ping::Multi) can handle even large ip ranges and entire networks.
373             Depending of your computing power and memory you can scan a class c network in less then 5 seconds.
374            
375             On a normal desktop computer and without any further tuning, one should be able to manage 2500-3000 ips in less then 5 minutes.
376            
377             Threads are utilised to boost performace. Threads feel a still a little bit beta today.
378            
379             =head2 Methodes
380            
381             =over 1
382            
383             =item C
384            
385             creates a new Net::Ping::Network instance. Needs a network base address and netmask or an array of ips to ping.
386             If a network base address and a mask is supplied, Net::Ping::Networks will build a List of all host-ips in the net
387             automaticaly.
388            
389             C<< $n = Net::Ping::Network->new("127.0.0.0", 29, [$timeout, $retries, $threads, $size]); >>
390            
391            
392             =item C
393            
394             depending on the context it returns a list containig all possible Hosts in the network or a space seperated string.
395            
396            
397             =item C
398            
399             executes the configured ping utilising the given parameters.
400             As lower the amount auf pings per threads is, as faster the methode will return.
401            
402             =item C
403            
404             Calculates the amount of possible hosts for a Netmask, value between 0 and 32 is expected.
405             Network-Address and Broadcast is removed, but a /32 has 1 Address.
406            
407             =item C
408            
409             Returns a Hashref of the Results. Keys are IPs, the Values are returncodes (0 for bad or 1 for ok).
410            
411             =item C
412            
413             Returns a Hashref of the per Host Process Time (PIND ROUNDTRIPTIME). Keys are the Host-IPs.
414            
415             =back
416            
417             =head1 COPYRIGHT
418            
419             Copyright 2007-2009, Bastian Angerstein. All rights reserved. This program is free
420             software; you can redistribute it and/or modify it under the same terms as
421             PERL itself.
422            
423             =head1 AVAILABILITY
424            
425             =head1 CAVEATS
426            
427             Threads are cpu and memory intensive and feel still beta. Have an extra eye on memory leaks.
428             Net::Ping::Networks is a quick and dirty but easy to read and understand implementation.
429             Documentation is in the Code.
430            
431             Also it "could" lead into trouble to use a multithreaded modul in a multithreaded environment.
432            
433             =head1 AUTHOR
434            
435             Bastian Angerstein - L
436            
437             =head1 SEE ALSO
438            
439             L, L
440            
441             =cut
442