aboutsummaryrefslogtreecommitdiffstats
path: root/clients/snmpfetch.pl
diff options
context:
space:
mode:
Diffstat (limited to 'clients/snmpfetch.pl')
-rwxr-xr-xclients/snmpfetch.pl281
1 files changed, 281 insertions, 0 deletions
diff --git a/clients/snmpfetch.pl b/clients/snmpfetch.pl
new file mode 100755
index 0000000..487ae11
--- /dev/null
+++ b/clients/snmpfetch.pl
@@ -0,0 +1,281 @@
+#! /usr/bin/perl
+use BER;
+use DBI;
+use POSIX;
+use Time::HiRes;
+use Net::Telnet;
+use strict;
+use warnings;
+require 'SNMP_Session.pm';
+
+use lib '../include';
+use nms;
+use threads;
+
+# normal mode: fetch switches from the database
+# instant mode: poll the switches specified on the command line
+if (defined($ARGV[0])) {
+ poll_loop(@ARGV);
+} else {
+ my $threads = 50;
+ for (1..$threads) {
+ threads->create(\&poll_loop);
+ }
+ poll_loop();
+}
+
+sub poll_loop {
+ my @switches = @_;
+ my $instant = (scalar @switches > 0);
+ my $timeout = 15;
+
+ my $dbh = nms::db_connect();
+ $dbh->{AutoCommit} = 0;
+
+ my $qualification;
+ if ($instant) {
+ $qualification = "sysname=?";
+ } else {
+ $qualification = <<"EOF";
+ (last_updated IS NULL OR now() - last_updated > poll_frequency)
+ AND (locked='f' OR now() - last_updated > '15 minutes'::interval)
+ AND ip is not null
+EOF
+ }
+
+ my $qswitch = $dbh->prepare(<<"EOF")
+SELECT
+ *,
+ DATE_TRUNC('second', now() - last_updated - poll_frequency) AS overdue
+FROM
+ switches
+ NATURAL LEFT JOIN switchtypes
+WHERE $qualification
+ORDER BY
+ priority DESC,
+ overdue DESC
+LIMIT 1
+FOR UPDATE OF switches
+EOF
+ or die "Couldn't prepare qswitch";
+ my $qlock = $dbh->prepare("UPDATE switches SET locked='t', last_updated=now() WHERE switch=?")
+ or die "Couldn't prepare qlock";
+ my $qunlock = $dbh->prepare("UPDATE switches SET locked='f', last_updated=now() WHERE switch=?")
+ or die "Couldn't prepare qunlock";
+ my $qpoll = $dbh->prepare("INSERT INTO polls (time, switch, port, bytes_in, bytes_out, errors_in, errors_out) VALUES (timeofday()::timestamp,?,?,?,?,?,?)")
+ or die "Couldn't prepare qpoll";
+ my $qtemppoll = $dbh->prepare("INSERT INTO temppoll (time, switch, temp) VALUES (timeofday()::timestamp,?::text::int,?::text::float)")
+ or die "Couldn't prepare qtemppoll";
+ my $qcpupoll = $dbh->prepare("INSERT INTO cpuloadpoll (time, switch, entity, value) VALUES (timeofday()::timestamp,?::text::int,?,?)")
+ or die "Couldn't prepare qtemppoll";
+
+ while (1) {
+ my $sysname;
+ if ($instant) {
+ $sysname = shift @ARGV;
+ exit if (!defined($sysname));
+ $qswitch->execute($sysname)
+ or die "Couldn't get switch";
+ } else {
+ # Find a switch to grab
+ $qswitch->execute()
+ or die "Couldn't get switch";
+ }
+ my $switch = $qswitch->fetchrow_hashref();
+
+ if (!defined($switch)) {
+ $dbh->commit;
+
+ if ($instant) {
+ mylog("No such switch $sysname available, quitting.");
+ exit;
+ } else {
+ mylog("No available switches in pool, sleeping.");
+ sleep 15;
+ next;
+ }
+ }
+
+ $qlock->execute($switch->{'switch'})
+ or die "Couldn't lock switch";
+ $dbh->commit;
+
+ if ($switch->{'locked'}) {
+ mylog("WARNING: Lock timed out on $switch->{'ip'}, breaking lock");
+ }
+
+ my $msg;
+ if (defined($switch->{'overdue'})) {
+ $msg = sprintf "Polling ports %s on %s (%s), %s overdue.",
+ $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'}, $switch->{'overdue'};
+ } else {
+ $msg = sprintf "Polling ports %s on %s (%s), never polled before.",
+ $switch->{'ports'}, $switch->{'ip'}, $switch->{'sysname'};
+ }
+ mylog($msg);
+
+ my $ip = $switch->{'ip'};
+
+ if ($ip eq '127.0.0.1') {
+ mylog("Polling disabled for this switch, skipping.");
+ $qunlock->execute($switch->{'switch'})
+ or die "Couldn't unlock switch";
+ $dbh->commit;
+ next;
+ }
+
+ my $community = $switch->{'community'};
+ my $start = [Time::HiRes::gettimeofday];
+ eval {
+ my $session;
+ if ($switch->{'wide_counters'}) {
+ $session = SNMPv2c_Session->open($ip, $community, 161)
+ or die "Couldn't talk to switch";
+ } else {
+ $session = SNMP_Session->open($ip, $community, 161)
+ or die "Couldn't talk to switch";
+ }
+ my @ports = expand_ports($switch->{'ports'});
+
+ for my $port (@ports) {
+ my $in = fetch_data($session, $port, 0, $switch->{'wide_counters'});
+ die $switch->{'switch'}.":$port: failed reading in" if !defined $in;
+ my $out = fetch_data($session, $port, 1, $switch->{'wide_counters'});
+ die $switch->{'switch'}.":$port: failed reading out" if !defined $out;
+ my $ine = fetch_errors($session, $port, 0);
+ die $switch->{'switch'}. ":$port: failed reading in-errors" if !defined $ine;
+ my $oute = fetch_errors($session, $port, 1);
+ die $switch->{'switch'}. ":$port: failed reading out-errors" if !defined $oute;
+
+ $qpoll->execute($switch->{'switch'}, $port, $in, $out, $ine, $oute) || die "%s:%s: %s\n", $switch->{'switch'}, $port, $in;
+ }
+ $session->close;
+ };
+ if ($@) {
+ mylog("ERROR: $@ (during poll of $ip)");
+ $dbh->rollback;
+ }
+
+ my $elapsed = Time::HiRes::tv_interval($start);
+ $msg = sprintf "Polled $switch->{'ip'} in %5.3f seconds.", $elapsed;
+ mylog($msg);
+
+ $qunlock->execute($switch->{'switch'})
+ or warn "Couldn't unlock switch";
+ $dbh->commit;
+ }
+}
+
+sub fetch_data {
+ my ($session, $port, $out, $wide_counters) = @_;
+
+ my $oid;
+ if ($wide_counters) {
+ if ($out) {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 10, $port); # interfaces.ifTable.ifEntry.ifHCOutOctets
+ } else {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 31, 1, 1, 1, 6, $port); # interfaces.ifTable.ifEntry.ifHCInOctets
+ }
+ } else {
+ if ($out) {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 16, $port); # interfaces.ifTable.ifEntry.ifOutOctets
+ } else {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 10, $port); # interfaces.ifTable.ifEntry.ifInOctets
+ }
+ }
+
+ return fetch_snmp($session, $oid);
+}
+sub fetch_errors {
+ my ($session, $port, $out) = @_;
+
+ my $oid;
+ if ($out) {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 20, $port); # interfaces.ifTable.ifEntry.ifOutErrors
+ } else {
+ $oid = BER::encode_oid(1, 3, 6, 1, 2, 1, 2, 2, 1, 14, $port); # interfaces.ifTable.ifEntry.ifInErrors
+ }
+
+ return fetch_snmp($session, $oid);
+}
+
+sub fetch_snmp {
+ my ($session, $oid) = @_;
+
+ if ($session->get_request_response($oid)) {
+ my ($bindings) = $session->decode_get_response ($session->{pdu_buffer});
+ my $binding;
+ while ($bindings ne '') {
+ ($binding,$bindings) = &decode_sequence ($bindings);
+ my ($oid,$value) = &decode_by_template ($binding, "%O%@");
+ return BER::pretty_print($value);
+ }
+ }
+ die "Couldn't get info from switch";
+}
+
+sub expand_ports {
+ my $in = shift;
+ my @ranges = split /,/, $in;
+ my @ret = ();
+
+ for my $range (@ranges) {
+ if ($range =~ /^\d+$/) {
+ push @ret, $range;
+ } elsif ($range =~ /^(\d+)-(\d+)$/) {
+ for my $i ($1..$2) {
+ push @ret, $i;
+ }
+ } else {
+ die "Couldn't understand '$range' in ports";
+ }
+ }
+
+ return (sort { $a <=> $b } @ret);
+}
+
+sub mylog {
+ my $msg = shift;
+ my $time = POSIX::ctime(time);
+ $time =~ s/\n.*$//;
+ printf STDERR "[%s] %s\n", $time, $msg;
+}
+
+#sub switch_exec {
+# my ($cmd, $conn) = @_;
+#
+# # Send the command and get data from switch
+## $conn->dump_log(*STDOUT);
+# my @data = $conn->cmd($cmd);
+# my @lines = ();
+# foreach my $line (@data) {
+# # Remove escape-7 sequence
+# $line =~ s/\x1b\x37//g;
+# push @lines, $line;
+# }
+#
+# return @lines;
+#}
+
+#sub switch_connect {
+# my ($ip) = @_;
+#
+# my $conn = new Net::Telnet( Timeout => $timeout,
+# Dump_Log => '/tmp/dumplog-tempfetch',
+# Errmode => 'return',
+# Prompt => '/es-3024|e(\-)?\d+\-\dsw>/i');
+# my $ret = $conn->open( Host => $ip);
+# if (!$ret || $ret != 1) {
+# return (0);
+# }
+# # XXX: Just send the password as text, I did not figure out how to
+# # handle authentication with only password through $conn->login().
+# #$conn->login( Prompt => '/password[: ]*$/i',
+# # Name => $password,
+# # Password => $password);
+# my @data = $conn->cmd($password);
+# # Get rid of banner
+# $conn->get;
+# return $conn;
+#}
+