Thursday, December 25, 2008

Ping all

If you happen to have many computers in your local network, and you want to find out which ones are working at the moment without getting up, well, there are several ways to do this, probably. Same for when you have DNS turned on and want to know what IP addresses all those computers have got at the moment.

So this might be yet another way to do this.

Its advantage is that it's straightforward. Its disadvantage is that it's rather crude and can be slow. Oh, and it doesn't find out the names of all those computers running non-gnu/linux systems... although, personally, I don't care about that very much.

It's written in Perl, because writing it in Bash would hurt too much, as it lacks easily-manipulated arrays and a way of getting the hostname out of the IP (well, I couldn't find a way) and Python doesn't provide anything to ping out of the box... I'm not good at Perl so a) I had a chance to practice and b) the code probably sucks tremendously in some bits.

I wanted to do the main monitoring using Perl's own ping object, but somehow it didn't want to produce correct results for non-gnu/linux systems... and a ping function shouldn't be so finicky. Whether, I used it wrong or there is something wrong with my configuration, it was easier to use an external ping command, redirect its output to /dev/null and return something if it finds a computer. All that is done on line 76.

Before pinging, the IP addresses for pinging are generated, one by one and put into an array. This is perhaps not the best way to do this, but it's rather convenient... not to mention that it's good practice.

Enough banter, here's the code.
 
1 #!/usr/bin/perl
2 #
3 # Ping all
4 #
5 # Pings all ip addresses within the specified range and
6 # retrieves the name
7 #
8 # Parameters:
9 # range start - IP address at which to check,
10 # range end - last IP address to check.
11 # Example:
12 # pingall 10.0.0.10 10.0.0.20
13 # Checks all the addresses between .10 and .20
14 # Author:
15 # Konrad Siek
16
17 use Socket;
18
19 # Quit, if no range is specified.
20 # Note: Number of parameters in Perl is one lower.
21 die "Usage: $0 <range start> <range end>\n" if ($#ARGV != 1);
22
23 # Validate IP and return its more manageable form.
24 sub to_ip {
25 my $message = "$_[0] is not a valid IP.";
26 my @array = split(/\./, $_[0]);
27 die $message if $#array != 3;
28 foreach $e (@array) {
29 die $message if $e =~ m/[^0-9]+/ or $e > 255;
30 }
31 return @array;
32 }
33
34 # Check the validity of range IPs and turn them into arrays.
35 my @start_range = to_ip(@ARGV[0]);
36 my @end_range = to_ip(@ARGV[1]);
37
38 # Increment the IP to the next. Roll over if overflow occurs.
39 sub increment {
40 @array = @_;
41 for($i = 3; $i >= 0;) {
42 if(@array[$i] >= 255) {
43 $i--;
44 } else {
45 @array[$i]++;
46 return @array;
47 }
48 }
49 return (0, 0, 0, 0);
50 }
51
52 # Compare two IP arrays.
53 sub compare {
54 @a = @_[0..3];
55 @b = @_[4..7];
56 for($i = 0; $i < 4; $i ++) {
57 if(@a[$i] < @b[$i]){
58 return -1;
59 }
60 if(@a[$i] > @b[$i]){
61 return 1;
62 }
63 }
64 return 0;
65 }
66
67 # Prepare list of addresses within range.
68 @addresses = ();
69 while (compare(@start_range, @end_range) <= 0) {
70 @addresses[++$#addresses] = join('.', @start_range);
71 @start_range = increment(@start_range);
72 }
73
74 # Ping each of the addresses from the prepared list.
75 foreach $address (@addresses) {
76 my $return = `ping -c 5 $address > /dev/null && echo 1`;
77 my $state = $return == 1 ? '+' : '-';
78 my $inet_addr = inet_aton($address);
79 my $hostname = gethostbyaddr($inet_addr, AF_INET);
80 print "[$state]\t$address\t$hostname\n";
81 }


The code is also available at GitHub as perl/ping_all.

No comments: