Created
May 9, 2018 23:37
-
-
Save karrots/7e9c0f262f038422b352169c8f302f52 to your computer and use it in GitHub Desktop.
Perl script used with Icinga2 to check port channel status.
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/env 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"; | |
my $group_oid = ".1.2.840.10006.300.43.1.2.1.1.13"; | |
# CISCO-PAGP-MIB::pagpAdminGroupCapability | |
#my $admin_group_oid = ".1.3.6.1.4.1.9.9.98.1.1.1.1.5"; | |
my $admin_group_oid = ".1.2.840.10006.300.43.1.2.1.1.4"; | |
# CISCO-PAGP-MIB::pagpEthcOperationMode | |
#my $mode_oid = ".1.3.6.1.4.1.9.9.98.1.1.1.1.1"; | |
my $mode_oid = ".1.2.840.10006.300.43.1.2.1.1.21"; | |
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 ( defined $mode{$mode} and length $mode{$mode} == 0); | |
} | |
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 "OK: 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 "WARNING: Broken port channel members: ", join(", ", @errs), "\n"; | |
exit 1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment