From fe69cd30db6239e626cf32fdfd595b0af8b22519 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Thu, 21 Aug 2008 17:14:29 +0200 Subject: [PATCH] contrib/snmp-probe-host.px: Added script to semi-automatically create SNMP "host" blocks. Details can be found in the inline documentation ("POD"). --- contrib/snmp-probe-host.px | 358 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100755 contrib/snmp-probe-host.px diff --git a/contrib/snmp-probe-host.px b/contrib/snmp-probe-host.px new file mode 100755 index 00000000..bb9f3299 --- /dev/null +++ b/contrib/snmp-probe-host.px @@ -0,0 +1,358 @@ +#!/usr/bin/perl +# +# collectd - snmp-probe-host.px +# Copyright (C) 2008 Florian octo Forster +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; only version 2 of the License is applicable. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# Author: +# Florian octo Forster +# + +use strict; +use warnings; +use SNMP; +use Config::General ('ParseConfig'); +use Getopt::Long ('GetOptions'); +use Socket6; + +sub get_config +{ + my %conf; + my $file = shift; + + %conf = ParseConfig (-ConfigFile => $file, + -LowerCaseNames => 1, + -UseApacheInclude => 1, + -IncludeDirectories => 1, + ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (), + -MergeDuplicateBlocks => 1, + -CComments => 0); + if (!%conf) + { + return; + } + return (\%conf); +} # get_config + +sub probe_one +{ + my $sess = shift; + my $conf = shift; + my @oids; + my $cmd = 'GET'; + my $vl; + + if (!$conf->{'table'} || !$conf->{'values'}) + { + warn "No 'table' or 'values' setting"; + return; + } + + @oids = split (/"\s*"/, $conf->{'values'}); + if (($conf->{'table'} =~ m/^(true|yes|on)$/i) && ($conf->{'instance'})) + { + $cmd = 'GETNEXT'; + push (@oids, $conf->{'instance'}); + } + + require Data::Dumper; + + #print "probe_one: \@oids = (" . join (', ', @oids) . ");\n"; + for (@oids) + { + my $oid_orig = $_; + my $vb; + my $status; + + if ($oid_orig =~ m/[^0-9\.]/) + { + my $tmp = SNMP::translateObj ($oid_orig); + if (!defined ($tmp)) + { + warn ("Cannot translate OID $oid_orig"); + return; + } + $oid_orig = $tmp; + } + + $vb = SNMP::Varbind->new ([$oid_orig]); + + if ($cmd eq 'GET') + { + $status = $sess->get ($vb); + if ($sess->{'ErrorNum'} != 0) + { + return; + } + } + else + { + my $oid_copy; + + $status = $sess->getnext ($vb); + if ($sess->{'ErrorNum'} != 0) + { + return; + } + + $oid_copy = $vb->[0]; + if ($oid_copy =~ m/[^0-9\.]/) + { + my $tmp = SNMP::translateObj ($oid_copy); + if (!defined ($tmp)) + { + warn ("Cannot translate OID $oid_copy"); + return; + } + $oid_copy = $tmp; + } + + #print "$oid_orig > $oid_copy ?\n"; + if (substr ($oid_copy, 0, length ($oid_orig)) ne $oid_orig) + { + return; + } + } + + #print STDOUT Data::Dumper->Dump ([$oid_orig, $status], [qw(oid_orig status)]); + } # for (@oids) + + return (1); +} # probe_one + +sub probe_all +{ + my $host = shift; + my $community = shift; + my $data = shift; + my $version = 2; + my @valid_data = (); + my $begin; + my $address; + + { + my @status; + + @status = getaddrinfo ($host, 'snmp'); + while (@status >= 5) + { + my $family = shift (@status); + my $socktype = shift (@status); + my $proto = shift (@status); + my $saddr = shift (@status); + my $canonname = shift (@status); + my $host; + my $port; + + ($host, $port) = getnameinfo ($saddr, NI_NUMERICHOST); + if (defined ($port)) + { + $address = $host; + } + else + { + warn ("getnameinfo failed: $host"); + } + } + } + if (!$address) + { + return; + } + + while ($version > 0) + { + my $sess; + + $sess = new SNMP::Session (DestHost => $host, + Community => $community, + Version => $version, + Timeout => 1000000, + UseNumeric => 1); + if (!$sess) + { + $version--; + next; + } + + $begin = time (); + + for (keys %$data) + { + my $name = $_; + if (probe_one ($sess, $data->{$name})) + { + push (@valid_data, $name); + } + + if ((@valid_data == 0) && ((time () - $begin) > 10)) + { + # break for loop + last; + } + } + + if (@valid_data) + { + # break while loop + last; + } + + $version--; + } # while ($version > 0) + + if (!@valid_data) + { + return; + } + + print < + Address "$address" + Version $version + Community "$community" +EOF + for (sort (@valid_data)) + { + print " Collect \"$_\"\n"; + } + print < +EOF +} # probe_all + +sub exit_usage +{ + print < [options] + +Options are: + -H | --host Hostname of the device to probe. + -C | --config Path to config file holding the SNMP data blocks. + -c | --community SNMP community to use. Default: `public'. + -h | --help Print this information and exit. + +USAGE + exit (1); +} + +=head1 NAME + +snmp-probe-host.px - Find out what information an SNMP device provides. + +=head1 SYNOPSIS + + ./snmp-probe-host.px --host switch01.mycompany.com --community ei2Acoum + +=head1 DESCRIPTION + +The C script can be used to automatically generate SNMP +configuration snippets for collectd's snmp plugin (see L). + +This script parses the collectd configuration and detecs all "data" blocks that +are defined for the SNMP plugin. It then queries the device specified on the +command line for all OIDs and registeres which OIDs could be answered correctly +and which resulted in an error. With that information the script figures out +which "data" blocks can be used with this hosts and prints an appropriate +"host" block to standard output. + +The script first tries to contact the device via SNMPv2. If after ten seconds +no working "data" block has been found, it will try to downgrade to SNMPv1. +This is a bit a hack, but works for now. + +=cut + +my $host; +my $file = '/etc/collectd/collectd.conf'; +my $community = 'public'; +my $conf; +my $working_data; + +=head1 OPTIONS + +The following command line options are accepted: + +=over 4 + +=item B<--host> I + +Hostname of the device. This B be a fully qualified domain name (FQDN), +but anything the system can resolve to an IP address will word. B. + +=item B<--config> I + +Sets the name of the collectd config file which defined the SNMP "data" blocks. +Due to limitations of the config parser used in this script +(C), C statements cannot be parsed correctly. +Defaults to F. + +=item B<--community> I + +SNMP community to use. Should be pretty straight forward. + +=back + +=cut + +GetOptions ('H|host|hostname=s' => \$host, + 'C|conf|config=s' => \$file, + 'c|community=s' => \$community, + 'h|help' => \&exit_usage) or die; + +if (!$host) +{ + print STDERR "No hostname given. Please use `--host'.\n"; + exit (1); +} + +$conf = get_config ($file) or die ("Cannot read config"); + +if (!defined ($conf->{'plugin'}) + || !defined ($conf->{'plugin'}{'snmp'}) + || !defined ($conf->{'plugin'}{'snmp'}{'data'})) +{ + print STDERR "Error: No , , or block found.\n"; + exit (1); +} + +probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'}); + +exit (0); + +=head1 BUGS + +=over 4 + +=item + +C statements in the config file are not handled correctly. + +=item + +SNMPv2 / SNMPv1 detection is a hack. + +=back + +=head1 AUTHOR + +Copyright (c) 2008 by Florian octo Forster +EoctoEatEnoris.netE. Licensed under the terms of the GPLv2. +Written for the norisEnetworkEAG L. + +=cut + +# vim: set sw=2 sts=2 ts=8 et : -- 2.11.0