mirror of
https://github.com/librenms/librenms-agent.git
synced 2024-05-09 09:54:52 +00:00
add portactivity SNMP extend (#159)
* add portactivity SNMP extend in its initial form * update for the current json_app_get * add version to the returned JSON * add basic POD documentation
This commit is contained in:
352
snmp/portactivity
Executable file
352
snmp/portactivity
Executable file
@ -0,0 +1,352 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
#Copyright (c) 2018, Zane C. Bowers-Hadley
|
||||
#All rights reserved.
|
||||
#
|
||||
#Redistribution and use in source and binary forms, with or without modification,
|
||||
#are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
#IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
#INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
#LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
#THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# FreeBSD /usr/include/netinet/tcp_fsm.h
|
||||
# Linux netstat(8)
|
||||
# FreeBSD --> Linux
|
||||
# LISTEN --> LISTEN
|
||||
# CLOSED --> CLOSED
|
||||
# SYN_SENT --> SYN_SENT
|
||||
# SYN_RECEIVED -->SYN_RECV
|
||||
# ESTABLISHED --> ESTABLISHED
|
||||
# CLOSE_WAIT --> CLOSE_WAIT
|
||||
# FIN_WAIT_1 --> FIN_WAIT1
|
||||
# CLOSING --> CLOSING
|
||||
# LAST_ACK --> LAST_ACK
|
||||
# FIN_WAIT_2 --> FIN_WAIT2
|
||||
# TIME_WAIT --> TIME_WAIT
|
||||
# ((no equivalent)) --> UNKNOWN
|
||||
#
|
||||
# UNKNOWN is being regarded as a valid state for all and will be used on OSes that supported it
|
||||
# The names returned by default are those used by FreeBSD.
|
||||
|
||||
=head1 NAME
|
||||
|
||||
portactivity - Generates JSON output based on netstat data for the specificied TCP services.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
portactivity [B<-P>] B<-p> <protocols>
|
||||
|
||||
=head1 USAGE
|
||||
|
||||
This is meant to be used as a SNMP extend for use with json_app_get in LibreNMS.
|
||||
|
||||
Below is a example of its usage with netsnmpd and checking HTTP and SSH.
|
||||
|
||||
extend portactivity /etc/snmp/portactivity -p http,ssh
|
||||
|
||||
=head1 SWITCHES
|
||||
|
||||
=head2 B<-P>
|
||||
|
||||
Prints the JSON in easily human readable format.
|
||||
|
||||
=head2 B<-p> <protocols>
|
||||
|
||||
This is a comma seperated list of TCP services to check.
|
||||
|
||||
=head1 SERVICES
|
||||
|
||||
NSS is used to resolve the TCP service protocol names. All the ones listed with -p
|
||||
must be findable that way or it will error.
|
||||
|
||||
If you are running something on a non-standard port and want to check for it, you either
|
||||
have to use the name of the port it is on, add it to the database, or change it in the
|
||||
database(if it is already there under a undesired name).
|
||||
|
||||
In general the file in question on most systems is going to be '/etc/services' and you
|
||||
will need to run services_mkdb(8) after updating it. But for specifics you will want to
|
||||
consult services(5).
|
||||
|
||||
=cut
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use JSON;
|
||||
use Getopt::Std;
|
||||
use Parse::Netstat qw(parse_netstat);
|
||||
|
||||
$Getopt::Std::STANDARD_HELP_VERSION = 1;
|
||||
sub main::VERSION_MESSAGE {
|
||||
print "Port Activity SNMP stats extend 0.0.0\n";
|
||||
}
|
||||
|
||||
sub main::HELP_MESSAGE {
|
||||
print "\n".
|
||||
"-p <protos> A comma seperated list of TCP protocols to check for in netstat.\n".
|
||||
"-P Print the output in a human readable manner.\n";
|
||||
}
|
||||
|
||||
#returns aa new hash with all zeroed values for a new protocol
|
||||
sub newProto{
|
||||
|
||||
return {
|
||||
'total_conns'=>0,
|
||||
'total_to'=>0,
|
||||
'total_from'=>0,
|
||||
'total'=>{
|
||||
'LISTEN'=>0,
|
||||
'CLOSED'=>0,
|
||||
'SYN_SENT'=>0,
|
||||
'SYN_RECEIVED'=>0,
|
||||
'ESTABLISHED'=>0,
|
||||
'CLOSE_WAIT'=>0,
|
||||
'FIN_WAIT_1'=>0,
|
||||
'CLOSING'=>0,
|
||||
'LAST_ACK'=>0,
|
||||
'FIN_WAIT_2'=>0,
|
||||
'TIME_WAIT'=>0,
|
||||
'UNKNOWN'=>0,
|
||||
'other'=>0,
|
||||
},
|
||||
'to'=>{
|
||||
'LISTEN'=>0,
|
||||
'CLOSED'=>0,
|
||||
'SYN_SENT'=>0,
|
||||
'SYN_RECEIVED'=>0,
|
||||
'ESTABLISHED'=>0,
|
||||
'CLOSE_WAIT'=>0,
|
||||
'FIN_WAIT_1'=>0,
|
||||
'CLOSING'=>0,
|
||||
'LAST_ACK'=>0,
|
||||
'FIN_WAIT_2'=>0,
|
||||
'TIME_WAIT'=>0,
|
||||
'UNKNOWN'=>0,
|
||||
'other'=>0,
|
||||
},
|
||||
'from'=>{
|
||||
'LISTEN'=>0,
|
||||
'CLOSED'=>0,
|
||||
'SYN_SENT'=>0,
|
||||
'SYN_RECEIVED'=>0,
|
||||
'ESTABLISHED'=>0,
|
||||
'CLOSE_WAIT'=>0,
|
||||
'FIN_WAIT_1'=>0,
|
||||
'CLOSING'=>0,
|
||||
'LAST_ACK'=>0,
|
||||
'FIN_WAIT_2'=>0,
|
||||
'TIME_WAIT'=>0,
|
||||
'UNKNOWN'=>0,
|
||||
'other'=>0,
|
||||
},
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
#returns the json output
|
||||
sub return_json{
|
||||
my %to_return;
|
||||
if(defined($_[0])){
|
||||
%to_return= %{$_[0]};
|
||||
}
|
||||
my $pretty=$_[1];
|
||||
|
||||
if (!defined( $to_return{data} ) ){
|
||||
$to_return{data}={};
|
||||
}
|
||||
|
||||
my $j=JSON->new;
|
||||
|
||||
if ( $pretty ){
|
||||
$j->pretty(1);
|
||||
}
|
||||
|
||||
print $j->encode( \%to_return );
|
||||
|
||||
if ( ! $pretty ){
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
|
||||
my %valid_states=(
|
||||
'LISTEN'=>1,
|
||||
'CLOSED'=>1,
|
||||
'SYN_SENT'=>1,
|
||||
'SYN_RECEIVED'=>1,
|
||||
'ESTABLISHED'=>1,
|
||||
'CLOSE_WAIT'=>1,
|
||||
'FIN_WAIT_1'=>1,
|
||||
'CLOSING'=>1,
|
||||
'LAST_ACK'=>1,
|
||||
'FIN_WAIT_2'=>1,
|
||||
'TIME_WAIT'=>1,
|
||||
'UNKNOWN'=>1,
|
||||
);
|
||||
|
||||
#gets the options
|
||||
my %opts=();
|
||||
getopts('p:P', \%opts);
|
||||
|
||||
#what will be returned
|
||||
my %to_return;
|
||||
$to_return{error}='0';
|
||||
$to_return{errorString}='';
|
||||
$to_return{version}=1;
|
||||
|
||||
if (! defined( $opts{p} ) ){
|
||||
$to_return{errorString}='No services specificied to check for';
|
||||
$to_return{error}=1;
|
||||
return_json(\%to_return, $opts{P});
|
||||
exit 1;
|
||||
}
|
||||
|
||||
#the list of protocols to check for
|
||||
my @protos_array=split(/\,/, $opts{p});
|
||||
|
||||
#holds the various protocol hashes
|
||||
my %protos;
|
||||
|
||||
#make sure each one specificied is defined and build the hash that will be returned
|
||||
my $protos_array_int=0;
|
||||
while ( defined( $protos_array[$protos_array_int] ) ){
|
||||
$protos{ $protos_array[$protos_array_int] }=newProto;
|
||||
|
||||
#check if it exists
|
||||
my $port=getservbyname( $protos_array[$protos_array_int] , 'tcp' );
|
||||
|
||||
# if it is not defined, then we error
|
||||
if ( !defined( $port ) ){
|
||||
$to_return{errorString}='"'.$protos_array[$protos_array_int].'" is not a known service either add it or double check your spelling';
|
||||
$to_return{error}=4;
|
||||
return_json(\%to_return, $opts{P});
|
||||
exit 4;
|
||||
}
|
||||
|
||||
$protos_array_int++;
|
||||
}
|
||||
|
||||
my $os=$^O;
|
||||
|
||||
my $netstat;
|
||||
|
||||
#make sure this is a supported OS
|
||||
if ( $os eq 'freebsd' ){
|
||||
$netstat='netstat -S -p tcp'
|
||||
}elsif( $os eq 'linux' ){
|
||||
$netstat='netstat -n'
|
||||
}else{
|
||||
$to_return{errorString}=$os.' is not a supported OS as of currently';
|
||||
$to_return{error}=3;
|
||||
return_json(\%to_return, $opts{P});
|
||||
exit 3;
|
||||
}
|
||||
|
||||
my $res = parse_netstat(output => join("", `$netstat`), flavor=>$os);
|
||||
|
||||
#check to make sure that it was able to parse the output
|
||||
if (
|
||||
(!defined( $res->[1] )) ||
|
||||
($res->[1] ne 'OK' )
|
||||
){
|
||||
$to_return{errorString}='Unable to parse netstat output';
|
||||
$to_return{error}=2;
|
||||
return_json(\%to_return, $opts{P});
|
||||
exit 2;
|
||||
}
|
||||
|
||||
#chew through each connection
|
||||
my $active_conns_int=0;
|
||||
while ( defined( $res->[2]{'active_conns'}[$active_conns_int] ) ){
|
||||
my $conn=$res->[2]{active_conns}[$active_conns_int];
|
||||
|
||||
#we only care about TCP currently
|
||||
if ( $conn->{proto} =~ /^[Tt][Cc][Pp]/ ){
|
||||
$protos_array_int=0;
|
||||
my $service;
|
||||
while(
|
||||
( defined( $protos_array[ $protos_array_int ] ) ) &&
|
||||
( !defined( $service ) ) #stop once we find it
|
||||
){
|
||||
#check if this matches either ports
|
||||
if (
|
||||
( $protos_array[ $protos_array_int ] eq $conn->{'local_port'} ) ||
|
||||
( $protos_array[ $protos_array_int ] eq $conn->{'foreign_port'} )
|
||||
){
|
||||
$service=$protos_array[ $protos_array_int ];
|
||||
}
|
||||
|
||||
$protos_array_int++;
|
||||
}
|
||||
|
||||
#only handle it if is a service we are watching for
|
||||
if ( defined( $service ) ){
|
||||
my $processed=0;
|
||||
|
||||
my $state=$conn->{'state'};
|
||||
#translate the state names
|
||||
if ( $os eq 'linux' ){
|
||||
if ( $state eq 'SYN_RECV' ){
|
||||
$state='SYN_RECEIVED';
|
||||
}elsif( $state eq 'FIN_WAIT1' ){
|
||||
$state='FIN_WAIT_1';
|
||||
}elsif( $state eq 'FIN_WAIT2' ){
|
||||
$state='FIN_WAIT_2'
|
||||
}
|
||||
}
|
||||
|
||||
#only count the state towards the total if not listening
|
||||
if ( $state ne 'LISTEN' ){
|
||||
$protos{$service}{'total_conns'}++;
|
||||
}
|
||||
|
||||
#make sure the state is a valid one
|
||||
# if it is not a valid one, set it to other, meaning something unexpected was set for the state that should not be
|
||||
if ( ! defined( $valid_states{$state} ) ){
|
||||
$state='other';
|
||||
}
|
||||
|
||||
#increment the total state
|
||||
$protos{$service}{'total'}{$state}++;
|
||||
|
||||
if (
|
||||
( $conn->{'foreign_port'} eq $service ) &&
|
||||
( $state ne 'LISTEN' )
|
||||
){
|
||||
$protos{$service}{'total_from'}++;
|
||||
$protos{$service}{'from'}{$state}++;
|
||||
$processed=1;
|
||||
}
|
||||
|
||||
if (
|
||||
( $conn->{'local_port'} eq $service ) &&
|
||||
( $state ne 'LISTEN' ) &&
|
||||
( ! $processed )
|
||||
){
|
||||
$protos{$service}{'total_to'}++;
|
||||
$protos{$service}{'to'}{$state}++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$active_conns_int++;
|
||||
}
|
||||
|
||||
#return the finished product
|
||||
$to_return{data}=\%protos;
|
||||
return_json(\%to_return, $opts{P});
|
||||
exit 0;
|
Reference in New Issue
Block a user