contrib/snmp-probe-host.px: Added script to semi-automatically create SNMP "host...
authorFlorian Forster <octo@noris.net>
Thu, 21 Aug 2008 15:14:29 +0000 (17:14 +0200)
committerFlorian Forster <octo@noris.net>
Thu, 21 Aug 2008 15:14:29 +0000 (17:14 +0200)
Details can be found in the inline documentation ("POD").

contrib/snmp-probe-host.px [new file with mode: 0755]

diff --git a/contrib/snmp-probe-host.px b/contrib/snmp-probe-host.px
new file mode 100755 (executable)
index 0000000..bb9f329
--- /dev/null
@@ -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 <octo at noris.net>
+#
+
+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 <<EOF;
+  <Host "$host">
+    Address "$address"
+    Version $version
+    Community "$community"
+EOF
+  for (sort (@valid_data))
+  {
+    print "    Collect \"$_\"\n";
+  }
+  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.
+
+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<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;
+
+=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.
+
+=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 <plugin>, <snmp>, or <data> block found.\n";
+  exit (1);
+}
+
+probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'});
+
+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 :