#!/opt/vdops/bin/perl # This script identifies ports on Cisco switches which have dropped packets # due to a Quality of Service feature. Requires CISCO-CLASS-BASED-QOS-MIB # or CISCO-PORT-QOS-MIB support # V Who When What # --------------------------------------------------------------------------- # 1.2.0 skendric 2011-02-21 Upgrade to Netops 1.4.0 # 1.1.4 skendric 2010-12-30 Add @insane to report # 1.1.3 skendric 2010-12-17 Futz with owner/owner_backup # 1.1.2 skendric 2010-07-27 Log QoS drop count # 1.1.1 skendric 2010-05-23 Increment alarm_count # 1.1.0 skendric 2010-05-21 Add skip_interface and down_for_maintenance # 1.0.1 skendric 2010-03-30 Upgrade to perl 5.10.1 # 1.0.0 skendric 2007-09-17 First Version # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/device # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # # This script takes the following approach: # -Parses the hosts table for a list of targets (or accepts a command- # line list) # -Identifies the ports on switches where the cbQosCMDropPkt64 or # cbQosCMNoBufDropPkt64 counters are non-zero # -Produces a report lists these switches/ports # # # 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 # -Try it out # # # # Caveats: # # # Known Bugs: # # # To do: # -In the report, specify which modules contain afflicted ports # -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 English qw( -no_match_vars ); use Getopt::Std; use List::MoreUtils qw(any); 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 %dropped_frames; # Hash of scalars, where the scalar records the # number of frames dropped by the key, which # consists of {device-name if-name} my %dropping_if; # Hash of arrays listing the ports which have # experienced drops, keyed by switch name my %qos_type; # Hash holding either 'port' or 'class', # representing support for either the # CISCO-PORT-QOS-MIB or # CISCO-CLASS-BASED-QOS-MIB my %skip_interface; # Hash of switches keyed by switchName-interface # These are interfaces with known issues which # we want to ignore, i.e. which we know about # and which we do not want triggering e-mail # Define global variables $program_name = 'qos-drops-alarm'; $usage = 'Usage: qos-drops-alarm -s {yes|no} [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.2.0'; $mid = 10; $short = 5; # Ping Stuff $ping_count = 3; $ping_timeout = 1; # Skip specific target / interface combinations %skip_interface = ( "cabrini-esx Fa1/0/22" => 1, "colo-core-rtr Gi1/0/28" => 1, ); # Target details @skip_name = qw//; @suffixes = qw/-esx -rtr/; # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # 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 info_before(); # Identify QOS MIB support sanity_check(); # Check for errorsCheck for errors do_the_work(); # Do the work identify_alarms(); # Count devices with alarms write_alarm_log(); # Record issues print_report(); # Print report notify_staff(); # Mail report } ##### End Main Program ############################################### ######################################################################## # Do the work ######################################################################## sub do_the_work { # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Acquiring QoS drop counts...'); # Loop through targets TARGET: for my $target (@target) { my (%arg, $drop_ref, $if_name); # Skip targets which we know are problematic if (any {$_ eq $target} @down_for_maintenance) { log_it("Skipping $target because it is down for maintenance"); next TARGET; } # Switch on MIB support (CISCO-CLASS-BASED-QOS-MIB or CISCO-PORT-QOS-MIB) given ($qos_type{$target}) { when ('class') { my $class_drop_ref; say "$target uses CISCO-CLASS-BASED-QOS-MIB" if $debug; # Acquire cbQosCMDropPkt64 $class_drop_ref = snmpWalk( {host=>$target, oid=>'cbQosCMDropPkt64'} ); unless (defined $class_drop_ref and @$class_drop_ref > 0) { log_it("$target not responding to cbQosCMDropPkt64 walk, skipping"); print $DOT if $mode eq 'interactive'; next TARGET; } # Look for drops INTERFACE: for my $varbind (@$class_drop_ref) { my $drops = $varbind->{val}; if ($drops != 0) { my (%arg, $iid, $q_index, $if_index, $if_name); $iid = $varbind->{iid}; ($q_index) = ($iid =~ /(\d+)\.\d+/); # Find ifIndex %arg = ( host => $target, oid => "cbQosIfIndex.$q_index" ); $if_index = snmpGet(\%arg); unless (defined $if_index and $if_index ne $EMPTY_STR) { push @{$dropping_if{$target}}, $QUERY; next INTERFACE; } # Get ifName $if_name = snmpGet( {host=>$target, oid=>"$ifName_oid.$if_index"} ); $if_name = $QUERY unless (defined $if_name and $if_name ne ''); # Save results $dropped_frames{"$target $if_name"} = $drops; given ($skip_interface{"$target $if_name"}) { when (defined) { log_it("For $target, $if_name reports $drops QoS drops; ignore because it belongs to \%skip_interface"); } when (undef) { log_it("For $target, $if_name reports $drops QoS drops"); push @{$dropping_if{$target}}, $if_name; $alarm_count{$target}++; } } } # End '$varbind->{val} != 0' } # End 'Look for drops' } # End case 'class' when ('port') { my $port_drop_ref; say "$target uses CISCO-PORT-QOS-MIB" if $debug; # Acquire cportQosDropPkts $port_drop_ref = snmpWalk( {host=>$target, oid=>'cportQosDropPkts'} ); unless (defined $port_drop_ref and @$port_drop_ref > 0) { log_it("$target not responding to cportQosDropPkts walk, skipping"); print $DOT if $mode eq 'interactive'; next TARGET; } # Look for drops INTERFACE: for my $varbind (@$port_drop_ref) { my $drops; ($drops) = ($varbind->{val} =~ /(\d+)/); if ($drops != 0) { my $if_index; # Dig out if_index ($if_index) = ($varbind->{iid} =~ /^(\d+)\./); unless (defined $if_index and $if_index ne $EMPTY_STR) { log_it("Cannot parse $if_index for $target, skipping"); next INTERFACE; } # Get ifName say "Getting ifName.$if_index" if $debug > 3; $if_name = snmpGet( {host=>$target, oid=>"$ifName_oid.$if_index"} ); $if_name = $QUERY unless (defined $if_name and $if_name ne ''); # Save results $dropped_frames{"$target $if_name"} = $drops; given ($skip_interface{"$target $if_name"}) { when (defined) { log_it("For $target, $if_name reports $drops QoS drops; ignore because it belongs to \%skip_interface"); } when (undef) { log_it("For $target, $if_name reports $drops QoS drops"); push @{$dropping_if{$target}}, $if_name; $alarm_count{$target}++; } } } # End '$varbind->{val} != 0 } # End 'Look for drops' } # End case 'port' } # End 'switch' # Entertain operator print $BANG if $mode eq 'interactive'; } # End 'Loop through targets' # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Gather more info ######################################################################## sub info_before { my @remove; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Identifying QOS MIB support...'); # Loop through targets, looking for problems TARGET: for my $target (@target) { my (%arg, $class_ref, $cport); # Does this device support CISCO-CLASS-BASED-QOS-MIB? say 'Walking cbQosIfType' if $debug > 3; %arg = ( host => $target, oid => '.1.3.6.1.4.1.9.9.166.1.1.1.1.2' ); $class_ref = snmpWalk(\%arg); if (@$class_ref > 0) { $qos_type{$target} = 'class'; print $BANG if $mode eq 'interactive'; say "$target uses QoS type $qos_type{$target}" if $debug; next TARGET; } # Does this device support CISCO-PORT-QOS-MIB? say 'Getting cportQosIndexTypeNew.0' if $debug > 3; %arg = ( host => $target, oid => '.1.3.6.1.4.1.9.9.189.1.3.3.0' ); $cport = snmpGet(\%arg); if (defined $cport and $cport ne $EMPTY_STR) { $qos_type{$target} = 'port'; print $BANG if $mode eq 'interactive'; say "$target uses QoS type $qos_type{$target}" if $debug; next TARGET; } print $DOT if $mode eq 'interactive'; } # Remove entries which failed checks prune_basic(@remove); @insane = @remove; # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub print_report { my @fields; my $handle; my $total = @target; 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; } # Count number of switches $shit_happens = keys %dropping_if; # 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} <