# -*- perl -*- # # # SNMP::Monitor - a Perl package for monitoring remote hosts via SNMP # # # Copyright (C) 1998 Jochen Wiedmann # Am Eisteich 9 # 72555 Metzingen # Germany # # Phone: +49 7123 14887 # Email: joe@ispsoft.de # # All rights reserved. # # You may distribute this module under the terms of either # the GNU General Public License or the Artistic License, as # specified in the Perl README file. # use strict; require Mail::Internet; require SNMP; require DBI; require Sys::Syslog; package SNMP::Monitor; $SNMP::Monitor::VERSION = '0.1012'; sub new ($$) { my($proto, $config) = @_; my($self) = { config => $config, debug => $config->{debug} }; bless($self, (ref($proto) || $proto)); if ($self->{debug}) { Sys::Syslog::syslog('debug', "Entering debugging mode"); } $self->{dbh} = DBI->connect($config->{dbi_dsn}, $config->{dbi_user}, $config->{dbi_pass}); if (!$self->{dbh}) { die "Cannot connect to database: " . $DBI::errstr; } $self->{sessions} = []; my($router, $interface); foreach $router (values(%{$config->{hosts}})) { my $session = SNMP::Monitor::Session->new($self, $router); $session->{events} = []; foreach $interface (@{$session->{interfaces}}) { my $class; foreach $class (@{$interface->{events}}) { $@ = ''; eval "require $class"; if ($@) { die $@; } my $event = $class->new($session, $interface); push(@{$session->{events}}, $event); } } if (@{$session->{events}}) { push(@{$self->{sessions}}, $session); } } if (!@{$self->{sessions}}) { die "No sessions found"; } $self; } sub Loop ($) { my($self) = @_; my $time = time; my($event, $session, $wait); while (1) { my $nexttime = time() + 60; foreach $session (@{$self->{sessions}}) { my $queued = 0; foreach $event (@{$session->{events}}) { if ($event->{count} == 0) { $event->Queue(); $queued = 1; } } if ($queued) { $session->Query(); } foreach $event (@{$session->{events}}) { if ($event->{count} == 0) { $event->Process(); $event->{count} = $event->{init_count}; } $event->{count} -= 1; } } my $wait = $nexttime - time(); if ($wait > 0) { sleep $wait; } } } sub Configuration ($$) { my($class, $file) = @_; if (! -r $file) { die "Cannot read configuration file $file"; } $@ = ''; my $config = do($file); if ($@) { die "Error while reading configuration file $file: $@"; } $config; } sub Message ($@) { my($self, %attr) = @_; if (!$attr{'to'}) { $attr{'to'} = $self->{'config'}->{'email'}; } my $body = delete $attr{body}; my $host = (delete $attr{mailhost}) || $self->{'config'}->{'mailhost'} || '127.0.0.1'; my $head = Mail::Header->new(); my($val, $header); foreach $header ('to', 'cc', 'bcc') { if ($attr{$header}) { foreach $val (split(/,/, $attr{$header})) { $head->add($header, $val); } delete $attr{$header}; } } while (($header, $val) = each %attr) { $head->add($header, $val); } my $mail = Mail::Internet->new([$body], Header => $head); $mail->smtpsend(); 1; } package SNMP::Monitor::Session; sub new ($$$) { my($proto, $monitor, $attr) = @_; my $self = { %$attr }; bless($self, (ref($proto) || $proto)); $self->{config} = $monitor; $self->{debug} = $monitor->{debug}; my $session = SNMP::Session->new(%$self); if (!$session) { die "Cannot create session for router " . $self->{name}; } $self->{session} = $session; $self; } sub Query ($) { my($self) = @_; my $session = $self->{session}; my @varlist; my $vr = $self->{vars_registered}; for (my $i = 0; $i < @$vr; $i++) { if ($vr->[$i]->[1]) { push(@varlist, $vr->[$i]->[0]); $vr->[$i]->[1] = 0; } } my $vl = SNMP::VarList->new(@varlist); if ($self->{debug}) { Sys::Syslog::syslog('debug', "Sending query: Session = %s", $session->{name}); foreach $vr (@$vl) { Sys::Syslog::syslog('debug', "Query variable: %s.%d", $vr->[0], $vr->[1]); } } $session->get($vl); if ($self->{debug}) { Sys::Syslog::syslog('debug', "Query response: Session = %s", $session->{name}); foreach $vr (@$vl) { Sys::Syslog::syslog('debug', "Query variable: %s.%d => %s", $vr->[0], $vr->[1], $vr->[2]); } } $self->{'time'} = time(); if ($session->{ErrorNum}) { if (!$self->{err_msg_sent}) { my $time = localtime(time()); my $name = $session->{name}; my $host = $session->{DestHost}; my $errmsg = $session->{ErrorStr}; $self->{err_msg_sent} = $self->Message(subject => "No response from $name", body => <<"MSG"); Warning: I did not get an SNMP reply from router $name ($host) at $time. The error message is: $errmsg You will not receive any further messages until the next successfull request or restart of SNMP::Monitor. MSG } } else { $self->{err_msg_sent} = 0; } } sub Message ($@) { my($self, %attr) = @_; if (!$attr{'to'} && $self->{email}) { $attr{'to'} = $self->{email}; } $self->{config}->Message(%attr); } package SNMP::Monitor::Event; sub new ($$$) { my($proto, $session, $attr) = @_; my $self = { %$attr }; bless($self, (ref($proto) || $proto)); $self->{count} = 0; $self->{init_count} = 1; $self->{session} = $session; $self; } sub Queue ($) { my($self) = @_; my $i; my $vr = $self->{vars_registered}; my $session = $self->{session}; if (!$vr) { $vr = $self->{vars_registered} = []; if (!$session->{vars_registered}) { $session->{vars_registered} = []; } my($var, $ref); foreach $var (@{$self->{vars}}) { for ($i = 0; $i < @{$session->{vars_registered}}; $i++) { $ref = $session->{vars_registered}->[$i]; if ($var->[0] eq $ref->[0]->[0] && $var->[1] eq $ref->[0]->[1]) { # Variable already present last; } } if ($i >= @$vr) { # New variable $session->{vars_registered}->[$i] = [$var, 0]; } push(@$vr, $i); } delete $self->{vars}; } for ($i = 0; $i < @$vr; $i++) { $session->{vars_registered}->[$vr->[$i]]->[1] = 1; } $session->{events_queued} = 1; } sub Process ($) { 1; } sub Message ($@) { my($self, %attr) = @_; if (!$attr{'to'} && $self->{email}) { $attr{'to'} = $self->{email}; } $self->{session}->Message(%attr); } 1; =head1 NAME SNMP::Monitor - a Perl package for monitoring remote hosts via SNMP =head1 SYNOPSIS require SNMP::Monitor; # Read a configuration file my $config = SNMP::Monitor->Configuration("/etc/snmpmon/config"); # Create a new monitor my $monitor = SNMP::Monitor->new($config); # Start monitoring (endless loop, never returns) $monitor->Loop(); =head1 DESCRIPTION The SNMP::Monitor module is a package for checking and watching arbitrary values via SNMP. Events can be triggered, Logging can be done, whatever you want. The package is based on the SNMP package, but it is merely created for system administrators and not for programmers. The following class methods are offered: =over 8 =item Configuration($file) (Class method) Read a monitor configuration from C<$file>. The module I is available for creating such files. See L. No error indicators, the method dies in case of trouble. =item new($config) (Class method) This is the monitor constructor. Given a monitor configuration C<$config>, as returned by the I method (see above), returns a new monitor. Internally the monitor is a set of sessions (instances of SNMP::Monitor::Session) and events (instances of SNMP::Monitor::Event). Currently there are two available event classes: One for watching an interface status and one for logging interface loads into a database. See L<"EVENT IMPLEMENTATION"> below. =item Message(\%attr) (Instance method) Called for sending an E-Mail via the I module. See L. The following keys are supported in the hash ref C<\%attr>: =over 12 =item I The message body. =item I A host being used as SMTP server. By default the mail host from the config file or localhost are used. =item I =item I =item I Mail recipients, or recipient lists (comma separated values). =back All other keys are used as mail headers, in particular the attributes I and I should be present. =item Loop() (Instance method) The monitor enters an endless loop. Every 60 seconds it checks its event lists and requests SNMP values, if desired. (You cannot rely on these 60 seconds, though, because the SNMP package doesn't support asynchronous SNMP requests.) =back =head1 EVENT IMPLEMENTATION Currently only two event classes are available: The I class for watching an interface status and the I class for logging interface utilization into a database. However, it is fairly simple two add arbitrary new event classes: All you need is a constructor method I for setting up an SNMP variable list and a method I for processing these lists when the monitor requested it for you. Let's see the I class for an example: sub new ($$$) { my($proto, $session, $attr) = @_; my $self = $proto->SUPER::new($session, $attr); my $table = "interfaces.ifTable.ifEntry"; my $v = "SNMP::Varbind"; my $num = $self->{num}; $self->{vars} = [ $v->new(["$table.ifDescr", $num]), $v->new(["$table.ifAdminStatus", $num]), $v->new(["$table.ifOperStatus", $num])]; $self; } The method starts by calling its super classes constructor, SNMP::Monitor::Event::new. Once that is done, it creates an attribute C<$self-E{'vars'}>, an array ref of SNMP variables that the monitor should fill in. It might additionally initialize the attribute C<$self-E{'init_count'}>: This attribute defaults to 1, telling the monitor, that it should request variables for this event every minute. For example, the I module is using a value of 5, because logging every 5 minutes seems to me to be sufficient. The second method to overwrite is the I method. This is called whenever the monitor has fetched SNMP variables for the event. Here's the I method of the I class: sub Process ($) { my($self) = @_; my $session = $self->{session}; my $vr_session = $session->{vars_registered}; my $vr_self = $self->{vars_registered}; # The following list corresponds to the list in the 'new' method. # This is important when calculation the index $i in $vr_self->[$i]. my $ifDescr = $vr_session->[$vr_self->[0]]->[0]->[2]; my $ifAdminStatus = $vr_session->[$vr_self->[1]]->[0]->[2]; my $ifOperStatus = $vr_session->[$vr_self->[2]]->[0]->[2]; my $num = $self->{num}; # Now do anything with the values; in case of the IfStatus # this means sending a mail whenever the status has changed ... # Note the current value for the next time we are called $self->{ifAdminStatus} = $ifAdminStatus; $self->{ifOperStatus} = $ifOperStatus; } =head1 AUTHOR AND COPYRIGHT This module is Copyright (C) 1998 by Jochen Wiedmann Am Eisteich 9 72555 Metzingen Germany Phone: +49 7123 14887 Email: joe@ispsoft.de All rights reserved. You may distribute this module under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. =head1 SEE ALSO L, L, L =cut