#!/opt/vdops/bin/perl # This script uses the CISCO-PING-MIB to ping a list of hosts (read from a # file), producing a report listing the hosts which did not respond. It # optionally accepts a VRF name as a command-line parameter # V Who When What # --------------------------------------------------------------------------- # 1.2.0 skendric 2011-02-21 Upgrade to Netops 1.4.0 # 1.1.1 skendric 2010-05-23 Remove empty snmpSet # 1.1.0 skendric 2010-02-05 Upgrade to perl 5.10.1 # 1.0.0 skendric 2009-08-09 First Version # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/polling # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # # This script takes the following approach: # -Reads a file containing a list of names or IP addresses # -Pings them using the CISCO-PING-MIB # -Produces a report listing the devices which did not answer # # # Requirements: # -The following MIB modules stashed in /opt/vdops/share/snmp/mibs, # or wherever it is that you store MIB modules: # CISCO-PRODUCTS-MIB.my # # -PERL modules: the WI::Netops collection # # # Assumptions: # # # Tested on: # -perl-5.12.2 # -net-snmp-5.6 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Type "proxy-ping-alarm" to see the command-line options # -Try it out # # # # Caveats: # # # Known Bugs: # # # To do: # -Add support for SNMPv3 # # Begin script # Load modules use strict; use warnings; use feature 'say'; use feature 'switch'; use Carp qw(carp cluck croak confess); use Data::Dumper; use Data::Validate::IP qw(is_ipv4); use English qw( -no_match_vars ); use Getopt::Std; use Net::Netmask; use Regexp::Common; use Socket; use Tie::File; use WI::Netops::CiscoTools 1.4.3; use WI::Netops::HostTools 1.0.4; use WI::Netops::NetopsTools 2.2.3; use WI::Netops::NetopsData 1.4.0; use WI::Netops::PingTools 1.1.7; use WI::Netops::SNMPTools 1.5.3; use WI::Netops::Utilities 1.4.4; # Declare global variables my $dead; # Count of hosts which didn't answer any pings my $host_file; # File containing list of IP addresses to ping my %hostname_of; # Hash of hostnames keyed by IP address my %missed_pings_for; # Hash of the number of pings missed, keyed by # IP address my $misses; # Count of hosts which missed some (but not all) # pings my %packed_form_of; # Hash of IP addresses in packed format, keyed # by IP address in decimal format my $problems; # Count of hosts which we were unable to ping due # to errors my $router; # IOS device which will act as the source of the # pings. This device must support CISCO-PING-MIB my $live; # Count of hosts which returned all pings my $vrf; # Name of the VRF (if any) # Define global variables $program_name = 'proxy-ping-alarm'; $usage = 'Usage: proxy-ping-alarm -s {yes|no} [-v {VRF name}] [-d {integer}] [-r] -p {router} ...]'; $version = '1.2.0'; # Initialize to zero $dead = 0; $misses = 0; $problems = 0; $live = 0; # Grab arguments getopts('d:h:p:rs:v:', \%option); @target = @ARGV; # Validate args $vrf = $option{v} if defined $option{v}; die 'Must specify -h {host file}' unless defined $option{h}; $host_file = $option{h}; die "The file $host_file does not exist\n" unless -e $host_file; die "The file $host_file contains zero bytes\n" unless -s $host_file; die "The file $host_file is not readable\n" unless -r $host_file; die 'Must specify -p {router}' unless defined $option{p}; $target[0] = $option{p}; $router = $option{p}; # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } ### Begin Main Program ############################################### { check_args(); # Check arguments read_config(); # Read Netops config file compile_mibs(); # Compile MIB files build_target(); # Populate @target target_check(); # Look for errors in @target basic_info(); # Gather information process_host_file(); # Produce hashes of hostnames and octal addr do_the_work(); # Do the work print_report(); # Print report notify_staff(); # Mail report } ##### End Main Program ############################################### ######################################################################## # Do the work: ping the addresses ######################################################################## sub do_the_work { my $target = $target[0]; my %used_numbers; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Pinging hosts...'); # Loop through hosts IP: for my $ip (keys %hostname_of) { my (%arg, $host, $packed_ip, $random, $sent, $status, $received, $val, @varbind); $host = $hostname_of{$ip}; $packed_ip = $packed_form_of{$ip}; # Produce a random number which we haven't used yet RANDOM: for (my $i = 0; $i < 10; $i++) { $random = int(rand 9999); if (defined $used_numbers{$random}) { log_it("Already seen random number $random, try again"); next RANDOM; } else { last RANDOM; } } $used_numbers{$random} = $random; # Debug info say "Processing $host / $ip" if $debug > 1; # Create the row instance say "Adding ciscoPingEntryStatus.$random i 5 (createAndWait)" if $debug>3; push @varbind, 'ciscoPingEntryStatus', $random, 5, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set owner say "Adding ciscoPingEntryOwner.$random s $program_name.$random" if $debug > 3; push @varbind, 'ciscoPingEntryOwner', $random, "$program_name.$random", 'OCTETSTR'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set ping protocol say "Adding ciscoPingProtocol.$random i 1" if $debug > 3; push @varbind, 'ciscoPingProtocol', $random, 1, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set ping address say "Adding ciscoPingAddress.$random x $ip" if $debug > 3; push @varbind, 'ciscoPingAddress', $random, $packed_ip, 'OCTETSTR'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set packet count say "Adding ciscoPingPacketCount.$random i $ping_count" if $debug > 3; push @varbind, 'ciscoPingPacketCount', $random, $ping_count, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set packet size say "Adding ciscoPingPacketSize.$random i 100" if $debug > 3; push @varbind, 'ciscoPingPacketSize', $random, 100, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set timeout (milliseconds) $ping_timeout = 1000; say "Adding ciscoPingPacketTimeout.$random i $ping_timeout" if $debug > 3; push @varbind, 'ciscoPingPacketTimeout', $random, $ping_timeout, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Set VRF if (defined $vrf) { say "Adding ciscoPingVrfName.$random s $vrf" if $debug > 3; push @varbind, 'ciscoPingVrfName', $random, $vrf, 'OCTETSTR'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; } # Verify that we are ready to go $status = snmpGet({host => $router, oid => "ciscoPingEntryStatus.$random"}); unless ($status eq 'notInService' or $status eq 'notReady') { print_it("\nciscoPingEntryStatus.$random is $status, skipping $ip"); next IP; } # Do the ping say "Pinging $ip / $host" if $debug; say "Setting ciscoPingEntryStatus.$random i 1 (active)" if $debug > 3; push @varbind, 'ciscoPingEntryStatus', $random, 1, 'INTEGER'; $val = snmpSet( {host => $router, varbind => \@varbind} ); undef @varbind; # Pause to give the ping time to finish sleep 1; # Have we finished pinging? $status = snmpGet({host => $router, oid => "ciscoPingCompleted.$random"}); unless (defined $status) { log_it("snmpSet ciscoPing is failing"); next IP; } # Dawdle, then check again unless ($status eq 'true') { sleep $ping_count * $ping_timeout/1000 + $short; # Sleep some more $status = snmpGet({host => $router, oid => "ciscoPingCompleted.$random"}); unless ($status eq 'true') { print_it("\nPinging to $ip has not finished, skipping"); next IP; } } # How many pings did we send? $sent = snmpGet( {host => $router, oid => "ciscoPingSentPackets.$random"} ); unless (defined $sent) { print_it("\nSent packets for $ip not defined, skipping"); next IP; } unless ($RE{num}{int}->matches($sent)) { print_it("Sent packets $sent for $ip not an integer, skipping"); next IP; } # How many responses did we receive? $received = snmpGet( {host=>$router, oid=>"ciscoPingReceivedPackets.$random"} ); unless (defined $received) { print_it("\nReceived packets for $ip not defined, skipping"); next IP; } unless ($RE{num}{int}->matches($received)) { print_it("\nReceived packets $received for $ip not an integer, skipping"); next IP; } # Store the result $missed_pings_for{$ip} = $sent - $received; given ($missed_pings_for{$ip}) { when ($_ == $ping_count) { log_it("$ip is not answering") } when ($_ > 0 ) { log_it("$ip is missing pings") } } # Clean up (destroy temporary table) destroy_table({host=>$router, table=>'ccCopyEntryRowStatus', iid=>$random}); # Entertain operator if ($mode eq 'interactive') { given ($missed_pings_for{$ip}) { when ($_ > 0) { print $DOT } default { print $BANG } } } } # Make things look pretty say "\n" if $mode eq 'interactive'; # Process the results for my $ip (keys %hostname_of) { $missed_pings_for{$ip} = $DASH unless defined $missed_pings_for{$ip}; given ($missed_pings_for{$ip}) { when ('0') { $live++; } when ($DASH) { $problems++; $shit_happens++; push @alarm_list, $ip; } when ($ping_count) { $dead++; $shit_happens++; push @alarm_list, $ip; } when ($_ > 0) { $misses++; $shit_happens++; push @alarm_list, $ip; } default { say "Unknown value $_ for $ip"; $shit_happens++; push @alarm_list, $ip; } } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub print_report { my $handle; my $total = keys %hostname_of; my $now = get_now(); # Debug trace trace_location('begin') if $debug; # If we are running in test mode, skip this routine unless ($dome) { print_it("Running in test mode, cannot print a meaningful report\n"); return 1; } # Direct output to screen or to file if ($mode eq 'interactive') { $handle = *STDOUT; } else { open $handle, '>', $report_file or die "Cannot open $report_file: $!\n"; } print {$handle} <) { my ($ip, $name); # Skip comments and blank lines next LINE if $line =~ /^#|^\s*\Z/; chomp $line; # Grab first column and (optional) second column ($ip, $name) = ($line =~ /(.*?)\s+(.*)/); # Complain if the IP address is malformed unless (is_ipv4($ip)) { say "$line does not start with a valid IP address, skipping"; next LINE; } # If we have the IP address and name already, save them if (defined $name and $name ne $EMPTY_STR) { $hostname_of{$ip} = $name; } # Otherwise, dig for the name else { $name = get_nodename($line); $hostname_of{$ip} = defined $name ? $name : 'unknown'; } } # Convert IP addresses to octal for my $ip (keys %hostname_of) { $packed_form_of{$ip} = inet_aton($ip); } # Debug trace trace_location('end') if $debug; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <