mirror of
https://github.com/librenms/librenms-agent.git
synced 2024-05-09 09:54:52 +00:00
346 lines
8.4 KiB
Perl
Executable File
346 lines
8.4 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
|
|
#Copyright (c) 2019, 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.1\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;
|
|
my %proto_lookup;
|
|
|
|
#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;
|
|
}
|
|
|
|
$proto_lookup{ $port } = $protos_array [$protos_array_int ];
|
|
|
|
$protos_array_int++;
|
|
}
|
|
|
|
my $netstat='netstat -n';
|
|
|
|
my $os=$^O;
|
|
|
|
my $res = parse_netstat(output => join("", `$netstat`), flavor=>$os, udp=>0, unix=>0);
|
|
|
|
#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 ( defined( $conn->{proto} ) &&
|
|
( $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 (
|
|
( defined($proto_lookup{ $conn->{'local_port'} }) ) ||
|
|
( defined($proto_lookup{ $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;
|