1#!/usr/local/bin/perl -w
2######################################################################
3### Observe interface counters in real time.
4######################################################################
5### Copyright (c) 1995-2000, Simon Leinen.
6###
7### This program is free software; you can redistribute it under the
8### "Artistic License" included in this distribution (file "Artistic").
9######################################################################
10### Author:       Simon Leinen  <simon@switch.ch>
11### Date Created: 21-Feb-1999
12###
13### Real-time full-screen display of the octet and (Cisco-specific)
14### CRC error counters on interfaces of an SNMP-capable node
15###
16### Description:
17###
18### Call this script with "-h" to learn about command usage.
19###
20### The script will poll the RFC 1213 ifTable at specified intervals
21### (default is every five seconds).
22###
23### For each interface except for those that are down, a line is
24### written to the terminal which lists the interfaces name (ifDescr),
25### well as the input and output transfer rates, as computed from the
26### deltas of the respective octet counts since the last sample.
27###
28### "Alarms"
29###
30### When an interface is found to have had CRC errors in the last
31### sampling interval, or only output, but no input traffic, it is
32### shown in inverse video.  In addition, when a link changes state
33### (from normal to inverse or vice versa), a bell character is sent
34### to the terminal.
35###
36### Miscellaneous
37###
38### Note that on the very first display, the actual SNMP counter
39### values are displayed.  THOSE ABSOLUTE COUNTER VALUES HAVE NO
40### DEFINED SEMANTICS WHATSOEVER.  However, in some versions of
41### Cisco's software, the values seem to correspond to the total
42### number of counted items since system boot (modulo 2^32).  This can
43### be useful for certain kinds of slowly advancing counters (such as
44### CRC errors, hopefully).
45###
46### The topmost screen line shows the name of the managed node, as
47### well as a few hard-to-explain items I found useful while debugging
48### the script.
49###
50### Please send any patches and suggestions for improvement to the
51### author (see e-mail address above).  Hope you find this useful!
52###
53### Original Purpose:
54###
55### This script should serve as an example of how to "correctly"
56### traverse the rows of a table.  This functionality is implemented in
57### the map_table() subroutine.  The example script displays a few
58### columns of the RFC 1213 interface table and Cisco's locIfTable.  The
59### tables share the same index, so they can be handled by a single
60### invocation of map_table().
61###
62require 5.003;
63
64use strict;
65
66use BER;
67use SNMP_Session "0.96";	# requires map_table_4() and ipv4only
68use POSIX;			# for exact time
69use Curses;
70use Math::BigInt;
71use Math::BigFloat;
72
73### Forward declarations
74sub out_interface ($$$$$$@);
75sub pretty_ps ($$);
76sub usage ($ );
77
78my $version = '1';
79
80my $desired_interval = 5.0;
81
82my $switch_engine_p = 0;
83
84my $all_p = 0;
85
86my $port = 161;
87
88my $max_repetitions = 0;
89
90my $suppress_output = 0;
91
92my $suppress_curses = 0;
93
94my $debug = 0;
95
96my $show_out_discards = 0;
97
98my $cisco_p = 0;
99
100## Whether to use 64-bit counters.  Can be requested with `-l' option.
101my $counter64_p = 0;
102
103## Whether to select IPv4-only in open().  Can be set using `-4' option.
104my $ipv4_only_p = 0;
105
106my $host;
107
108my $community;
109
110my $use_getbulk_p = 1;
111
112while (defined $ARGV[0]) {
113    if ($ARGV[0] =~ /^-v/) {
114	if ($ARGV[0] eq '-v') {
115	    shift @ARGV;
116	    usage (1) unless defined $ARGV[0];
117	} else {
118	    $ARGV[0] = substr($ARGV[0], 2);
119	}
120	if ($ARGV[0] eq '1') {
121	    $version = '1';
122	} elsif ($ARGV[0] eq '2c' or $ARGV[0] eq '2') {
123	    $version = '2c';
124	} else {
125	    usage (1);
126	}
127    } elsif ($ARGV[0] =~ /^-m/) {
128	if ($ARGV[0] eq '-m') {
129	    shift @ARGV;
130	    usage (1) unless defined $ARGV[0];
131	} else {
132	    $ARGV[0] = substr($ARGV[0], 2);
133	}
134	if ($ARGV[0] =~ /^[0-9]+$/) {
135	    $max_repetitions = $ARGV[0];
136	} else {
137	    usage (1);
138	}
139    } elsif ($ARGV[0] =~ /^-p/) {
140	if ($ARGV[0] eq '-p') {
141	    shift @ARGV;
142	    usage (1) unless defined $ARGV[0];
143	} else {
144	    $ARGV[0] = substr($ARGV[0], 2);
145	}
146	if ($ARGV[0] =~ /^[0-9]+$/) {
147	    $port = $ARGV[0];
148	} else {
149	    usage (1);
150	}
151    } elsif ($ARGV[0] =~ /^-t/) {
152	if ($ARGV[0] eq '-t') {
153	    shift @ARGV;
154	    usage (1) unless defined $ARGV[0];
155	} else {
156	    $ARGV[0] = substr($ARGV[0], 2);
157	}
158	if ($ARGV[0] =~ /^[0-9]+(\.[0-9]+)?$/) {
159	    $desired_interval = $ARGV[0];
160	} else {
161	    usage (1);
162	}
163    } elsif ($ARGV[0] eq '-B') {
164	$use_getbulk_p = 0;
165    } elsif ($ARGV[0] eq '-s') {
166	$switch_engine_p = 1;
167    } elsif ($ARGV[0] eq '-a') {
168	$all_p = 1;
169    } elsif ($ARGV[0] eq '-c') {
170	$cisco_p = 1;
171    } elsif ($ARGV[0] eq '-l') {
172	$counter64_p = 1;
173    } elsif ($ARGV[0] eq '-n') {
174	$suppress_output = 1;
175	$suppress_curses = 1;
176    } elsif ($ARGV[0] eq '-C') {
177	$suppress_output = 0;
178	$suppress_curses = 1;
179    } elsif ($ARGV[0] eq '-d') {
180	$suppress_output = 0;
181	$suppress_curses = 1;
182	$debug = 1;
183    } elsif ($ARGV[0] eq '-D') {
184	$show_out_discards = 1;
185    } elsif ($ARGV[0] eq '-4') {
186	$ipv4_only_p = 1;
187    } elsif ($ARGV[0] eq '-h') {
188	usage (0);
189	exit 0;
190    } elsif ($ARGV[0] =~ /^-/) {
191	usage (1);
192    } else {
193	if (!defined $host) {
194	    $host = $ARGV[0];
195	} elsif (!defined $community) {
196	    $community = $ARGV[0];
197	} else {
198	    usage (1);
199	}
200    }
201    shift @ARGV;
202}
203defined $host or usage (1);
204defined $community or $community = 'public';
205usage (1) if $#ARGV >= $[;
206
207my $ifDescr = [1,3,6,1,2,1,2,2,1,2];
208my $ifAdminStatus = [1,3,6,1,2,1,2,2,1,7];
209my $ifOperStatus = [1,3,6,1,2,1,2,2,1,8];
210my $ifInOctets = [1,3,6,1,2,1,2,2,1,10];
211my $ifOutOctets = [1,3,6,1,2,1,2,2,1,16];
212my $ifInUcastPkts = [1,3,6,1,2,1,2,2,1,11];
213my $ifOutUcastPkts = [1,3,6,1,2,1,2,2,1,17];
214my $ifOutDiscards = [1,3,6,1,2,1,2,2,1,19];
215my $ifAlias = [1,3,6,1,2,1,31,1,1,1,18];
216## Counter64 variants
217my $ifHCInOctets = [1,3,6,1,2,1,31,1,1,1,6];
218my $ifHCOutOctets = [1,3,6,1,2,1,31,1,1,1,10];
219## Cisco-specific variables enabled by `-c' option
220my $locIfInCRC = [1,3,6,1,4,1,9,2,2,1,1,12];
221my $locIfOutCRC = [1,3,6,1,4,1,9,2,2,1,1,12];
222
223my $cseL3SwitchedTotalPkts = [1,3,6,1,4,1,9,9,97,1,4,1,1,1];
224my $cseL3SwitchedTotalOctets = [1,3,6,1,4,1,9,9,97,1,4,1,1,2];
225my $cseL3CandidateFlowHits = [1,3,6,1,4,1,9,9,97,1,4,1,1,3];
226my $cseL3EstablishedFlowHits = [1,3,6,1,4,1,9,9,97,1,4,1,1,4];
227my $cseL3ActiveFlows = [1,3,6,1,4,1,9,9,97,1,4,1,1,5];
228my $cseL3FlowLearnFailures = [1,3,6,1,4,1,9,9,97,1,4,1,1,6];
229my $cseL3IntFlowInvalids = [1,3,6,1,4,1,9,9,97,1,4,1,1,7];
230my $cseL3ExtFlowInvalids = [1,3,6,1,4,1,9,9,97,1,4,1,1,8];
231
232my $clock_ticks = POSIX::sysconf( &POSIX::_SC_CLK_TCK );
233
234my $win = new Curses
235    unless $suppress_curses;
236
237my %old;
238my $sleep_interval = $desired_interval + 0.0;
239my $interval;
240my $linecount;
241
242sub rate_32 ($$$@) {
243    my ($old, $new, $interval, $multiplier) = @_;
244    $multiplier = 1 unless defined $multiplier;
245    my $diff = $new-$old;
246    if ($diff < 0) {
247	$diff += (2**32);
248    }
249    return $diff / $interval * $multiplier;
250}
251
252sub rate_64 ($$$@) {
253    my ($old, $new, $interval, $multiplier) = @_;
254    $multiplier = 1 unless defined $multiplier;
255    return 0 if $old == $new;
256    my $diff = Math::BigInt->new ($new-$old);
257    if ($diff < 0) {
258	$diff = $diff->add (2**64);
259    }
260    warn "rate_64 ($old, $new, $interval, $multiplier)\n"
261	if $debug;
262    warn "  diff: $diff\n"
263	if $debug;
264    ## hrm.  Why is this so complicated?
265    ## I want a real programming language (such as Lisp).
266    my $result = new Math::BigFloat ($diff->bnorm ());
267    warn "  result: $result\n"
268	if $debug;
269    $result /= $interval;
270    warn "  result: $result\n"
271	if $debug;
272    $result *= $multiplier;
273    warn "  result: $result\n"
274	if $debug;
275    return $result;
276}
277
278sub rate ($$$$@) {
279    my ($old, $new, $interval, $counter64_p, $multiplier) = @_;
280    $multiplier = 1 unless defined $multiplier;
281    return $counter64_p
282	? rate_64 ($old, $new, $interval, $multiplier)
283	: rate_32 ($old, $new, $interval, $multiplier);
284}
285
286sub rate_or_0 ($$$@) {
287    my ($old, $new, $interval, $counter64_p, $multiplier) = @_;
288    $counter64_p = 0 unless defined $counter64_p;
289    $multiplier = 1 unless defined $multiplier;
290    return defined $new
291	? rate ($old, $new, $interval, $counter64_p, $multiplier)
292	: 0;
293}
294
295sub out_interface ($$$$$$@) {
296    my ($index, $descr, $admin, $oper, $in, $out);
297    my ($crc, $comment);
298    my ($drops);
299    my ($clock) = POSIX::times();
300    my $alarm = 0;
301
302    ($index, $descr, $admin, $oper, $in, $out, $comment, @_) = @_;
303    ($crc, @_) = @_ if $cisco_p;
304    ($drops, @_) = @_ if $show_out_discards;
305
306    grep (defined $_ && ($_=pretty_print $_),
307	  ($descr, $admin, $oper, $in, $out, $crc, $comment, $drops));
308    $win->clrtoeol ()
309	unless $suppress_curses;
310    return unless $all_p || defined $oper && $oper == 1; # up
311    return unless defined $in && defined $out;
312    ## Suppress interfaces called "unrouted VLAN..."
313    return if $descr =~ /^unrouted VLAN/;
314    if (!defined $old{$index}) {
315	if ($suppress_output) {
316	    # do nothing
317	} elsif ($suppress_curses) {
318	    printf STDOUT ("%5d  %-24s %10s %10s",
319			   $index,
320			   defined $descr ? $descr : '',
321			   defined $in ? $in : '-',
322			   defined $out ? $out : '-');
323	} else {
324	    $win->addstr ($linecount, 0,
325			  sprintf ("%5d  %-24s %10s %10s",
326				   $index,
327				   defined $descr ? $descr : '',
328				   defined $in ? $in : '-',
329				   defined $out ? $out : '-'));
330	}
331	if ($show_out_discards) {
332	    if ($suppress_output) {
333		# do nothing
334	    } elsif ($suppress_curses) {
335		printf STDOUT (" %8s", defined $drops ? $drops : '-');
336	    } else {
337		$win->addstr (sprintf (" %8s",
338				       defined $drops ? $drops : '-'));
339	    }
340	}
341	if ($cisco_p) {
342	    if ($suppress_output) {
343		# do nothing
344	    } elsif ($suppress_curses) {
345		printf STDOUT (" %10s", defined $crc ? $crc : '-');
346	    } else {
347		$win->addstr (sprintf (" %10s",
348				       defined $crc ? $crc : '-'));
349	    }
350	}
351	if ($suppress_output) {
352	    # do nothing
353	} elsif ($suppress_curses) {
354	    printf STDOUT (" %s", defined $comment ? $comment : '');
355	} else {
356	    $win->addstr (sprintf (" %s", defined $comment ? $comment : ''));
357	}
358	print "\n" if !$suppress_output and $suppress_curses;
359    } else {
360	my $old = $old{$index};
361
362	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
363	my $d_in = rate_or_0 ($old->{'in'}, $in, $interval, $counter64_p, 8);
364	my $d_out = rate_or_0 ($old->{'out'}, $out, $interval, $counter64_p, 8);
365	my $d_drops = rate_or_0 ($old->{'drops'}, $drops, $interval, 0);
366	my $d_crc = rate_or_0 ($old->{'crc'}, $crc, $interval, 0);
367	$alarm = ($d_crc != 0)
368	    || 0 && ($d_out > 0 && $d_in == 0);
369	print STDERR "\007" if $alarm && !$old->{'alarm'};
370	print STDERR "\007" if !$alarm && $old->{'alarm'};
371	$win->standout() if $alarm && !$suppress_curses;
372	if ($suppress_output) {
373	    # do nothing
374	} elsif ($suppress_curses) {
375	    printf STDOUT ("%5d  %-24s %s %s",
376			   $index,
377			   defined $descr ? $descr : '',
378			   pretty_ps ($in, $d_in),
379			   pretty_ps ($out, $d_out));
380	} else {
381	    $win->addstr ($linecount, 0,
382			  sprintf ("%5d  %-24s %s %s",
383				   $index,
384				   defined $descr ? $descr : '',
385				   pretty_ps ($in, $d_in),
386				   pretty_ps ($out, $d_out)));
387	}
388	if ($show_out_discards) {
389	    if ($suppress_output) {
390		# do nothing
391	    } elsif ($suppress_curses) {
392		printf STDOUT (" %8.1f %s", defined $drops ? $d_drops : 0);
393	    } else {
394		$win->addstr (sprintf (" %8.1f %s",
395				       defined $drops ? $d_drops : 0));
396	    }
397	}
398	if ($cisco_p) {
399	    if ($suppress_output) {
400		# do nothing
401	    } elsif ($suppress_curses) {
402		printf STDOUT (" %10.1f", defined $crc ? $d_crc : 0);
403	    } else {
404		$win->addstr (sprintf (" %10.1f",
405				       defined $crc ? $d_crc : 0));
406	    }
407	}
408	if ($suppress_output) {
409	    # do nothing
410	} elsif ($suppress_curses) {
411	    printf STDOUT (" %s", defined $comment ? $comment : '');
412	} else {
413	    $win->addstr (sprintf (" %s",
414				   defined $comment ? $comment : ''));
415	}
416	$win->standend() if $alarm && !$suppress_output;
417	print "\n" if !$suppress_output and $suppress_curses;
418    }
419    $old{$index} = {'in' => $in,
420		    'out' => $out,
421		    'crc' => $crc,
422		    'drops' => $drops,
423		    'clock' => $clock,
424		    'alarm' => $alarm};
425    ++$linecount;
426    $win->refresh ()
427	unless $suppress_output;
428}
429
430sub out_switching_engine ($$$$$$@) {
431    my ($index,
432	$pkts, $octets,
433	$candidate_flow_hits,
434	$established_flow_hits,
435	$active_flows,
436	$flow_learn_failures,
437	$int_flow_invalids,
438	$ext_flow_invalids) = @_;
439    my ($clock) = POSIX::times();
440    my $alarm = 0;
441
442    grep (defined $_ && ($_=pretty_print $_),
443	  ($pkts, $octets,
444	   $candidate_flow_hits,
445	   $established_flow_hits,
446	   $active_flows,
447	   $flow_learn_failures,
448	   $int_flow_invalids,
449	   $ext_flow_invalids));
450    warn "RETRIEVED: pkts: $pkts\noctets: $octets\n"
451	if $debug;
452    $win->clrtoeol ()
453	unless $suppress_curses;
454    return unless defined $pkts and defined $octets;
455    ## Suppress interfaces called "unrouted VLAN..."
456    if (!defined $old{$index}) {
457	if ($suppress_output) {
458	    # do nothing
459	} elsif ($suppress_curses) {
460	    printf STDOUT ("%5d %10s %10s\n",
461			   $index,
462			   defined $pkts ? $pkts : '-',
463			   defined $octets ? $octets : '-');
464	} else {
465	    $win->addstr ($linecount, 0,
466			  sprintf ("%5d %10s %10s",
467				   $index,
468				   defined $pkts ? $pkts : '-',
469				   defined $octets ? $octets : '-'));
470	}
471    } else {
472	my $old = $old{$index};
473
474	$interval = ($clock-$old->{'clock'}) * 1.0 / $clock_ticks;
475	my $d_pkts = rate_or_0 ($old->{'pkts'}, $pkts, $interval, 0);
476	my $d_octets = rate_or_0 ($old->{'octets'}, $octets, $interval, 1, 8);
477	warn "RATE: pkts: $d_pkts\nbits: $d_octets\n"
478	    if $debug;
479	$alarm = 0;
480	print STDERR "\007" if $alarm && !$old->{'alarm'};
481	print STDERR "\007" if !$alarm && $old->{'alarm'};
482	$win->standout() if $alarm && !$suppress_curses;
483	if ($suppress_output) {
484	    # do nothing
485	} elsif ($suppress_curses) {
486	    printf STDOUT ("%2d  %s %s",
487			   $index,
488			   pretty_ps ($pkts, $d_pkts),
489			   pretty_ps ($octets, $d_octets));
490	} else {
491	    $win->addstr ($linecount, 0,
492			  sprintf ("%2d  %s %s",
493				   $index,
494				   pretty_ps ($pkts, $d_pkts),
495				   pretty_ps ($octets, $d_octets)));
496	}
497	$win->standend() if $alarm && !$suppress_curses;
498	print "\n" if !$suppress_output and $suppress_curses;
499    }
500    $old{$index} = {'pkts' => $pkts,
501		    'octets' => $octets,
502		    'clock' => $clock,
503		    'alarm' => $alarm};
504    ++$linecount;
505    $win->refresh ()
506	unless $suppress_curses;
507}
508
509sub pretty_ps ($$) {
510    my ($count, $bps) = @_;
511    if (! defined $count) {
512	return '      -   ';
513    } elsif ($bps > 1000000) {
514	return sprintf ("%8.4f M", $bps/1000000);
515    } elsif ($bps > 1000) {
516	return sprintf ("%9.1fk", $bps/1000);
517    } else {
518	return sprintf ("%10.0f", $bps);
519    }
520}
521
522$win->erase ()
523    unless $suppress_curses;
524my $session =
525    ($version eq '1' ? SNMPv1_Session->open ($host, $community, $port, undef, undef, undef, undef, $ipv4_only_p)
526     : $version eq '2c' ? SNMPv2c_Session->open ($host, $community, $port, undef, undef, undef, undef, $ipv4_only_p)
527     : die "Unknown SNMP version $version")
528  || die "Opening SNMP_Session";
529$session->debug (1) if $debug;
530$use_getbulk_p = 0 if $version eq '1';
531$session->{'use_getbulk'} = 0 unless $use_getbulk_p;
532
533### max_repetitions:
534###
535### We try to be smart about the value of $max_repetitions.  Starting
536### with the session default, we use the number of rows in the table
537### (returned from map_table_4) to compute the next value.  It should
538### be one more than the number of rows in the table, because
539### map_table needs an extra set of bindings to detect the end of the
540### table.
541###
542$max_repetitions = $session->default_max_repetitions
543    unless $max_repetitions;
544while (1) {
545    unless ($suppress_output) {
546	if ($suppress_curses) {
547	    printf STDOUT ("interval: %4.1fs %d reps\n",
548			   $interval || $desired_interval,
549			   $max_repetitions);
550	} else {
551	    $win->addstr (0, 0, sprintf ("%-20s interval %4.1fs %d reps",
552					 $host,
553					 $interval || $desired_interval,
554					 $max_repetitions));
555	    $win->standout();
556	    $win->addstr (1, 0,
557			  sprintf (("%5s  %-24s %10s %10s"),
558				   "index", "name",
559				   "bits/s", "bits/s"));
560	    if ($show_out_discards) {
561		$win->addstr (sprintf ((" %8s"),
562				       "drops/s"));
563	    }
564	    if ($cisco_p) {
565		$win->addstr (sprintf ((" %10s"), "pkts/s"));
566	    }
567	    $win->addstr (sprintf ((" %s"), "description"));
568	    $win->addstr (2, 0,
569			  sprintf (("%2s  %-24s %10s %10s"),
570				   "", "",
571				   "in", "out"));
572	    if ($show_out_discards) {
573		$win->addstr (sprintf ((" %8s"),
574				       ""));
575	    }
576	    if ($cisco_p) {
577		$win->addstr (2, 0,
578			      sprintf ((" %10s %s"),
579				       "CRC",
580				       ""));
581	    }
582	    $win->clrtoeol ();
583	    $win->standend();
584	}
585    }
586    $linecount = 3;
587    my @oids;
588
589    if ($switch_engine_p) {
590	@oids = (
591		 $cseL3SwitchedTotalPkts,
592		 $cseL3SwitchedTotalOctets,
593		 $cseL3CandidateFlowHits,
594		 $cseL3EstablishedFlowHits,
595		 $cseL3ActiveFlows,
596		 $cseL3FlowLearnFailures,
597		 $cseL3IntFlowInvalids,
598		 $cseL3ExtFlowInvalids
599		 );
600    } else {
601	@oids = ($ifDescr,$ifAdminStatus,$ifOperStatus);
602	if ($counter64_p) {
603	    @oids = (@oids,$ifHCInOctets,$ifHCOutOctets);
604	} else {
605	    @oids = (@oids,$ifInOctets,$ifOutOctets);
606	}
607	@oids = (@oids,$ifAlias);
608	if ($cisco_p) {
609	    push @oids, $locIfInCRC;
610	}
611	if ($show_out_discards) {
612	    push @oids, $ifOutDiscards;
613	}
614    }
615    my $calls =
616	$switch_engine_p
617	? $session->map_table_4
618	(\@oids, \&out_switching_engine, $max_repetitions)
619	: $session->map_table_4
620	(\@oids, \&out_interface, $max_repetitions);
621    $win->clrtobot (), $win->refresh ()
622	unless $suppress_curses;
623    $max_repetitions = $calls + 1
624	if $calls > 0;
625    $sleep_interval -= ($interval - $desired_interval)
626	if defined $interval;
627    select (undef, undef, undef, $sleep_interval);
628}
6291;
630
631sub usage ($) {
632    warn <<EOM;
633Usage: $0 [-t secs] [-v (1|2c)] [-c] [-l] [-m max] [-4] [-p port] host [community]
634       $0 -h
635
636  -h           print this usage message and exit.
637
638  -c           also use Cisco-specific variables (locIfInCrc)
639
640  -l           use 64-bit counters (requires SNMPv2 or higher)
641
642  -t secs      specifies the sampling interval.  Defaults to 5 seconds.
643
644  -v version   can be used to select the SNMP version.  The default
645   	       is SNMPv1, which is what most devices support.  If your box
646   	       supports SNMPv2c, you should enable this by passing "-v 2c"
647   	       to the script.  SNMPv2c is much more efficient for walking
648   	       tables, which is what this tool does.
649
650  -B           do not use get-bulk
651
652  -m max       specifies the maxRepetitions value to use in getBulk requests
653               (only relevant for SNMPv2c).
654
655  -4           use only IPv4 addresses, even if host also has an IPv6
656               address.  Use this for devices that are IPv6-capable
657               but whose SNMP agent doesn\'t listen to IPv6 requests.
658
659  -m port      can be used to specify a non-standard UDP port of the SNMP
660               agent (the default is UDP port 161).
661
662  host         hostname or IP address of a router
663
664  community    SNMP community string to use.  Defaults to "public".
665EOM
666    exit (1) if $_[0];
667}
668