Last active
March 29, 2018 09:33
-
-
Save bldewolf/6314435 to your computer and use it in GitHub Desktop.
A small Perl script using Net::SNMP to check port channel health on a given Cisco device. Exit values should be Nagios-compatible.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use strict; | |
use warnings; | |
use Net::SNMP; | |
use Getopt::Long; | |
sub snmp_error { | |
my $session = shift; | |
print "SNMP error: ", $session->error(), "\n"; | |
exit 3; | |
} | |
# Pull a whole table, chop off the oid prefixes | |
sub clean_table { | |
my ($session, $oid, $nonfatal) = @_; | |
my $raw = $session->get_table($oid); | |
if(!defined $raw) { | |
# Yes, this is how you detect the error type, unfortunately. | |
if(!$nonfatal || $session->error() ne "The requested table is empty or does not exist") { | |
snmp_error($session); | |
} | |
} | |
my %table; | |
for my $key (keys %{$raw}) { | |
my $index = $key; | |
$index =~ s/^$oid\.//; | |
$table{$index} = $raw->{$key}; | |
} | |
return %table; | |
} | |
# Pull a subset of a table, chop off oid prefixes | |
sub clean_table_oids { | |
my ($session, $oid, @oids) = @_; | |
my $raw; | |
if(@oids) { # skip the lookup if we don't have any OIDs | |
$raw = $session->get_request(-varbindlist => \@oids); | |
snmp_error($session) if(!defined $raw); | |
} | |
my %table; | |
for my $key (keys %{$raw}) { | |
my $index = $key; | |
$index =~ s/^$oid\.//; | |
$table{$index} = $raw->{$key}; | |
} | |
return %table; | |
} | |
# IF-MIB::ifDescr | |
my $ifdescr_oid = ".1.3.6.1.2.1.2.2.1.2"; | |
# CISCO-PAGP-MIB::pagpGroupIfIndex | |
my $group_oid = ".1.3.6.1.4.1.9.9.98.1.1.1.1.8"; | |
# CISCO-PAGP-MIB::pagpAdminGroupCapability | |
my $admin_group_oid = ".1.3.6.1.4.1.9.9.98.1.1.1.1.5"; | |
# CISCO-PAGP-MIB::pagpEthcOperationMode | |
my $mode_oid = ".1.3.6.1.4.1.9.9.98.1.1.1.1.1"; | |
my $switch; | |
my $community; | |
my $version = 2; | |
my $result = GetOptions ("switch=s" => \$switch, | |
"version=s" => \$version, | |
"community=s" => \$community); | |
die "Need args" if(!defined $switch or !defined $community or !$result); | |
my ($session, $err) = Net::SNMP->session( -hostname => $switch, -version => $version, -community => $community); | |
if (!defined($session)) { | |
print "Can't create SNMP session to $switch: $err\n"; | |
exit(3); | |
} | |
# Collect administrative grouping settings (configured etherchannel group number). | |
# | |
# When querying broken interfaces, we can't tell what the ifindex of their | |
# intended port channel is, only what the administrative group they're supposed | |
# to be in is. So we grab all of the groups, hope there's some active members, | |
# and use those to figure out what port channel the inactive members are in. | |
# | |
my %mode = clean_table($session, $mode_oid, 1); | |
for my $mode (keys %mode) { # skip ports in mode 'off' | |
delete $mode{$mode} if($mode{$mode} == 1); | |
} | |
my %group = clean_table_oids($session, $group_oid, | |
map { "$group_oid.$_" } keys %mode); | |
my %admin = clean_table_oids($session, $admin_group_oid, | |
map { "$admin_group_oid.$_" } keys %mode); | |
my %inv_admin; | |
my %broken; | |
for my $key (keys %admin) { | |
# Group up interface by admin group number | |
$inv_admin{$admin{$key}}{members}{$key} = 1; | |
if($group{$key} != 0) { # set ifIndex of virtual if not broken | |
$inv_admin{$admin{$key}}{virtual} = $group{$key} | |
} else { | |
$broken{$key} = 1; | |
} | |
} | |
# Ah, easy! | |
if(!keys %broken) { | |
print "No broken port channel members found.\n"; | |
exit 0; | |
} | |
# Build a list of names we need to look up | |
my %ifindex; | |
for my $key (keys %broken) { | |
$ifindex{$key} = 1; | |
$ifindex{$inv_admin{$admin{$key}}{virtual}} = 1 | |
if(exists $inv_admin{$admin{$key}}{virtual}); | |
} | |
# We could use something other than ifdescr here for prettier names | |
%ifindex = clean_table_oids($session, $ifdescr_oid, | |
map { "$ifdescr_oid.$_" } keys %ifindex); | |
# More pretty-printing | |
my @errs; | |
for my $key (sort { $a <=> $b } keys %inv_admin) { | |
my $name = $key; | |
$name = $ifindex{$inv_admin{$key}{virtual}} if(exists $inv_admin{$key}{virtual}); | |
my @ports; | |
for my $port (sort keys %{$inv_admin{$key}{members}}) { | |
push @ports, $ifindex{$port} || $port | |
if(exists $broken{$port}); | |
} | |
push @errs, "$name (" . join(", ", @ports) . ")" | |
if(@ports); | |
} | |
print "Broken port channel members: ", join(", ", @errs), "\n"; | |
exit 1; |
To tristanbob:
LACP OID:
-
find an aggregator:
http://www.circitor.fr/Mibs/Html/I/IEEE8023-LAG-MIB.php#dot3adAggPartnerSystemID
1.2.840.10006.300.43.1.1.1.1.8 -
find partner of this aggregator:
http://www.circitor.fr/Mibs/Html/I/IEEE8023-LAG-MIB.php#dot3adAggPortPartnerAdminSystemID
1.2.840.10006.300.43.1.2.1.1.9
Hope this could help.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Since excluding administratively disabled ports still wasn't covering 100% of my use cases, i expanded it again to exclude ports with NOMON in the description.
Again insert in the right places:
# CISCO-IF-MIB::ifAlias
my $ifalias_oid = ".1.3.6.1.2.1.31.1.1.1.18";
my %ifalias = clean_table($session, $ifalias_oid, 1);
for my $ifalias (keys %ifalias) { # skip ports with description text including 'NOMON'
delete $mode{$ifalias} if($ifalias{$ifalias} =~ m/NOMON/);
}