#!/usr/local/bin/perl
#
# Check bgp neighbor
# 
# You could do ipv4 with just stock check_snmp.  But we want slightly
# prettier messages.  IPv6 is a lot harder.  
#
# $Header: /home/doke/work/nagios/RCS/check_bgp_neighbor,v 1.2 2016/10/05 19:19:35 doke Exp $


use strict;
use warnings;
no warnings 'redefine';
no warnings 'uninitialized';
use Getopt::Long;
use Socket qw( inet_pton AF_INET6 );
use UD::SNMP qw( $mib2 );


use vars qw( $host $community $neighbor $verbose $help @crits @warns
    @unknowns @oks $rc $sep @perfdata $uds );

$host = '';
$community = '';
$neighbor = '';
$verbose = 0;
$help = 0;

sub usage {
    my( $rc ) = @_;
    print "Usage: $0 [-vh] -H <host> -C <community> -n <neighbor-ip>
    -H s  controller hostname [$host]
    -C s  snmp community [$community]
    -n s  bgp neighbor's ip address [$neighbor]
    -v    verbose
    -h    help
";
    exit $rc;
    }

Getopt::Long::Configure ("bundling");
GetOptions(
    'H=s' => \$host,
    'C=s' => \$community,
    'n=s' => \$neighbor,
    'v+' => \$verbose,
    'h' => \$help,
    );
&usage( 0 ) if ( $help );


sub alarm_handler  { 
    die "alarm timeout in pid $$\n";
    exit -1
    }
$SIG{ALRM} = \&alarm_handler;
alarm( 30 );  # make sure we don't hang nagios


if ( ! $host ) {
    print "no host specified\n";
    usage( 3 );
    }
if ( ! $community ) {
    print "no community specified\n";
    usage( 3 );
    }
if ( ! $neighbor ) {
    print "no neighbor specified\n";
    usage( 3 );
    }

check_bgp_neighbor();

$rc = 0;
$sep = '';
if ( $#crits >= 0 ) {
    $rc = 2;
    print "CRITICAL ", join( ", ", @crits );
    $sep = '; ';
    }
if ( $#warns >= 0 ) {
    $rc = 1 if ( $rc == 0 );
    print $sep, "Warning ", join( ", ", @warns );
    $sep = '; ';
    }
if ( $#unknowns >= 0 ) {
    $rc = 3 if ( $rc == 0 );
    print $sep, "Unknown ", join( ", ", @unknowns );
    $sep = '; ';
    }
if ( $rc == 0 || $verbose ) {
    print $sep, "Ok ", join( ", ", @oks );
    }
print( " | ", join( ", ", @perfdata ) ) if ( $#perfdata >= 0 );
print "\n";
exit $rc;


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


sub check_bgp_neighbor {
    my( $neighbor_oid, $ip_n, $oid, $result, @oids, $found, $state, @states );

    $uds = UD::SNMP->new();
    $uds->{verbose} = $verbose;

    if ( $neighbor =~ m/^\s*(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\s*$/ 
            && 1 <= $1 && $1 <= 255 
            && 0 <= $2 && $2 <= 255 
            && 0 <= $3 && $3 <= 255 
            && 0 <= $4 && $4 <= 255 ) { 
	$neighbor = join( '.', ( $1, $2, $3, $4 ) );
	$neighbor_oid = $neighbor;
	$oid = ( "$mib2.15.3.1.2.$neighbor_oid" );

	$verbose && print "neighbor $neighbor\n";
	$verbose && print "neighbor oid $neighbor_oid\n";
	$verbose && print "oid $oid\n";

	@oids = ( $oid );
	$result = $uds->snmp_get( $host, $community, 'bgpPeerState', \@oids );
	if ( ! $result || ! $result->{ $oid } ) { 
	    if ( $uds->{ error } ) { 
		push @unknowns, "can't get bgpPeerState: " . $uds->{ error }; 
		}
	    else { 
		push @unknowns, "can't get bgpPeerState: no such neighbor"; 
		}
	    return;
	    }
	$state = $result->{ $oid };
	}

    # ipv6 on juniper
    # based on http://stackoverflow.com/questions/4800691/perl-ipv6-address-expansion-parsing
    elsif ( $neighbor =~ m/^\s*([\da-f:]{2,39})\s*$/i ) { 
	$ip_n = inet_pton( AF_INET6, $1 );
        $neighbor = join( ":", unpack( "H4H4H4H4H4H4H4H4", $ip_n ) );
        if ( $neighbor !~ m/[\da-f:]{39}/i ) { 
	    push @unknowns, "invalid neighbor ipv6 address";
	    return;
	    }
        $neighbor_oid = join( ".", unpack( "C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2", 
	    $ip_n ) );
	$verbose && print "neighbor $neighbor\n";
	$verbose && print "neighbor oid $neighbor_oid\n";

	# jnxBgpM2PeerState
	$oid = "1.3.6.1.4.1.2636.5.1.1.2.1.1.1.2";
	#$uds->{ verbose } = 4;
	$result = $uds->snmp_walk( $host, $community, 'jnxBgpM2PeerState', 
	    $oid );
	if ( ! $result ) { 
	    if ( $uds->{ error } ) { 
		push @unknowns, "can't get jnxBgpM2PeerState: " . $uds->{ error }; 
		}
	    else { 
		push @unknowns, "can't get jnxBgpM2PeerState"; 
		}
	    return;
	    }
	$found = 0;
	foreach $oid ( keys %$result ) { 
	    if ( $oid =~ m/\Q$neighbor_oid\E$/ ) { 
		$state = $result->{ $oid };
		$found = 1;
		$verbose && print "$oid = $state\n";
		}
	    }
	if ( ! $found ) { 
	    push @unknowns, "cannot find bgp neighbor in jnxBgpM2PeerState";
	    return;
	    }
	}
    else { 
	push @unknowns, "invalid neighbor ip address";
	return; 
	}

    @states = (                                         
	'unknown',
	'idle',  # 1
	'connect',  # 2
	'active',  # 3
	'opensent',  # 4
	'openconfirm',  # 5
	'established',  # 6
	'unknown',
	);
    $verbose && print "bgpPeerState $state $states[ $state ]\n";

    if ( $state == 6 ) {
	push @oks, "bgp established";
	}
    elsif ( $state == 5 ) {
	push @warns, "bgp session is in openconfirm";
	}
    else {  
	push @crits, "bgp session is in state $state $states[ $state ]";
	}
    }



