Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: b6009737c2
Fetching contributors…

Cannot retrieve contributors at this time

executable file 440 lines (359 sloc) 9.348 kB
#!/usr/bin/perl
#
# collectd - snmp-probe-host.px
# Copyright (C) 2008,2009 Florian octo Forster
# Copyright (C) 2009 noris network AG
#
# 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 <octo at noris.net>
#
use strict;
use warnings;
use SNMP;
use Config::General ('ParseConfig');
use Getopt::Long ('GetOptions');
use Socket6;
our %ExcludeOptions =
(
'IF-MIB64' => qr/^\.?1\.3\.6\.1\.2\.1\.31/,
'IF-MIB32' => qr/^\.?1\.3\.6\.1\.2\.1\.2/
);
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 $excludes = @_ ? 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)
{
$cmd = 'GETNEXT';
if (defined ($conf->{'instance'}))
{
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;
}
for (@$excludes)
{
if ($oid_orig =~ $_)
{
return;
}
}
$vb = SNMP::Varbind->new ([$oid_orig]);
if ($cmd eq 'GET')
{
$status = $sess->get ($vb);
if ($sess->{'ErrorNum'} != 0)
{
return;
}
if (!defined ($status))
{
return;
}
if ("$status" eq 'NOSUCHOBJECT')
{
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 $excludes = @_ ? 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}, $excludes))
{
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)
print <<EOF;
<Host "$host">
Address "$address"
Version $version
Community "$community"
EOF
for (sort (@valid_data))
{
print " Collect \"$_\"\n";
}
if (!@valid_data)
{
print <<EOF;
# WARNING: Autoconfiguration failed.
# TODO: Add one or more `Collect' statements here:
# Collect "foo"
EOF
}
print <<EOF;
Interval 60
</Host>
EOF
} # probe_all
sub exit_usage
{
print <<USAGE;
Usage: snmp-probe-host.px --host <host> [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.
-x | --exclude Exclude a specific MIB. Call with "help" for more
information.
USAGE
exit (1);
}
sub exit_usage_exclude
{
print "Available exclude MIBs:\n\n";
for (sort (keys %ExcludeOptions))
{
print " $_\n";
}
print "\n";
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<snmp-probe-host.px> script can be used to automatically generate SNMP
configuration snippets for collectd's snmp plugin (see L<collectd-snmp(5)>).
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;
my @excludes = ();
=head1 OPTIONS
The following command line options are accepted:
=over 4
=item B<--host> I<hostname>
Hostname of the device. This B<should> be a fully qualified domain name (FQDN),
but anything the system can resolve to an IP address will word. B<Required
argument>.
=item B<--config> I<config file>
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<Config::General>), C<Include> statements cannot be parsed correctly.
Defaults to F</etc/collectd/collectd.conf>.
=item B<--community> I<community>
SNMP community to use. Should be pretty straight forward.
=item B<--exclude> I<MIB>
This option can be used to exclude specific data from being enabled in the
generated config. Currently the following MIBs are understood:
=over 4
=item B<IF-MIB>
Exclude interface information, such as I<ifOctets> and I<ifPackets>.
=back
=back
=cut
GetOptions ('H|host|hostname=s' => \$host,
'C|conf|config=s' => \$file,
'c|community=s' => \$community,
'x|exclude=s' => \@excludes,
'h|help' => \&exit_usage) or die;
if (!$host)
{
print STDERR "No hostname given. Please use `--host'.\n";
exit (1);
}
if (@excludes)
{
my $tmp = join (',', @excludes);
my @tmp = split (/\s*,\s*/, $tmp);
@excludes = ();
for (@tmp)
{
my $mib = uc ($_);
if ($mib eq 'HELP')
{
exit_usage_exclude ();
}
elsif (!exists ($ExcludeOptions{$mib}))
{
print STDERR "No such MIB: $mib\n";
exit_usage_exclude ();
}
push (@excludes, $ExcludeOptions{$mib});
}
}
$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 <plugin>, <snmp>, or <data> block found.\n";
exit (1);
}
probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'}, \@excludes);
exit (0);
=head1 BUGS
=over 4
=item
C<Include> 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
E<lt>octoE<nbsp>atE<nbsp>noris.netE<gt>. Licensed under the terms of the GPLv2.
Written for the norisE<nbsp>networkE<nbsp>AG L<http://noris.net/>.
=cut
# vim: set sw=2 sts=2 ts=8 et :
Jump to Line
Something went wrong with that request. Please try again.