#!/usr/local/bin/perl
#
# $Header: /home/vikas/src/nocol/perlnocol/RCS/bgpmon,v 2.2 1999/10/26 04:50:37 vikas Exp $
#
#        bgpmon - perl monitor for BGP processes
#
#   logs into a router and grabs/parses a bgp summary, then displays
# all down peers.  compatible with most routers based on logic given in
# bgpmon-confg.  sample configuration includes CISCO and ASCEND_GRF object
# definitions.
#
#   i've written my own nocol_main routine primarily because nocollib.pl's
# main routine doesn't support the flexibility this program will need to
# report the several different variable types, or handle the long
# configuration.  i still do, however, use nocollib.pl's startup and event
# handling routines so that this will be compatible with future versions of
# NOCOL should nocollib.pl change.
#
# Author: Jonathan A. Zdziarski, jonz@netrail.net
#
#       Copyright 1998 Jonathan A. Zdziarski, jonz@netrail.net
#
# This software is distributed as part of the NOCOL/SNIPS package.
#
# FEATURES:
#        - works with ascend, bay and cisco routers
#        - ability to add your own router logic for different kinds of routers
#        - ability to have different passwords for each router
#        - line-by-line peer configuration, or...
#        - one-step router configuration (supply the router, and bgpmon will
#          grab all the peers off of it, name them, and enter them
#          as individual events in nocol)
#        - router-down assumption (peers are down/unchanged if a router is
#          unreachable)
#        - one-step router updating (if a router was down when bgpmon started,
#          it will attempt to reach the router later and add peers)
#        - different severity settings defined by as number
#
# CONFIGURATION:
#
#        - If you specify ASNUM:NAME as the name of a single peer,
#          ASNUM will define the as number of that peer, rather than a bgp
#          summary.  This is useful to retain proper severity levels in the
#          event that bgpmon is never able to assertain the as number from
#          a bgp summary (e.g. the router is down when bgpmon is started)
#        - Did a sprintf("%6d", ASN) on all ASN's (internally) to prevent
#          router/config differences
#
#
# STRUCTURES USED:
#       @items  = peer \t rtr \t peerip
#       @routers = rtr
#       $router{$name} = type addr passwrd user
#       $object{$type} = port \t commands
#       $comp{$type} = array \t up \t down
#
#####
$nocolroot = '/nocol';                  # SET_THIS
push(@INC, "$nocolroot/bin");

require 5.000;
require "nocollib.pl";

# bgpmon variables
$POLLINTERVAL=60*5;			# 5 minute polling interval
$UPDATEINTERVAL=60*120;			# Update router peers ever 2 hours
$severity{'DEFAULT'} = $E_ERROR;	# Default severity
$ASSUME = 0;                            # Assume all peers down if router is
                                        # unreachable
$DEBUG = 0;

# nocollib.pl variables
$debug = $DEBUG;
$libdebug = 0;
$varunits = "State";

$|=1; # unbuffered stdout 

# subroutine: readconf
#    purpose: reads in the master configuration/parses
#             call this only after nocol_startup
sub readconf {
  open(CONFIG,"<$cfile") || die("$0: $cfile: $!");
  while(<CONFIG>) {
    chomp;
    s/\t+/ /g; # replace tabs with spaces
    s/^\s*(.*?)\s*$/$1/; # trim whitespace
    next if (/^#/);
    next if (/^$/);

    my($definition, $parms) = split(/\s+/, $_, 2); 

    if ($definition =~ /^object$/i) {  # commands to get/parse bgp summary 
      my($name, $port, $array, $up, $commands) = split(/\s+/, $parms, 5);
      $object{$name} = "$port\t$commands";
      $comp{$name} = "$array\t$up";
      $DEBUG && print "$0: new object ($name) = $port, $commands\n";
    }
    elsif ($definition =~ /^router_def$/i) {  # router name, type, addr, pass
      my($name, $type, $address, $pass, $user) = split(/\s+/, $parms);
      $router{$name} = "$type\t$address\t$pass\t$user";
      $DEBUG && print "$0: new router ($name) is a $type ($address)\n";

    }
    elsif ($definition =~ /^router$/i) {  # monitor all peers on this router
      push(@routers, join("\t", split(/\s+/, $parms)));
    }
    elsif ($definition =~ /^severity$/i) {  # set severity level for this as
      my($as, $severity) = split(/\s+/, $parms);
      if ($as>0) { $as=sprintf("%6d", $as); $as =~ s/ /0/g; }
      $DEBUG && print "$0: added severity for as$as - $severity\n";
      $severity{$as}=$$severity;
    }
    elsif ($definition =~ /^peer$/i) {  # monitor this peer on the router
      my($router, $name, $ip) = split(/\s+/, $parms);
      push(@items, "$name\t$router\t$ip");
    }
    elsif ($definition =~ /^assume$/i) {  # down or prev status if unreachable
      my($stat) = split(/\s+/, $parms);
      if ($stat =~ /^unchanged$/i) { $ASSUME = 1; }
      elsif ($stat =~ /^down$/i)  { $ASSUME = 0; }
      else { die "$0: unknown status: $stat\n"; }
    }
    elsif ($definition =~ /^pollinterval$/i) {
      ($POLLINTERVAL) = split(/\s+/, $parms);
    }
    else { print STDERR "$0: unknown definition: $definition (skipping)\n"; }
  }
  close(CONFIG);
  die "$0: nothing to monitor in $cfile; exiting.\n" 
    if ((0>$#items) && (0>$#routers));
}

# MAIN ROUTINE
&nocol_startup;		# nocollib.pl initialization
&readconf;		# read in configuration
&initializepeers;	# init all single peers
&updatepeers;		# grab all peers from routers specified

while(1) { # till death do us part
  my($stime, $deltatime, $updatetime);
  $stime = time;
  $updatetime = time unless ($updatetime > 0);

  foreach $router (keys(%router)) {
    my($router_type, $router_ip, $router_pass, $router_user) = 
      split(/\t/, $router{$router});
    my($router_port, $router_cmds) = split(/\t/, $object{$router_type});
    die "$0: no object definition '$router_type'\n" 
      unless ($object{$router_type});
    die "$0: no comprehension for '$router_type'\n" 
      unless ($comp{$router_type} ne "");

    $router_cmds =~ s/\%PASS(WORD)?\%/$router_pass/gi;
    $router_cmds =~ s/\%USER(NAME)?\%/$router_user/gi;
    my(@buffer) = &getbgpsumm($router_ip, $router_port, 
      split(/\:/, $router_cmds));

    # if we get an error (host unreachable, etc), set all peers on that
    # router down if the assumption is to do so
    if ($BUFFER[0] eq "*ERROR*") {
      print STDERR "$0: $ip: $BUFFER[1]\n";
      next if ($ASSUME == 1);
      foreach $myitem (@items) {
        my($item_name, $item_router, $item_ip) = split(/\t/, $myitem);
        next unless ($item_router eq $router);
        my($asref) = $ascache{$item_ip};
        $varname = "BGP/$item_router";
        $severity = $severity{'DEFAULT'};
        $severity = $severity{$asref} 
          if (($severity{$asref} ne "") && ($asref));
        $severity{"$item_router\t$item_ip"} = $severity;
        &update_event("$item_router\t$item_ip", 0, 0, $severity);
        $DEBUG && print "$0: update_event: $item_router:$item_ip, 0, 0, " .
                        "$severity\n";
      }
    }

    foreach $item (@items) {
      my($item_name, $item_router, $item_ip) = split(/\t/, $item);
      next unless ($item_router eq $router);

      my($arrayref, $up) = split(/\t/, $comp{$router_type});
      my($num_status, $num_ip, $num_as) = split(/\,/, $arrayref);

      foreach $buffer (@buffer) {
        chomp;
        $buffer =~ s/^\s+//g;
        $buffer =~ s/\t+/ /g;
        $buffer =~ s/\s+/ /g;
        my(@fields) = split(/ /, $buffer);
        my($status) = $fields[$num_status];
        my($ipref) = $fields[$num_ip];
        my($asref) = sprintf("%6d", $fields[$num_as]);
        $asref =~ s/ /0/g;
        $ascache{$ipref} = $asref;
        if ($ipref =~ /(\d+\.\d+\.\d+\.\d+)/) { $ipref = $1; } else { next; }
        $DEBUG && print "$0: status of $ipref: $status\n";
        my($peerstatus) = 0; my($value) = 0;
        foreach(split(/\,/, $up)) { if ($status =~ /^$_$/i) { $value = 10; $peerstatus = 1; } }
        
        $varname = "BGP/$item_router";
        $severity = $severity{'DEFAULT'};
        $severity = $severity{$asref} if (($severity{$asref} ne "") && ($asref));
        &update_event("$item_router\t$ipref", $peerstatus, $value, $severity);
        $DEBUG && print "$0: update_event: $item_router:$ipref, $peerstatus, $value, $severity ($asref)\n";
      } # foreach $buffer (@buffer)
    }   # foreach $item (@items)
  }     # foreach $router (keys %router)


  $DEBUG && print "$0: opening $datafile for writing\n";
  open(OEVENTS,">$datafile") || die "$0: $datafile: $!\n";
  foreach $item (@items) {
    my($item_name, $item_router, $item_ip) = split(/\t/, $item);
    if(!$forget{"$item_router\t$item_ip"}) {
      &writeevent(OEVENTS, "$item_router\t$item_ip"); 
    }
  }
  close(OEVENTS);

  $deltatime = time - $updatetime;
  if ($UPDATEINTERVAL < $deltatime) { 
    &updatepeers;
    $updatetime = time;
  }

  $deltatime = time - $stime;
  if ($POLLINTERVAL > $deltatime) { sleep($POLLINTERVAL - $deltatime); }
} # while(1)


# subroutine: getbgpsumm
#    purpose: retrieves a bgp summary from a router
#     syntax: &getbgpsumm [hostname] [port] [commands]
#
# NOTE: make sure the commands end with an 'exit' command to close the socket

sub getbgpsumm {
  my($HOST, $PORT, @COMMANDS) = @_;
  my(@buffer);

  $debug && print "$0: connecting to $HOST:$PORT\n";

  local ($sock) = &newSocket($HOST, $PORT, 'tcp');
  return ("*ERROR*", "newSocket: $!") if (! defined($sock));
  select($sock); $| = 1; select(STDOUT);
  foreach(@COMMANDS) { $debug && print "$0: sending command: $_\n";
                       print $sock "$_\r\n"; sleep 2; }
  while(<$sock>) {  push(@buffer, $_); }
  $debug && print "$0: socket closed, $#buffer lines captured.\n";
  close($sock);

  @buffer;
}       # sub getbgpsumm()

# subroutine: updatepeers
#    purpose: runs through all router objects, and grabs/adds peers to
#             the individual peer list.  this is run every so
#             often within the program, incase a router was down when
#             bgpmon was started, so that it is added when it goes up.
sub updatepeers {
  foreach $router (@routers) {
    my($name) = split(/\t/, $router);
    my($object, $ip, $pass, $user) = split(/\t/, $router{$name});
    my($buffer);
    die "$0: unknown router definition: $name\n" unless ($router{$name} ne "");
    die "$0: unknown object: $object\n" unless ($object{$object} ne "");
    die "$0: no comprehension for $object\n" unless ($comp{$object} ne "");
    my($PORT, $COMMAND) = split(/\t/, $object{$object});
    $COMMAND =~ s/\%PASS(WORD)?\%/$pass/gi;
    $COMMAND =~ s/\%USER(NAME)?\%/$user/gi;
    my(@COMMANDS) = split(/\:/, $COMMAND);
    my(@BUFFER) = &getbgpsumm($ip, $PORT, @COMMANDS);
    if ($BUFFER[0] eq "*ERROR*") {
      print STDERR "$0: $ip: $BUFFER[1]\n";
      next;
    }
    my($arrayref, $up) = split(/\t/, $comp{$object});
    my($num_status, $num_ip, $num_as) = split(/\,/, $arrayref);
    foreach $buffer (@BUFFER) {
      chomp;
      $buffer =~ s/^\s+//g;
      $buffer =~ s/\t+/ /g;
      $buffer =~ s/\s+/ /g;
      my(@fields) = split(/ /, $buffer);
      $status = $fields[$num_status];
      $ipref = $fields[$num_ip];
      $asref = sprintf("%6d", $fields[$num_as]);
      $asref =~ s/ /0/g;
      if ($ipref =~ /(\d+\.\d+\.\d+\.\d+)/) { $ipref = $1; } else { next; }
      $ascache{$ipref} = $asref;
      my($exists) = 0;
      foreach $existing_item (@items) {
        my($existing_name, $existing_routername, $existing_peerip) = 
          split(/\t/, $existing_item);
        if (($existing_routername eq $name) && ($existing_peerip eq $ipref)              && ($existing_routername ne "") && ($existing_peerip ne "")) {
          $exists = 1;
          $DEBUG && print "$0: item already exists: $existing_routername:" .
                          "$existing_peerip\n";
        }
      } # foreach $existing_item 
      if ($exists != 1) {
        push(@items, "-\t$name\t$ipref"); 
        $DEBUG && print "$0: adding $ipref/$name to items list\n";
        my($peerhostname);
        if ($hostcache{$ipref} eq "") {
          $peerhostname = gethostbyaddr(pack('C4', split(/\./, $ipref)), 2);
          if ($peerhostname ne "") {
            my(@fields)=split(/\./, $peerhostname); pop(@fields); pop(@fields);
            $peerhostname=join(".", @fields);
          }
          else { $peerhostname = $ipref; }
          $hostcache{$ipref}=$peerhostname;
        } else { $peerhostname = $hostcache{$ipref}; }
        $varname = "BGP/$name";
        $severity = $severity{'DEFAULT'};
        $severity = $severity{$asref} if ($severity{$asref} ne "");
        $DEBUG && print "$0: initializing: $name:$ipref ($asref) " .
                        "$peerhostname $varname ($severity)\n";
        $asref =~ s/^0+//;
        &init_event("$asref:$peerhostname", $ipref, "$name\t$ipref");
      } # if ($exists)
    }   # foreach $buffer (@BUFFERS)
  }     # foreach $name (@routers)
}       # sub updatepeers()


# subroutine: initializepeers
#    purpose: initializes all peers defined in config
sub initializepeers {
  foreach $item (@items) {
    my($item_name, $rname, $item_ip) = split(/\t/, $item);
    die "$0: unknown router definition: $rname\n" unless ($router{$rname} ne "");
    $DEBUG && print "$0: $item_name, $rname, $item_ip\n";
    
    if (($item_name =~ /^\d+\:/) && ($ascache{$item_ip} eq "")) {
      my($x) = (split(/\:/, $item_name))[0];
      $x = sprintf("%6d", $x);
      $x =~ s/ /0/g;
      $ascache{$item_ip} = $x;  
      $DEBUG && print "$0: setting as$x\n";
    } 

    my($peerhostname);
    if ($hostcache{$item_ip} eq "") {
      $peerhostname = gethostbyaddr(pack('C4', split(/\./, $item_ip)), 2);
      if ($peerhostname ne "") {
        my(@fields)=split(/\./, $peerhostname); pop(@fields); pop(@fields);
        $peerhostname=join(".", @fields);
      }
      else { $peerhostname = $item_ip; }
      $hostcache{$ipref}=$peerhostname;
    }
    else { $peerhostname = $hostcache{$item_ip}; }
    $peerhostname = $item_name if ($item_name ne "-");
    $varname = "BGP/$rname";
    $DEBUG && print "$0: initializing: $peerhostname $varname ($rname:$item_ip)\n";
    &init_event($peerhostname, $item_ip, "$rname\t$item_ip");
  }     # foreach $item
}       # sub initializepeers()

