Code

Merge branch 'collectd-4.4'
[collectd.git] / contrib / snmp-probe-host.px
1 #!/usr/bin/perl
2 #
3 # collectd - snmp-probe-host.px
4 # Copyright (C) 2008  Florian octo Forster
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the
8 # Free Software Foundation; only version 2 of the License is applicable.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 #
19 # Author:
20 #   Florian octo Forster <octo at noris.net>
21 #
23 use strict;
24 use warnings;
25 use SNMP;
26 use Config::General ('ParseConfig');
27 use Getopt::Long ('GetOptions');
28 use Socket6;
30 sub get_config
31 {
32   my %conf;
33   my $file = shift;
35   %conf = ParseConfig (-ConfigFile => $file,
36     -LowerCaseNames => 1,
37     -UseApacheInclude => 1,
38     -IncludeDirectories => 1,
39     ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
40     -MergeDuplicateBlocks => 1,
41     -CComments => 0);
42   if (!%conf)
43   {
44     return;
45   }
46   return (\%conf);
47 } # get_config
49 sub probe_one
50 {
51   my $sess = shift;
52   my $conf = shift;
53   my @oids;
54   my $cmd = 'GET';
55   my $vl;
57   if (!$conf->{'table'} || !$conf->{'values'})
58   {
59     warn "No 'table' or 'values' setting";
60     return;
61   }
63   @oids = split (/"\s*"/, $conf->{'values'});
64   if (($conf->{'table'} =~ m/^(true|yes|on)$/i) && ($conf->{'instance'}))
65   {
66     $cmd = 'GETNEXT';
67     push (@oids, $conf->{'instance'});
68   }
70   require Data::Dumper;
72   #print "probe_one: \@oids = (" . join (', ', @oids) . ");\n";
73   for (@oids)
74   {
75     my $oid_orig = $_;
76     my $vb;
77     my $status;
79     if ($oid_orig =~ m/[^0-9\.]/)
80     {
81       my $tmp = SNMP::translateObj ($oid_orig);
82       if (!defined ($tmp))
83       {
84         warn ("Cannot translate OID $oid_orig");
85         return;
86       }
87       $oid_orig = $tmp;
88     }
90     $vb = SNMP::Varbind->new ([$oid_orig]);
92     if ($cmd eq 'GET')
93     {
94       $status = $sess->get ($vb);
95       if ($sess->{'ErrorNum'} != 0)
96       {
97         return;
98       }
99     }
100     else
101     {
102       my $oid_copy;
104       $status = $sess->getnext ($vb);
105       if ($sess->{'ErrorNum'} != 0)
106       {
107         return;
108       }
110       $oid_copy = $vb->[0];
111       if ($oid_copy =~ m/[^0-9\.]/)
112       {
113         my $tmp = SNMP::translateObj ($oid_copy);
114         if (!defined ($tmp))
115         {
116           warn ("Cannot translate OID $oid_copy");
117           return;
118         }
119         $oid_copy = $tmp;
120       }
122       #print "$oid_orig > $oid_copy ?\n";
123       if (substr ($oid_copy, 0, length ($oid_orig)) ne $oid_orig)
124       {
125         return;
126       }
127     }
129     #print STDOUT Data::Dumper->Dump ([$oid_orig, $status], [qw(oid_orig status)]);
130   } # for (@oids)
132   return (1);
133 } # probe_one
135 sub probe_all
137   my $host = shift;
138   my $community = shift;
139   my $data = shift;
140   my $version = 2;
141   my @valid_data = ();
142   my $begin;
143   my $address;
145   {
146     my @status;
148     @status = getaddrinfo ($host, 'snmp');
149     while (@status >= 5)
150     {
151       my $family    = shift (@status);
152       my $socktype  = shift (@status);
153       my $proto     = shift (@status);
154       my $saddr     = shift (@status);
155       my $canonname = shift (@status);
156       my $host;
157       my $port;
159       ($host, $port) = getnameinfo ($saddr, NI_NUMERICHOST);
160       if (defined ($port))
161       {
162         $address = $host;
163       }
164       else
165       {
166         warn ("getnameinfo failed: $host");
167       }
168     }
169   }
170   if (!$address)
171   {
172     return;
173   }
175   while ($version > 0)
176   {
177     my $sess;
179     $sess = new SNMP::Session (DestHost => $host,
180       Community => $community,
181       Version => $version,
182       Timeout => 1000000,
183       UseNumeric => 1);
184     if (!$sess)
185     {
186       $version--;
187       next;
188     }
190     $begin = time ();
192     for (keys %$data)
193     {
194       my $name = $_;
195       if (probe_one ($sess, $data->{$name}))
196       {
197         push (@valid_data, $name);
198       }
200       if ((@valid_data == 0) && ((time () - $begin) > 10))
201       {
202         # break for loop
203         last;
204       }
205     }
207     if (@valid_data)
208     {
209       # break while loop
210       last;
211     }
213     $version--;
214   } # while ($version > 0)
216   if (!@valid_data)
217   {
218     return;
219   }
221   print <<EOF;
222   <Host "$host">
223     Address "$address"
224     Version $version
225     Community "$community"
226 EOF
227   for (sort (@valid_data))
228   {
229     print "    Collect \"$_\"\n";
230   }
231   print <<EOF;
232     Interval 60
233   </Host>
234 EOF
235 } # probe_all
237 sub exit_usage
239   print <<USAGE;
240 Usage: snmp-probe-host.px --host <host> [options]
242 Options are:
243   -H | --host          Hostname of the device to probe.
244   -C | --config        Path to config file holding the SNMP data blocks.
245   -c | --community     SNMP community to use. Default: `public'.
246   -h | --help          Print this information and exit.
248 USAGE
249   exit (1);
252 =head1 NAME
254 snmp-probe-host.px - Find out what information an SNMP device provides.
256 =head1 SYNOPSIS
258   ./snmp-probe-host.px --host switch01.mycompany.com --community ei2Acoum
260 =head1 DESCRIPTION
262 The C<snmp-probe-host.px> script can be used to automatically generate SNMP
263 configuration snippets for collectd's snmp plugin (see L<collectd-snmp(5)>).
265 This script parses the collectd configuration and detecs all "data" blocks that
266 are defined for the SNMP plugin. It then queries the device specified on the
267 command line for all OIDs and registeres which OIDs could be answered correctly
268 and which resulted in an error. With that information the script figures out
269 which "data" blocks can be used with this hosts and prints an appropriate
270 "host" block to standard output.
272 The script first tries to contact the device via SNMPv2. If after ten seconds
273 no working "data" block has been found, it will try to downgrade to SNMPv1.
274 This is a bit a hack, but works for now.
276 =cut
278 my $host;
279 my $file = '/etc/collectd/collectd.conf';
280 my $community = 'public';
281 my $conf;
282 my $working_data;
284 =head1 OPTIONS
286 The following command line options are accepted:
288 =over 4
290 =item B<--host> I<hostname>
292 Hostname of the device. This B<should> be a fully qualified domain name (FQDN),
293 but anything the system can resolve to an IP address will word. B<Required
294 argument>.
296 =item B<--config> I<config file>
298 Sets the name of the collectd config file which defined the SNMP "data" blocks.
299 Due to limitations of the config parser used in this script
300 (C<Config::General>), C<Include> statements cannot be parsed correctly.
301 Defaults to F</etc/collectd/collectd.conf>.
303 =item B<--community> I<community>
305 SNMP community to use. Should be pretty straight forward.
307 =back
309 =cut
311 GetOptions ('H|host|hostname=s' => \$host,
312   'C|conf|config=s' => \$file,
313   'c|community=s' => \$community,
314   'h|help' => \&exit_usage) or die;
316 if (!$host)
318   print STDERR "No hostname given. Please use `--host'.\n";
319   exit (1);
322 $conf = get_config ($file) or die ("Cannot read config");
324 if (!defined ($conf->{'plugin'})
325   || !defined ($conf->{'plugin'}{'snmp'})
326   || !defined ($conf->{'plugin'}{'snmp'}{'data'}))
328   print STDERR "Error: No <plugin>, <snmp>, or <data> block found.\n";
329   exit (1);
332 probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'});
334 exit (0);
336 =head1 BUGS
338 =over 4
340 =item
342 C<Include> statements in the config file are not handled correctly.
344 =item
346 SNMPv2 / SNMPv1 detection is a hack.
348 =back
350 =head1 AUTHOR
352 Copyright (c) 2008 by Florian octo Forster
353 E<lt>octoE<nbsp>atE<nbsp>noris.netE<gt>. Licensed under the terms of the GPLv2.
354 Written for the norisE<nbsp>networkE<nbsp>AG L<http://noris.net/>.
356 =cut
358 # vim: set sw=2 sts=2 ts=8 et :