-
-
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.