Ticket #22077: mrtg-ping-probe

File mrtg-ping-probe, 19.8 KB (added by msantos@…, 15 years ago)

modified version with a fix to the RE that seems to work (for me)

Line 
1#!/usr/bin/env perl
2##################################################################
3# @(#) $Id: mrtg-ping-probe,v 2.5 2003/09/13 18:04:29 pwo Exp $
4# @(#) mrtg-ping-probe release $Name: Release_2_2_0 $
5#
6# Copyright (c) 1997-2003 Peter W. Osel <pwo@pwo.de>.
7# All Rights Reserved.
8#
9# See the file COPYRIGHT in the distribution for the exact terms.
10#
11##################################################################
12#
13# "I saw -- from the cathedral -- you were watching me"
14#
15#       -- Tanita Tikaram, `Cathedral Song'
16#       on: Tanita Tikaram, `Ancient Heart', 1988, WEA Records
17#
18##################################################################
19use 5.6.1;
20use Getopt::Std;
21use File::Basename;
22use Config;
23
24$Prog_name = basename($0);              # Who I am
25$Prog_vers = q$Revision: 2.5 $;
26$Prog_id = q$Id: mrtg-ping-probe,v 2.5 2003/09/13 18:04:29 pwo Exp $;
27$Usage = "Usage: $Prog_name [-hsvV] [-d deadtime] [-k count] [-l length] [-o ping_options] [-p [factor*]{min|max|avg|loss|integer}/[factor*]{min|max|avg|loss|integer}] [-r [rsh:][user@]host[:osname]] [-t timeout] host\n";
28
29# Parse Command Line:
30die $Usage unless getopts('d:Dhk:l:o:p:r:st:vV');
31
32# Generic Options
33$Debug          = defined($opt_D) ? $opt_D : 0;
34$PrintHelp      = defined($opt_h) ? $opt_h : 0;
35$Verbose        = defined($opt_v) ? $opt_v : 0;
36$Silent         = defined($opt_s) ? $opt_s : 0;
37$PrintVersion   = defined($opt_V) ? $opt_V : 0;
38
39# Tool Specific Options
40$DeadTime       = defined($opt_d) ? $opt_d : 0;
41$PacketCount    = defined($opt_k) ? $opt_k : "10";
42$PacketLength   = defined($opt_l) ? $opt_l : "56";
43$PingOptions    = defined($opt_o) ? $opt_o : "";
44$PickList       = defined($opt_p) ? $opt_p : "max/min";
45$RemotePing     = defined($opt_r) ? $opt_r : "";
46$TimeOut        = defined($opt_t) ? $opt_t : 0;
47
48# Check Sanity of Arguments
49$err += check_numeric("-d", $DeadTime);
50$err += check_numeric("-k", $PacketCount);
51$err += check_numeric("-l", $PacketLength);
52$err += check_numeric("-t", $TimeOut);
53$err += check_picklst("-p", $PickList);
54
55if ($err) {
56        print STDERR $Usage;
57        exit(1);
58        }
59
60
61if ($PrintVersion ) {
62        print STDERR "$Prog_name: $Prog_vers\n";
63        exit(0);
64        }
65
66if ($PrintHelp) {
67        print $Usage;
68        exit(0);
69        }
70
71if (@ARGV > 1) {
72        print STDERR "$Prog_name: ERROR: ignoring superfluous arguments\n";
73        print STDERR "$Usage";
74        }
75
76if (@ARGV < 1) {
77        print STDERR "$Prog_name: FATAL: ping what?\n";
78        print STDERR "$Usage";
79        exit(1);
80        }
81
82
83($HostToPing) = @ARGV;
84
85
86($pt{min}, $pt{avg}, $pt{max}, $pt{loss}) =
87        ping($HostToPing, $PacketLength, $PacketCount, $TimeOut);
88
89print "$Prog_name: DBG: main(): ping() ret: $pt{min}, $pt{avg}, $pt{max}, $pt{loss}\n" if $Debug;
90
91$PickList =~ /^(?:(\d+)\*)?(\w+)\/(?:(\d+)\*)?(\w+)$/;
92($f1, $p1, $f2, $p2) = ($1, $2, $3, $4);
93$f1 = $f1 ? $f1 : 1;
94$f2 = $f2 ? $f2 : 1;
95print "$Prog_name: DBG: main(): before psub: f = ($f1 $f2); p = ($p1 $p2)\n" if $Debug;
96
97$p1 = $p1 =~ /^\d+$/ ? $p1 : $pt{$p1};
98$p2 = $p2 =~ /^\d+$/ ? $p2 : $pt{$p2};
99
100print "$Prog_name: DBG: main(): after  psub: f = ($f1 $f2); p = ($p1 $p2)\n" if $Debug;
101
102# The external mrtg probe returns up to 4 lines of output:
103#       1. Line: current state of the 'incoming bytes counter'
104#       2. Line: current state of the 'outgoing bytes counter'
105#       3. Line: string, telling the uptime of the target.
106#       4. Line: telling the name of the target.
107# We leave out line 3, and 4.
108
109printf "%d\n%d\n", $f1 * $p1, $f2 * $p2;
110
111exit(0);
112
113
114##################################################################
115sub check_numeric {
116        my($opt, $val) = @_;
117        my($err) = 0;
118
119        unless ($val =~ /^\d+$/) {
120                print STDERR "$Prog_name: FATAL: option $opt requires numeric argument.\n";
121                ++$err;
122                }
123        return($err);
124        }
125
126##################################################################
127sub check_picklst {
128        my($opt, $val) = @_;
129        my($err, $i) = (0, 0);
130        my(@v, @n);
131
132        unless ($val =~ /^(?:(\d+)\*)?(\w+)\/(?:(\d+)\*)?(\w+)$/) {
133                print STDERR "$Prog_name: FATAL: option $opt requires [factor*]word/[factor*]word argument, I found \"$val\"\n";
134                ++$err;
135                }
136        @n = ($1, $3);
137        @v = ($2, $4);
138
139        print "$Prog_name: DBG: check_picklst(): n = (@n); v = (@v)\n" if $Debug;
140
141        foreach $i (0..1) {
142                unless ($v[$i] =~ /^(min|max|avg|loss|\d+)$/) {
143                        print STDERR "$Prog_name: FATAL: option $opt uses unknown item $v[$i]\n";
144                        print STDERR "$Prog_name: FATAL: option $opt may choose from min|max|avg|loss or number\n";
145                        ++$err;
146                        }
147                }
148
149        return($err);
150        }
151
152##################################################################
153# ping selects the external or internal, built-in ping method
154# and starts the external ping program optionally with a
155# timeout to abort external ping programs that seem to hang
156# when the target is unreachable.
157
158sub ping {
159        my($host, $length, $count, $timeout) = @_;
160        my(%pt);
161
162        ($pt{min}, $pt{avg}, $pt{max}, $pt{loss}) =
163                ext_ping($host, $length, $count, $timeout);
164
165        return($pt{min}, $pt{avg}, $pt{max}, $pt{loss});
166        }
167
168##################################################################
169# ping host retrieve and return min, avg, max round trip time
170# relying on finding a standard ping in PATH.
171# Try to not be platform specific if at all possible.
172#
173sub ext_ping {
174        my($host, $length, $count, $timeout) = @_;
175        my(%ping, $ping_output, $redirect_stderr, $pid, %pt);
176        my($alarm_exists);
177
178        # List of known ping programs
179        %ping = (
180                'MSWin32'       => "ping -l $length -n $count $host",
181                'aix'           => "/etc/ping $host $length $count",
182                'bsdos'         => "/bin/ping -s $length -c $count $host",
183                'darwin'        => "/sbin/ping -s $length -c $count $host",
184                'dec_osf'       => "/sbin/ping -s $length -c $count $host",
185                'freebsd'       => "/sbin/ping -s $length -c $count $host",
186                'hpux'          => "/etc/ping $host $length $count",
187                'irix'          => "/usr/etc/ping -s $length -c $count $host",
188                'linux'         => "/bin/ping -s $length -c $count $host",
189                'netbsd'        => "/sbin/ping -s $length -c $count $host",
190                'openbsd'       => "/sbin/ping -s $length -c $count $host",
191                'os2'           => "ping $host $length $count",
192                'OS/2'          => "ping $host $length $count",
193                'solaris'       => "/usr/sbin/ping -s $host $length $count",
194                'sunos'         => "/usr/etc/ping -s $host $length $count",
195                );
196
197        unless (defined($ping{$Config{'osname'}})) {
198                print STDERR "${Prog_name}: FATAL: Not yet configured for $Config{'osname'}\n";
199                exit(1);
200                }
201
202        # add ping options, if any
203        $ping{$Config{'osname'}} =~ s/ / $PingOptions / if $PingOptions;
204
205        # windows 95/98 does not support stderr redirection...
206        # also OS/2 users reported problems with stderr redirection...
207        $redirect_stderr = $Config{'osname'} =~ /^(MSWin32|os2|OS\/2)$/i ? "" : "2>&1";
208
209        # freebsd > 3.x does not allow option -s,
210        # unless we run as root (which we shouldn't)
211        if (($Config{'osname'} =~ /^freebsd$/i) && $>) {
212                # remove option -s from ping command
213                $ping{$Config{'osname'}} =~ s/ -s \d+//;
214                print "$Prog_name: DBG: ext_ping(): ping = ($ping{$Config{'osname'}})\n" if $Debug;
215                }
216
217        # initialize return values
218        $pt{loss} = 100;
219        $pt{min} = $pt{avg} = $pt{max} = $DeadTime;
220        $ping_output = "";
221
222        # finally call the external ping program and read its output:
223        unless ($pid = open(PING, "$ping{$Config{'osname'}} $redirect_stderr |")) {
224                print STDERR "${Prog_name}: FATAL: Can't open $ping{$Config{'osname'}}: $!";
225                exit(1);
226                }
227
228        $alarm_exists = eval { alarm(0); 1 };
229        print STDERR "${Prog_name}: WARN: built-in alarm() does not exist, can't timeout ping command\n"
230                if $Verbose && $timeout && !$alarm_exists;
231
232        if ($alarm_exists) {
233                # read and timeout ping() if it takes too long...
234                eval {
235                        local $SIG{ALRM} = sub { die "alarm\n" };       # \n required!
236                        alarm $timeout;
237                        while (<PING>) {
238                                $ping_output .= $_;
239                                }
240                        alarm 0;
241                        };
242
243                if ($@) {
244                        die unless $@ eq "alarm\n";     # propagate unexpected errors
245                        # timed out, kill child, get remaining output, ...
246                        kill("TERM", $pid);
247                        while (<PING>) {
248                                $ping_output .= $_;
249                                }
250
251                        unless ($Silent) {
252                                print STDERR "${Prog_name}: ERROR: external ping hit timeout $timeout, assuming target $host is unreachable\n";
253                                print STDERR "${Prog_name}: INFO: The output of the ping command $ping{$Config{'osname'}} was:\n";
254                                print STDERR "$ping_output\n";
255                                }
256                        }
257                }
258        else {
259                # read and hope that ping() will return in time...
260                while (<PING>) {
261                        $ping_output .= $_;
262                        }
263
264                }
265
266        # didn't time out, analyze ping output.
267        close(PING);
268
269        # try to find round trip times
270        if ($ping_output =~ m@(?:round-trip|rtt)(?:\s+\(ms\))?\s+min/avg/max(?:/(?:m|std)-?dev)?\s+=\s+(\d+(?:\.\d+)?)/(\d+(?:\.\d+)?)/(\d+(?:\.\d+)?)@m) {
271                $pt{min} = $1; $pt{avg} = $2; $pt{max} = $3;
272                }
273        elsif ($ping_output =~ m@^\s+\w+\s+=\s+(\d+(?:\.\d+)?)ms,\s+\w+\s+=\s+(\d+(?:\.\d+)?)ms,\s+\w+\s+=\s+(\d+(?:\.\d+)?)ms\s+$@m) {
274                # this should catch most windows locales
275                $pt{min} = $1; $pt{avg} = $3; $pt{max} = $2;
276                }
277        else {
278                unless ($Silent) {
279                        print STDERR "${Prog_name}: ERROR: Could not find ping summary for $host\n";
280                        print STDERR "${Prog_name}: INFO: The output of the ping command $ping{$Config{'osname'}} was:\n";
281                        print STDERR "$ping_output\n";
282                        }
283                }
284
285        # try to find packet loss
286        # ToDo: only if requested?)
287        if ($ping_output =~ m@(\d+(\.\d+)?)% (?:packet )?loss(?:$|,)@m) {
288                # Unix
289                $pt{loss} = $1;
290                }
291        elsif ($ping_output =~ m@\(perte\s+(\d+)%\),\s+$@m) {
292                # Windows french locale
293                $pt{loss} = $1;
294                }
295        elsif ($ping_output =~ m@\((\d+)%\s+(?:loss|perdidos|persi|de perda|Verlust)\),\s+$@m) {
296                # Windows portugesee, spanish, brazilian, german locale
297                $pt{loss} = $1;
298                }
299        else {
300                unless ($Silent) {
301                        print STDERR "${Prog_name}: ERROR: Could not find packet loss summary for $host\n";
302                        print STDERR "${Prog_name}: INFO: The output of the ping command $ping{$Config{'osname'}} was:\n";
303                        print STDERR "$ping_output\n";
304                        }
305                }
306
307        # If pping timed out, values are still set to 100% loss, and rtt to DeadTime
308        # On windows, 100% loss will still show a rtt of 0, so reset it to DeadTime
309
310        print "$Prog_name: DBG: ext_ping(): ret-val-mat: $pt{min}, $pt{avg}, $pt{max}, $pt{loss}\n" if $Debug;
311        $pt{min} = $pt{avg} = $pt{max} = $DeadTime if $pt{loss} == 100;
312        print "$Prog_name: DBG: ext_ping(): ret-val-res: $pt{min}, $pt{avg}, $pt{max}, $pt{loss}\n" if $Debug;
313        return($pt{min}, $pt{avg}, $pt{max}, $pt{loss});
314}
315
316__END__
317
318=head1 NAME
319
320mrtg-ping-probe - a round trip time and packet loss probe for MRTG
321
322=head1 SYNOPSIS
323
324B<mrtg-ping-probe>
325[ B<-hsvV> ]
326[ B<-d> I<deadtime> ]
327[ B<-k> I<count> ]
328[ B<-l> I<length> ]
329[ B<-o> I<ping_options> ]
330[ B<-p> [I<factor>*]I<item>/[I<factor>*]I<item> ]
331[ B<-r> I<[rsh:][user@]host[:osname]> ]
332[ B<-t> I<timeout> ]
333I<host>
334
335=head1 DESCRIPTION
336
337B<mrtg-ping-probe> pings the given host I<host> and prints on stdout
338two lines extracted from the ping output.  The default is to print
339the maximum, and the minimum round trip time.
340
341It is meant to be called by the Multi Router Traffic Grapher (MRTG).
342
343
344=head1 OPTIONS
345
346
347=over 8
348
349
350=item B<-h>
351
352print help on stdout and exit.
353
354
355=item B<-v>
356
357Be more verbose.
358
359
360=item B<-V>
361
362Print version number on stderr and exit.
363
364
365=item B<-d> I<deadtime>
366
367Specifies the value we return for round trip times in case we assume
368that the target is down.  The default is zero.  We assume that the
369target is unreachable, if we cannot find the ping summary or if the
370ping program was aborted because of a set timeout.
371
372For WAN connections that usually have round trip times of 10ms and
373higher, ranges of zero round trip time are highly visible.  In a LAN
374environment, you might set it to a high value, e.g. 999, which however
375might change the scale of the graphs in such a way that you hardly see
376the regular round trip times.  You might use mrtg-misc-probe's pong
377option to generate a graph that shows reachability of targets, instead.
378
379
380=item B<-k> I<count>
381
382Specifies the number of of ping packets to be sent.  The default is to
383send 10 ping packets.
384
385
386=item B<-l> I<length>
387
388Use I<length> as the length of the data portion of the ICMP ECHO
389request packet.  The default I<length> is 56 data bytes.
390
391
392=item B<-o> I<ping_options>
393
394Pass I<ping_options> to the ping program.  You can use this generic
395option to e.g. pass an option to ping to suppress displaying addresses
396as host names.  This helps to prevent the ping to fail because it
397cannot map hostnames to IP addresses and vice versa.  To pass several
398arguments, enclose the options in quotes.  Check the documentation of
399your ping program for possible options.
400
401
402=item B<-p> [I<factor>B<*>]I<item>/[I<factor>B<*>]I<item>
403
404Pick the values you want mrtg-ping-probe to return.  Allowed values for
405I<item> are: B<min>, B<max>, B<avg>, B<loss>, or an I<integer>.  Each item
406can be preceded by a integer factor used to multiply the value returned
407by the ping program.  The default pick-list is B<min/max>.
408
409To display ping times in microseconds instead of milliseconds, use: B<-p
4101000*max/1000*min>.
411
412
413=item B<-r> I<[rsh:][user@]host[:osname]>
414
415B<Not Yet Implemented>
416
417run ping on remote host I<host>, as user I<user> (or as local user, if
418no user is given).  Uses B<rsh -n> to start program on remote host,
419unless you provide a different program name.  If the remote host has a
420different system type than the local host (if the osname is different)
421you have to say so.
422
423This option can be used if you run mrtg on a host that cannot ping to
424the final target, and you cannot install mrtg and/or perl on the
425intermediate host used to ping the final target.
426
427
428=item B<-s>
429
430Silent mode.  Do not generate error messages if there is no response
431from the ping program or if it ran into the timeout.  Usually cron will
432mail you these error messages, which might be helpful to debug
433problems.
434
435
436=item B<-t> I<timeout>
437
438Abort the external ping program after I<timeout> seconds.  A I<timeout>
439value of zero (the default) means, we do not abort the external ping
440program.
441
442If mrtg-ping-probe seems to hang forever, check your ping program, it
443might be a version that wants to B<receive> the given number of
444ECHO_RESPONSE packets instead of just sending them.  If your target is
445unreachable, these pings ping forever.
446
447You want to choose I<timeout> as short as possible to leave mrtg enough
448time for all your other targets, but long enough so you do not abort
449pings (too often).  You might use (I<count> * worst case round trip
450time) as a starting point.  (Or install a ping program that is not
451broken ;-)
452
453If your perl installation does not implement the built-in alarm()
454function, the timeout option will be ignored.  You will get a warning
455about this only in verbose mode (option B<-v>).  I have not found a
456perl installation on Windows that implements the alarm() built-in
457function on Win32.  So basically on Windows the timeout option is
458not working.
459
460=back
461
462
463=head1 RETURN VALUE
464
465The program exits with an exit value 0, if it believes it was
466successful.
467
468
469=head1 EXAMPLES
470
471=over 4
472
473=item B<mrtg-ping-probe ricochet>
474
475Retrieves the maximum and minimum round trip time to the host
476B<ricochet>, using the default length and count.
477
478
479=item B<mrtg-ping-probe -p max/avg ricochet>
480
481Retrieves the maximum and average round trip time to the host
482B<ricochet>, using the default length and count.
483
484
485=item B<mrtg-ping-probe -p '1000*max/1000*avg' ricochet>
486
487Retrieves the maximum and average round trip time to the host
488B<ricochet> multiplied by a factor of 1000, using the default length
489and count.
490
491
492=item B<mrtg-ping-probe -k 17 -l 1000 192.168.192.42>
493
494Retrieves the maximum and minimum round trip time to the host
495192.168.192.42, using 17 1000 data bytes pings.
496
497
498=item B<mrtg-ping-probe -o -n ricochet.pwo.de>
499
500Suppress displaying addresses as host names on Solaris 2 (to protect
501from DNS problems causing the ping to fail) by passing option B<-n> to
502the ping program.
503
504Note that in this example `B<-n>' is not an option for mrtg-ping-probe,
505but gets passed to the ping program.
506
507
508=item B<mrtg-ping-probe -o '-n -I 3' ricochet.pwo.de>
509
510Pass several options B<-n -I 3> to the ping program.
511
512
513=item B<mrtg-ping-probe -p loss/loss ricochet.pwo.de>
514
515Monitors the packet loss for the link to host ricochet.pwo.de.
516
517
518=back
519
520=head1 FILES
521
522B<mrtg-ping-probe> uses an external ping program, like
523F</usr/sbin/ping>.
524
525=head1 SEE ALSO
526
527mrtg(1), mrtg-ping-cfg, ping(1), mrtg.cfg-ping, mrtg-misc-probe(1)
528
529http://www.mrtg.org/
530
531http://pwo.de/projects/mrtg/
532
533=head1 DIAGNOSTICS
534
535=over 4
536
537=item FATAL: Not yet configured for I<osname>
538
539Currently B<mrtg-ping-probe> depends on an external ping program, which
540every operating systems hides in another place.  Also different
541programs require different arguments.  We have a configuration table
542listing the ping program for each operating systems.  You have to
543figure out how to call which program on your platform, and add to the
544information to the table.  Please contribute back any additions, so I
545can include them in the next version.
546
547
548=item ERROR: ignoring superfluous arguments
549
550More than one argument was given.  B<mrtg-ping-probe> will ignore all
551but the first argument.  The first argument is taken as a hostname or
552IP address of an host and B<mrtg-ping-probe> will try to ping it.
553
554=item FATAL: ping what?
555
556No argument was given.  B<mrtg-ping-probe> terminates, as there is
557nothing to ping.
558
559
560=item FATAL: option I<option> requires numeric argument.
561
562The argument for option I<option> was not an integer number.
563
564
565=item FATAL: Can't open ping: I<some reason>
566
567B<mrtg-ping-probe> was not able to execute the external ping program.
568Check the pathname and permissions of the external ping program.
569I<some reason> might give some useful hints.
570
571
572=item ERROR: external ping hit timeout I<timeout>,
573assuming target I<host> is unreachable
574
575We ran into a timeout pinging the target host I<host>.  You might have
576to increase the timeout value (Option B<-t>) if this happens when the
577target is up and the round trip time just happens to be longer than
578usual.
579
580The captured output of the ping program is printed and will (hopefully)
581give further hints why this problem occurred.
582
583This message is not printed if mrtg-ping-probe runs in silent mode
584(Option B<-s>).
585
586
587=item ERROR: Could not find ping summary for I<host>
588
589B<mrtg-ping-probe> was not able to find the ping summary.  Most likely,
590the host is not reachable.  If your operating system changed (e.g. it
591was upgraded to a new version, or a new version of the ping program was
592installed), it might also be necessary to change the regular expression
593that extracts the round trip times.  You might want to use the perl
594script check-ping-fmt (which is part of the source distribution) to test
595the regular expression.
596
597The captured output of the ping program is printed and will (hopefully)
598give further hints why this problem occurred.
599
600This message is not printed if mrtg-ping-probe runs in silent mode
601(Option B<-s>).
602
603
604
605=item ERROR: Could not find packet loss summary for I<host>
606
607B<mrtg-ping-probe> was not able to find the packet loss summary.
608If your operating system changed (e.g. it was upgraded to a new
609version, or a new version of the ping program was installed), it might
610also be necessary to change the regular expression that extracts the
611packet loss.  You might want to use the perl script check-ping-fmt
612(which is part of the source distribution) to test the regular
613expression.
614
615The captured output of the ping program is printed and will (hopefully)
616give further hints why this problem occurred.
617
618This message is not printed if mrtg-ping-probe runs in silent mode
619(Option B<-s>).
620
621
622=back
623
624=head1 RESTRICTIONS
625
626B<mrtg-ping-probe> currently depends on an external ping(1) program.
627If the external program does not support an option, the option given to
628B<mrtg-ping-probe> will be ignored.
629
630Under B<freebsd> release 3.x or later, ping option B<-s>
631I<packet-length> (B<mrtg-ping-probe> option B<-l> I<length>) is only
632allowed to be used when we run as root (which we should not), therefore
633this option is silently removed on B<freebsd> before we call the
634external ping program.
635
636=head1 BUGS
637
638This program has way too many options and tries to support too many
639different systems.
640
641Using this program to monitor sub-millisecond round trip times or
642packet loss might be questionable.
643
644Option B<-r>, remote execution of the ping program, is not yet
645implemented.
646
647
648=head1 COPYRIGHT
649
650Copyright (c) 1997-2003 Peter W. Osel <pwo@pwo.de>.
651All Rights Reserved.
652
653See the file COPYRIGHT in the distribution for the exact terms.
654
655=head1 AUTHOR
656
657Written by Peter W. Osel E<lt>pwo@pwo.deE<gt>.
658http://pwo.de/
659