-
-
Save bldewolf/6314435 to your computer and use it in GitHub Desktop.
#!/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; |
Great script!
I expanded it to exclude administratively disabled port channels (yeah i know questionable use case but i thought it was handy in my case)
Not sure if the delete is clean enough as i'm a perl noob, but it seems to work. Maybe someone could make it nicer if they care.
Insert in the right places:
# CISCO-IF-MIB::ifAdminStatus
my $adminstat_oid = ".1.3.6.1.2.1.2.2.1.7";
my %adminstat = clean_table($session, $adminstat_oid, 1);
for my $adminstat (keys %adminstat) { # skip ports in mode 'down'
delete $mode{$adminstat} if($adminstat{$adminstat} == 2);
}
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/);
}
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.
Is there any way to make this work with LACP etherchannels? I briefly searched the LACP LAG MIB but didn't find similar values.