#!/usr/bin/perl -w # # Copyright (C) 2004 Jimmy Olsen # # 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; version 2 dated June, # 1991. # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # # $Log$ # Revision 1.8.2.1 2005/02/16 20:13:10 jimmyo # Added man page for munin-node-configure-snmp. # # Revision 1.8 2004/11/16 20:00:42 jimmyo # License cleanups. # use strict; use Net::SNMP; use Socket; use Getopt::Long; my $debug = 0; my $version = "1.2.6"; my $config = "/etc/munin/munin-node.conf"; my $servicedir = "/etc/munin/plugins"; my $libdir = "/usr/share/munin/plugins"; my $bindir = "/usr/sbin"; my $sysName = "1.3.6.1.2.1.1.5.0"; my $name; my $session; my $error; my $response; my $community = "public"; my $snmpver = "2c"; my $snmpport = "161"; my $do_usage = 0; my $do_version = 0; my $do_error = 0; my $newer = undef; my @plugins = (); my %plugconf = (); my %hostconf = (); $do_error = 1 unless GetOptions ( "help" => \$do_usage, "debug!" => \$debug, "config=s" => \$config, "servicedir=s" => \$servicedir, "plugins=s" => \@plugins, "libdir=s" => \$libdir, "version!" => \$do_version, "snmpversion=s" => \$snmpver, "community=s" => \$community, "newer=s" => \$newer ); if (! @plugins) { @plugins = &get_plugins ($libdir); } @plugins = split (/,/, join (',', @plugins)); print "# DEBUG: Checking plugins: ", join (',', @plugins), "\n" if $debug; if ($do_error or $do_usage or !@ARGV) { print "Usage: $0 [options] [host/cidr] [...] Options: --help View this help page --version Show version information --debug View debug information (very verbose) --config Override configuration file [$config] --servicedir Override plugin dir [$servicedir] --libdir Override plugin lib [$libdir] "; exit (!$do_usage); # 1 if error, 0 if --help } if ($do_version) { print "munin-node-configure-snmp (munin-node) version $version.\n"; print "Written by Jimmy Olsen\n"; print "\n"; print "Copyright (C) 2004 Jimmy Olsen\n"; print "This is free software released under the GNU Public License. There is NO\n"; print "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"; exit 0; } foreach my $plugin (@plugins) { &fetch_plugin_config ($plugin, \%plugconf); } while (my $addr = shift) { my $num = 32; if ($addr =~ /([^\/]+)\/(\d+)/) { $num = $2; $addr = $1; } $num = 32 - $num; $num = 2 ** $num; print "# Doing $addr / $num\n" if $debug; for (my $i = 0; $i < $num; $i++) { print "# Doing $addr -> $i...\n" if $debug; my $tmpaddr = $addr; if ($tmpaddr =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { my @tmpaddr = split (/\./, $tmpaddr); $tmpaddr[3] += $i; $tmpaddr = gethostbyaddr (inet_aton (join ('.', @tmpaddr)), AF_INET); $tmpaddr ||= join ('.', @tmpaddr); } print "# ($tmpaddr)\n" if $debug; &do_host ("$tmpaddr", $community, $snmpver, $snmpport); } } #interfaces ($name); sub do_host { my $host = shift; my $comm = shift; my $ver = shift; my $port = shift; if ($host =~ /([^:]+):(\d+)/) { $host = $1; $port = $2; } ($session, $error) = Net::SNMP->session( -hostname => $host, -community => $comm, -port => $port, -version => $ver, ); $session->translate (0); die $error if $error; if (!defined ($session)) { print "# Dropping host \"$host\": $error" . "\n"; return 0; } if (!defined ($response = $session->get_request($sysName))) { print "# Dropping host \"$host\": " . $session->error() . "\n"; return 0; } $name = $response->{$sysName}; foreach my $plugin (@plugins) { my $auto = snmp_autoconf_plugin ($plugin, \%plugconf, \%hostconf, $host); if (defined $auto) { if ($plugconf{$plugin}->{wild}) { foreach my $id (@{$auto}) { if (! -e "$servicedir/snmp_$host"."_$plugin"."_$id") { print "ln -s $libdir/snmp__$plugin", "_ $servicedir/snmp_$host", "_$plugin", "_$id\n"; } } } else { if (! -e "$servicedir/snmp_$host"."_$plugin") { print "ln -s $libdir/snmp__$plugin", " $servicedir/snmp_$host", "_$plugin\n"; } } } } } sub snmp_autoconf_plugin { my $plugname = shift; my $plugconf = shift; my $hostconf = shift; my $host = shift; print "# Running autoconf on $plugname for $host...\n" if $debug; # First round of requirements if (defined $plugconf->{$plugname}->{req}) { print "# Checking requirements...\n" if $debug; foreach my $req (@{$plugconf->{$plugname}->{req}}) { if ($req->[0] =~ /\.$/) { print "# Delaying testing of $req->[0], as we need the indexes first.\n" if $debug; next; } my $snmp_val = snmp_get_single ($session, $req->[0]); if (!defined $snmp_val or $snmp_val !~ /$req->[1]/) { print "# Nope. Duh.\n" if $debug; return undef; } } } # We need the number of "things" to autoconf my $num = 1; if (defined $plugconf->{$plugname}->{num}) { $num = snmp_get_single ($session, $plugconf->{$plugname}->{num}); return undef if !defined $num; } print "# Number of items to autoconf is $num...\n" if $debug; # Then the index base my $indexes; if (defined $plugconf->{$plugname}->{ind}) { $indexes = snmp_get_index ($plugconf->{$plugname}->{ind}, $num); return undef if !defined $indexes; } else { $indexes->{0} = 1; } print "# Got indexes: ", join (',', keys (%{$indexes})), "\n" if $debug; return undef unless scalar keys %{$indexes}; # Second round of requirements (now that we have the indexes) if (defined $plugconf->{$plugname}->{req}) { print "# Checking requirements...\n" if $debug; foreach my $req (@{$plugconf->{$plugname}->{req}}) { if ($req->[0] !~ /\.$/) { print "# Already tested of $req->[0], before we got hold of the indexes.\n" if $debug; next; } foreach my $key (keys %$indexes) { my $snmp_val = snmp_get_single ($session, $req->[0] . $key); if (!defined $snmp_val or $snmp_val !~ /$req->[1]/) { print "# Nope. Deleting $key from possible solutions.\n" if $debug; delete $indexes->{$key}; # Disable } } } } my @tmparr = sort keys %$indexes; return \@tmparr; } sub fetch_plugin_config { my $plugname = shift; my $plugconf = shift; my $plugin = "snmp__" . $plugname; if (-x "$libdir/$plugin" . "_") { $plugin .= "_"; $plugconf->{$plugname}->{wild} = 1; } elsif (-x "$libdir/$plugin") { $plugconf->{$plugname}->{wild} = 0; } else { print "# Skipping $plugname: Couldn't find plugin \"$libdir/$plugin\".\n" if $debug; return 0; } print "# SNMPconfing plugin \"$plugname\" ( $libdir/$plugin )\n" if $debug; my $fork = open (PLUG, "-|"); if ($fork == -1) { die "# ERROR: Unable to fork: $!"; } elsif ($fork == 0) # Child { close (STDERR); open (STDERR, ">&STDOUT"); exec ("$bindir/munin-run", "--config", $config, "--servicedir", $libdir, $plugin, "snmpconf"); } else { while () { chomp; s/^\s+//; s/\s+$//; my ($a, $b) = split (/\s+/, $_, 2); next unless defined $a; if ($a =~ /^require$/i and defined $b) { my ($oid, $val) = split (/\s+/, $b); if (! defined $val) { $val = ".*"; } push (@{$plugconf->{$plugname}->{req}}, [$oid, $val]); print "# Registered $plugname requirement: $oid =~ /$val/\n" if $debug; } elsif ($a =~ /^index$/i and defined $b) { $plugconf->{$plugname}->{ind} = $b; print "# Registered $plugname index : $b\n" if $debug; } elsif ($a =~ /^number$/i and defined $b) { $plugconf->{$plugname}->{num} = $b; print "# Registered $plugname number : $b\n" if $debug; } elsif ($a =~ /^env\.(\S+)$/) { $plugconf->{$plugname}->{env}->{$1} = $b; print "# Registered $plugname env : $b\n" if $debug; } else { print "# Couldn't parse line line $_\n"; } } } return 0; } sub snmp_get_single { my $session = shift; my $oid = shift; if ((!defined ($response = $session->get_request($oid))) or $session->error_status) { return undef; } print "# Fetched value \"$response->{$oid}\"\n" if $debug; return $response->{$oid}; } sub snmp_get_index { my $oid = shift; my $num = shift; my $ret = $oid . "0"; my $rhash = {}; $num++; # Avaya switch b0rkenness... for (my $i = 0; $i < $num; $i++) { if ($i == 0) { print "# Checking for $ret\n" if $debug; $response = $session->get_request($ret); } if ($i or !defined $response or $session->error_status) { print "# Checking for sibling of $ret\n" if $debug; $response = $session->get_next_request($ret); } if (!$response or $session->error_status) { return undef; } my @keys = keys %$response; $ret = $keys[0]; last unless ($ret =~ /^$oid\d+$/); print "# Index $i: ", join ('|', @keys), "\n" if $debug; $rhash->{$response->{$ret}} = 1; } return $rhash; } sub interfaces { my $name = shift; my %interfaces = (); my $num; my $ifNumber = "1.3.6.1.2.1.2.1.0"; my $ifEntryIndex = "1.3.6.1.2.1.2.2.1.1"; # dot something my $ifEntryType = "1.3.6.1.2.1.2.2.1.3"; # dot something my $ifEntrySpeed = "1.3.6.1.2.1.2.2.1.5"; # dot something print "# System name: ", $name, "\n" if $debug; if (!defined ($response = $session->get_request($ifNumber)) or $session->error_status) { die "Croaking: " . $session->error(); } $num = $response->{$ifNumber} +1; # Add one because of bogus switch entries print "# Number of interfaces: ", $num, "\n" if $debug; my $ret = $ifEntryIndex . ".0"; for (my $i = 0; $i < $num;) { if ($i == 0) { $response = $session->get_request($ret); } if ($i or !defined $response or $session->error_status) { $response = $session->get_next_request($ret); } if (!$response or $session->error_status) { die "Croaking: ", $session->error(); } my @keys = keys %$response; $ret = $keys[0]; last unless ($ret =~ /^$ifEntryIndex\.\d+$/); print "# Index $i: ", join ('|', @keys), "\n" if $debug; $interfaces{$response->{$ret}} = 1; $i++; } foreach my $key (keys %interfaces) { $response = $session->get_request($ifEntrySpeed . "." . $key); if (!$response or $session->error_status) { die "Croaking: ", $session->error(); } my @keys = keys %$response; print "# Speed $key: ", join ('|', @keys), ": ", $response->{$keys[0]}, "\n" if $debug; if ($response->{$keys[0]} == 0) { delete $interfaces{$key}; } } foreach my $key (keys %interfaces) { $response = $session->get_request($ifEntryType . "." . $key); if (!$response or $session->error_status) { die "Croaking: ", $session->error(); } my @keys = keys %$response; print "# Type $key: ", join ('|', @keys), ": ", $response->{$keys[0]}, "\n" if $debug; if ($response->{$keys[0]} != 6) { delete $interfaces{$key}; } } foreach my $key (sort keys %interfaces) { print "snmp_${name}_if_$key\n"; } } sub get_plugins { my $dir = shift; my @plugs = (); my @plugins = (); print "DEBUG: Opening \"$dir\" for reading...\n" if $debug; opendir (DIR, $dir) or die "Could not open \"$dir\" for reading: $!"; @plugs = readdir (DIR); closedir (DIR); foreach my $plug (@plugs) { my $p = undef; my $path = "$dir/$plug"; $path = readlink($path) and $path = $path =~ /^\// ? $path : "$dir/$path" while -l $path; next unless -f $path; next unless -x _; next if $plug =~ /^\./; $p->{'family'} = "contrib"; # Set default family... print "DEBUG: Checking plugin: $plug..." if $debug; if (! open (FILE, "$dir/$plug")) { warn "WARNING: Could not open file \"$dir/$plug\" for reading ($!). Skipping."; next; } while () { chomp; if (/#%#\s+family\s*=\s*(\S+)\s*$/) { $p->{'family'} = $1; print "$1..." if $debug; } elsif (/#%#\s+capabilities\s*=\s*(.+)$/) { foreach my $cap (split (/\s+/, $1)) { $p->{'capability'}->{$cap} = 1; print "$cap..." if $debug; } } } close (FILE); print "\n" if $debug; if (defined $p->{'capability'}->{'snmpconf'}) { $plug =~ s/^snmp__//; $plug =~ s/_$//; push (@plugins, $plug); } } return @plugins; } 1; =head1 NAME munin-node-configure-snmp - A sub-program used by munin-node-configure to do the actual SNMP probing. =head1 SYNOPSIS munin-node-configure-snmp [options] [host/cidr] [...] =head1 DESCRIPTION Munin's node is a daemon that Munin connects to fetch data. This data is stored in .rrd-files, and later graphed and htmlified. It's designed to let it be very easy to graph new datasources. Munin-node-configure-snmp is a program that is used by another program in the Munin package, munin-node-configure, to do SNMP probing of hosts or networks. This program is only meant to be run by other programs in the Munin package, not by hand. =head1 VERSION This is munin-node v1.2.6 =head1 AUTHORS Jimmy Olsen. =head1 BUGS munin-node-configure-snmp does not have any known bugs. Please report other bugs in the bug tracker at L. =head1 COPYRIGHT Copyright © 2004 Jimmy Olsen. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. This program is released under the GNU General Public License =cut