Skip to content

Instantly share code, notes, and snippets.

@karrots
Created May 9, 2018 23:37
Show Gist options
  • Save karrots/7e9c0f262f038422b352169c8f302f52 to your computer and use it in GitHub Desktop.
Save karrots/7e9c0f262f038422b352169c8f302f52 to your computer and use it in GitHub Desktop.
Perl script used with Icinga2 to check port channel status.
#!/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