#!/usr/local/bin/perl

eval 'exec /usr/local/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

#  junipoll - a JUNIper router snmp POLLer (for firewall filter counters)
#  Copyright (C) 2002-2005  Dave Plonka
#
#  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; either version 2 of the License, or
#  (at your option) any later version.
#
#  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., 675 Mass Ave, Cambridge, MA 02139, USA.

# $Id: junipoll.PL,v 1.18 2005/05/19 19:39:33 dplonka Exp $
# Dave Plonka <plonka@doit.wisc.edu>, original May 22 2002

# { CONFIGURATION SECTION BEGIN ################################################

# { these can be specified from the command line:
my @peer = (); # [community@]host [...]
my $default_community = 'public';
my $verbose = 0;
# }

# } CONFIGURATION SECTION END ##################################################

use Altoids;   # http://net.doit.wisc.edu/~plonka/Altoids/
use RRDs;      # http://ee-staff.ethz.ch/~oetiker/webtools/rrdtool/
use Getopt::Std;
use FindBin;

# convert the RCS revision to a reasonable Exporter VERSION:
my $VERSION;
'$Revision: 1.18 $' =~ m/(\d+)\.(\d+)/ && (( $VERSION ) = sprintf("%d.%03d", $1, $2));

usage(2) if !getopts('vc:nmhV');
if ($opt_v or $opt_n) {
   $verbose = 1
}
if ($opt_c) {
   $default_community = $opt_c
}
if (@ARGV) {
   @peer = @ARGV
}

if ($opt_V) {
   print <<_EOF_
This is $FindBin::Script version $VERSION

Copyright 2002-2003, Dave Plonka

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; either version 2 of the License, or
(at your option) any later version.

_EOF_
   ;
   exit(0);
}

usage(0) if $opt_h;

usage(2) unless @peer;

# map the OIDs (as defined in their MIB):
my $hints = map_oids();

$snmp = Altoids->new($hints);
die unless ref($snmp);

foreach my $peer (@peer) {
   if ($peer =~ m/^(\S+)\@(\S+)$/) {
      $community = $1;
      $host = $2
   } else {
      $community = $default_community;
      $host = $peer
   }
   if (!$snmp->open($host, $community, 161)) {
      warn "$_: open failed.\n";
      next
   }
   %retblock = $snmp->walk('jnxFWCounterPacketCount', 'jnxFWCounterByteCount');
   my $when = time;
   if (-1 == $retblock{-1}) {
      warn "$_: get failed.\n";
      next
   }
   hashprint($host, %retblock) if $verbose;
   $snmp->close();

   my($filter, $counter_hr, %results, $counter, $hr);
  while (($filter, $counter_hr) = each(%{$retblock{jnxFWCounterPacketCount}})) {
       while (($counter, $hr) = each(%$counter_hr)) {
	  # magic-number "2" means "counter" (from "JUNIPER-FIREWALL-MIB")
	  $results{"${host}_${filter}_${counter}"}{WHEN} = $when;
          $results{"${host}_${filter}_${counter}"}{pkts} = $hr->{2};
          $results{"${host}_${filter}_${counter}"}{bytes} =
	     $retblock{jnxFWCounterByteCount}{$filter}{$counter}{2};
       }
  }
   updateRRDs(\%results) unless $opt_n;
}

exit;

sub updateRRDs {
   my $results = shift @_;
   my($name, $hr);
   while (my($name, $hr) = each %{$results}) {
      my $file = "$name.rrd";
      $file =~ s|/|_|g;
      createGeneralRRD($file,
                       $hr->{WHEN},
                       $opt_m? qw(COUNTER ds0 COUNTER ds1) :
                               qw(COUNTER pkts COUNTER bytes)
                      ) unless -f $file;
      my @values = ();
      push(@values, $hr->{pkts}, $hr->{bytes});
      updateRRD($file, $hr->{WHEN}, @values);
   }
}

sub createGeneralRRD {
   my $file = shift;
   die if '' eq $file;
   my $when = shift;
   die if -1 == $when;

   die unless @_; # DS types and names are required

   my $time_t = $when;

   my $startwhen = $time_t - 300;
   my($name, $type, @DS);
   while (($type = shift(@_)) &&
	  ($name = shift(@_))) {
      push(@DS, "DS:${name}:${type}:400:U:U")
   }
   RRDs::create($file,
      '--start', $startwhen,
      '--step', 300,
      @DS,
      qw(
         RRA:AVERAGE:0:1:105408
         RRA:AVERAGE:0:6:600
         RRA:AVERAGE:0:24:600
         RRA:AVERAGE:0:288:1827
         RRA:MAX:0:24:600
         RRA:MAX:0:288:1827
	 )
      );
   my $err=RRDs::error;
   warn "ERROR creating $file: $err\n" if $err;
}

sub updateRRD {
   my $file = shift;
   die if '' eq $file;
   my $when = shift;
   die if -1 == $when;
   my @values = @_;

   if ($verbose) {
      print "RRDs::update $file $when @values ...\n"
   }
   # strip off leading '+' signs otherwise, as of rrdtool-1.0.46,
   # update complains "not a simple integer"
   grep { s/^\+// } @values;
   RRDs::update($file, $when . ':' . join(':', @values));
   my $err=RRDs::error;
   if ($err) {
      warn "ERROR updating $file: $err\n"
   }
}

sub map_oids {
# { These OIDs were glommed from Juniper's mib:
#    http://www.juniper.net/techpubs/software/junos53/swconfig53-net-mgmt/html/mib-firewall.txt
#

$hints = {
   sysDescr => { # from "RFC1213-MIB"
      humanoid => 'iso.org.dod.internet.mgmt.mib-2.system.sysDescr',
           oid => '1.3.6.1.2.1.1.1',
           rhs => 'string',
   },

   iso => {
      oid => '1',
      humanoid => 'iso',
   },

   org => {
      oid => '1.3',
      humanoid => 'iso.org',
   },

   dod => {
      oid => '1.3.6',
      humanoid => 'iso.org.dod',
   },

   internet => {
      oid => '1.3.6.1',
      humanoid => 'iso.org.dod.internet',
   },

   mgmt => {
      oid => '1.3.6.1.2',
      humanoid => 'iso.org.dod.internet.mgmt',
   },

   private => {
      oid => '1.3.6.1.4',
      humanoid => 'iso.org.dod.internet.private',
   },

   enterprises => {
      oid => '1.3.6.1.4.1',
      humanoid => 'iso.org.dod.internet.private.enterprises',
   },

   # { The following were glommed from "JUNIPER-FIREWALL-MIB":

   juniperMIB => {
      oid => '1.3.6.1.4.1.2636',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB',
   },

   jnxMibs => {
      oid => '1.3.6.1.4.1.2636.3',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs',
   },

   jnxFirewalls => {
      oid => '1.3.6.1.4.1.2636.3.5',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls',
   },

   jnxFirewallCounterTable => {
      oid => '1.3.6.1.4.1.2636.3.5.2',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable',
   },

   # INDEX of jnxFWCounterFilterName, jnxFWCounterName, jnxFWCounterType

   jnxFirewallCounterEntry => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterFilterName => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.1',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterFilterName',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterName => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.2',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterName',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterType => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.3',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterType',
      lhs => ['nstring', 'nstring'],
      rhstrans => {
	 1 => 'other(1)',
	 2 => 'counter(2)',
	 3 => 'policer(3)',
      }
   },

   jnxFWCounterPacketCount => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.4',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterPacketCount',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterByteCount => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.5',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterByteCount',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterDisplayFilterName => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.6',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterDisplayFilterName',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterDisplayName => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.7',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterDisplayName',
      lhs => ['nstring', 'nstring', 'index'],
   },

   jnxFWCounterDisplayType => {
      oid => '1.3.6.1.4.1.2636.3.5.2.1.6',
      humanoid => 'iso.org.dod.internet.private.enterprises.juniperMIB.jnxMibs.jnxFirewalls.jnxFirewallCounterTable.jnxFirewallCounterEntry.jnxFWCounterDisplayType',
      lhs => ['nstring', 'nstring'],
      rhstrans => {
	 1 => 'other(1)',
	 2 => 'counter(2)',
	 3 => 'policer(3)',
      }
   },

   # }

};

return $hints
}

################################################################################

sub usage {
   print STDERR <<_EOF_
usage: $FindBin::Script [-v|n|V] [-m] [-c default_community] [community@]host [...]
       -v - verbose (mnemonic: 'v'erbose)
       -n - don't create or update RRD files, just show counters (implies "-v")
            (mnemonic: 'n'o, don't do anything)
       -m - name the data sources "ds0" and "ds1", rather than "pkts" and
	    "bytes", respectively, ala MRTG when "LogFormat: rrdtool" is set.
	    (mnemonic: 'm'rtg mode)
       -h - shows this usage information
            (mnemonic: 'h'elp)
       -V - show info about this $FindBin::Script version
            (mnemonic: 'V'ersion)
_EOF_
   ;
   exit $_[0]
}

################################################################################

sub hashprint {
   my $prefix  = shift @_;
   my %hash  = @_;
   my ($val, $key);
   my $delim = ($opt_d? $opt_d : '.');

   while (($key, $val) = each(%hash)) {
      if ('HASH' eq ref($val)) {
         hashprint($prefix? $prefix . $delim . $key : $key, %{$val})
      } else {
         print $prefix, $delim, "$key = $val\n"
      }
   }
}
