Code

contrib/snmp-probe-host.px: Work-around for Windows systems.
[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)
65   {
66     $cmd = 'GETNEXT';
67     if (defined ($conf->{'instance'}))
68     {
69       push (@oids, $conf->{'instance'});
70     }
71   }
73   require Data::Dumper;
75   #print "probe_one: \@oids = (" . join (', ', @oids) . ");\n";
76   for (@oids)
77   {
78     my $oid_orig = $_;
79     my $vb;
80     my $status;
82     if ($oid_orig =~ m/[^0-9\.]/)
83     {
84       my $tmp = SNMP::translateObj ($oid_orig);
85       if (!defined ($tmp))
86       {
87         warn ("Cannot translate OID $oid_orig");
88         return;
89       }
90       $oid_orig = $tmp;
91     }
93     $vb = SNMP::Varbind->new ([$oid_orig]);
95     if ($cmd eq 'GET')
96     {
97       $status = $sess->get ($vb);
98       if ($sess->{'ErrorNum'} != 0)
99       {
100         return;
101       }
102       if (!defined ($status))
103       {
104         return;
105       }
106       if ("$status" eq 'NOSUCHOBJECT')
107       {
108         return;
109       }
110     }
111     else
112     {
113       my $oid_copy;
115       $status = $sess->getnext ($vb);
116       if ($sess->{'ErrorNum'} != 0)
117       {
118         return;
119       }
121       $oid_copy = $vb->[0];
122       if ($oid_copy =~ m/[^0-9\.]/)
123       {
124         my $tmp = SNMP::translateObj ($oid_copy);
125         if (!defined ($tmp))
126         {
127           warn ("Cannot translate OID $oid_copy");
128           return;
129         }
130         $oid_copy = $tmp;
131       }
133       #print "$oid_orig > $oid_copy ?\n";
134       if (substr ($oid_copy, 0, length ($oid_orig)) ne $oid_orig)
135       {
136         return;
137       }
138     }
140     #print STDOUT Data::Dumper->Dump ([$oid_orig, $status], [qw(oid_orig status)]);
141   } # for (@oids)
143   return (1);
144 } # probe_one
146 sub probe_all
148   my $host = shift;
149   my $community = shift;
150   my $data = shift;
151   my $version = 2;
152   my @valid_data = ();
153   my $begin;
154   my $address;
156   {
157     my @status;
159     @status = getaddrinfo ($host, 'snmp');
160     while (@status >= 5)
161     {
162       my $family    = shift (@status);
163       my $socktype  = shift (@status);
164       my $proto     = shift (@status);
165       my $saddr     = shift (@status);
166       my $canonname = shift (@status);
167       my $host;
168       my $port;
170       ($host, $port) = getnameinfo ($saddr, NI_NUMERICHOST);
171       if (defined ($port))
172       {
173         $address = $host;
174       }
175       else
176       {
177         warn ("getnameinfo failed: $host");
178       }
179     }
180   }
181   if (!$address)
182   {
183     return;
184   }
186   while ($version > 0)
187   {
188     my $sess;
190     $sess = new SNMP::Session (DestHost => $host,
191       Community => $community,
192       Version => $version,
193       Timeout => 1000000,
194       UseNumeric => 1);
195     if (!$sess)
196     {
197       $version--;
198       next;
199     }
201     $begin = time ();
203     for (keys %$data)
204     {
205       my $name = $_;
206       if (probe_one ($sess, $data->{$name}))
207       {
208         push (@valid_data, $name);
209       }
211       if ((@valid_data == 0) && ((time () - $begin) > 10))
212       {
213         # break for loop
214         last;
215       }
216     }
218     if (@valid_data)
219     {
220       # break while loop
221       last;
222     }
224     $version--;
225   } # while ($version > 0)
227   if (!@valid_data)
228   {
229     return;
230   }
232   print <<EOF;
233   <Host "$host">
234     Address "$address"
235     Version $version
236     Community "$community"
237 EOF
238   for (sort (@valid_data))
239   {
240     print "    Collect \"$_\"\n";
241   }
242   print <<EOF;
243     Interval 60
244   </Host>
245 EOF
246 } # probe_all
248 sub exit_usage
250   print <<USAGE;
251 Usage: snmp-probe-host.px --host <host> [options]
253 Options are:
254   -H | --host          Hostname of the device to probe.
255   -C | --config        Path to config file holding the SNMP data blocks.
256   -c | --community     SNMP community to use. Default: `public'.
257   -h | --help          Print this information and exit.
259 USAGE
260   exit (1);
263 =head1 NAME
265 snmp-probe-host.px - Find out what information an SNMP device provides.
267 =head1 SYNOPSIS
269   ./snmp-probe-host.px --host switch01.mycompany.com --community ei2Acoum
271 =head1 DESCRIPTION
273 The C<snmp-probe-host.px> script can be used to automatically generate SNMP
274 configuration snippets for collectd's snmp plugin (see L<collectd-snmp(5)>).
276 This script parses the collectd configuration and detecs all "data" blocks that
277 are defined for the SNMP plugin. It then queries the device specified on the
278 command line for all OIDs and registeres which OIDs could be answered correctly
279 and which resulted in an error. With that information the script figures out
280 which "data" blocks can be used with this hosts and prints an appropriate
281 "host" block to standard output.
283 The script first tries to contact the device via SNMPv2. If after ten seconds
284 no working "data" block has been found, it will try to downgrade to SNMPv1.
285 This is a bit a hack, but works for now.
287 =cut
289 my $host;
290 my $file = '/etc/collectd/collectd.conf';
291 my $community = 'public';
292 my $conf;
293 my $working_data;
295 =head1 OPTIONS
297 The following command line options are accepted:
299 =over 4
301 =item B<--host> I<hostname>
303 Hostname of the device. This B<should> be a fully qualified domain name (FQDN),
304 but anything the system can resolve to an IP address will word. B<Required
305 argument>.
307 =item B<--config> I<config file>
309 Sets the name of the collectd config file which defined the SNMP "data" blocks.
310 Due to limitations of the config parser used in this script
311 (C<Config::General>), C<Include> statements cannot be parsed correctly.
312 Defaults to F</etc/collectd/collectd.conf>.
314 =item B<--community> I<community>
316 SNMP community to use. Should be pretty straight forward.
318 =back
320 =cut
322 GetOptions ('H|host|hostname=s' => \$host,
323   'C|conf|config=s' => \$file,
324   'c|community=s' => \$community,
325   'h|help' => \&exit_usage) or die;
327 if (!$host)
329   print STDERR "No hostname given. Please use `--host'.\n";
330   exit (1);
333 $conf = get_config ($file) or die ("Cannot read config");
335 if (!defined ($conf->{'plugin'})
336   || !defined ($conf->{'plugin'}{'snmp'})
337   || !defined ($conf->{'plugin'}{'snmp'}{'data'}))
339   print STDERR "Error: No <plugin>, <snmp>, or <data> block found.\n";
340   exit (1);
343 probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'});
345 exit (0);
347 =head1 BUGS
349 =over 4
351 =item
353 C<Include> statements in the config file are not handled correctly.
355 =item
357 SNMPv2 / SNMPv1 detection is a hack.
359 =back
361 =head1 AUTHOR
363 Copyright (c) 2008 by Florian octo Forster
364 E<lt>octoE<nbsp>atE<nbsp>noris.netE<gt>. Licensed under the terms of the GPLv2.
365 Written for the norisE<nbsp>networkE<nbsp>AG L<http://noris.net/>.
367 =cut
369 # vim: set sw=2 sts=2 ts=8 et :