mirror of
https://github.com/librenms/librenms-agent.git
synced 2024-05-09 09:54:52 +00:00
450 lines
13 KiB
Perl
Executable File
450 lines
13 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
|
|
#Copyright (c) 2023, 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.
|
|
|
|
=for comment
|
|
|
|
Add this to snmpd.conf as below and restart snmpd.
|
|
|
|
extend privoxy /etc/snmp/extends/privoxy
|
|
|
|
Supported command line options are as below.
|
|
|
|
-f Logfile.
|
|
Default: /var/log/privoxy/logfile
|
|
-c gzip+base64 compression
|
|
-p Pretty print.
|
|
|
|
The last is only really relevant to the usage with SNMP.
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Getopt::Std;
|
|
use File::ReadBackwards;
|
|
use JSON;
|
|
use Time::Piece;
|
|
use IPC::Run3;
|
|
|
|
# get the current time
|
|
my $t = localtime;
|
|
my $till = $t->epoch;
|
|
$till = $till - 300;
|
|
|
|
# needed as strptime will always assume UTC, resulting in localtime and it being off
|
|
if ( $t->tzoffset =~ /^-/ ) {
|
|
my $offset = $t->tzoffset;
|
|
$offset =~ s/^\-//;
|
|
$till = $till - $offset;
|
|
}
|
|
else {
|
|
my $offset = $t->tzoffset;
|
|
$offset =~ s/^\+//;
|
|
$till = $till + $offset;
|
|
}
|
|
|
|
my $logfile = '/var/log/privoxy/logfile';
|
|
my $compress;
|
|
|
|
#gets the options
|
|
my %opts;
|
|
getopts( 'f:cp', \%opts );
|
|
if ( defined( $opts{f} ) ) {
|
|
$logfile = $opts{f};
|
|
}
|
|
if ( defined( $opts{c} ) ) {
|
|
$compress = 1;
|
|
}
|
|
|
|
my $json = JSON->new->allow_nonref->canonical(1);
|
|
if ( $opts{p} ) {
|
|
$json->pretty();
|
|
}
|
|
|
|
# initiate what will be returned
|
|
my $to_return = {
|
|
error => 0,
|
|
errorString => '',
|
|
version => 1,
|
|
data => {
|
|
client_requests => 0,
|
|
client_cons => 0,
|
|
out_requests => 0,
|
|
crunches => 0,
|
|
blocks => 0,
|
|
block_percent => 0,
|
|
fast_redirs => 0,
|
|
con_timeouts => 0,
|
|
con_failures => 0,
|
|
ska_offers => 0,
|
|
nog_conns => 0,
|
|
reused_server_cons => 0,
|
|
empty_resps => 0,
|
|
empty_resps_new => 0,
|
|
empty_resps_reuse => 0,
|
|
imp_accounted => 0,
|
|
req_get => 0,
|
|
req_head => 0,
|
|
req_post => 0,
|
|
req_put => 0,
|
|
req_delete => 0,
|
|
req_connect => 0,
|
|
req_options => 0,
|
|
req_trace => 0,
|
|
req_patch => 0,
|
|
ver_1_0 => 0,
|
|
ver_1_1 => 0,
|
|
ver_2 => 0,
|
|
ver_3 => 0,
|
|
max_reqs => 0,
|
|
bytes_to_client => 0,
|
|
resp_1xx => 0,
|
|
resp_2xx => 0,
|
|
resp_200 => 0,
|
|
resp_2xx_other => 0,
|
|
resp_3xx => 0,
|
|
resp_301 => 0,
|
|
resp_302 => 0,
|
|
resp_303 => 0,
|
|
resp_3xx_other => 0,
|
|
resp_4xx => 0,
|
|
resp_403 => 0,
|
|
resp_404 => 0,
|
|
resp_451 => 0,
|
|
resp_4xx_other => 0,
|
|
resp_5xx => 0,
|
|
resp_500 => 0,
|
|
resp_502 => 0,
|
|
resp_503 => 0,
|
|
resp_504 => 0,
|
|
resp_5xx_other => 0,
|
|
unique_bdomains => 0,
|
|
unique_bdomains_np => 0,
|
|
unique_domains => 0,
|
|
unique_domains_np => 0,
|
|
ubd_np_per => 0,
|
|
ubd_per => 0,
|
|
},
|
|
};
|
|
|
|
my $bw;
|
|
eval { $bw = File::ReadBackwards->new($logfile)
|
|
or die "can't read " . $logfile . "... $!"; };
|
|
if ($@) {
|
|
$to_return->{error} = 1;
|
|
$to_return->{errorString} = $@;
|
|
$to_return->{data} = {};
|
|
print $json->encode($to_return);
|
|
if ( !$opts{p} ) {
|
|
print "\n";
|
|
}
|
|
exit 0;
|
|
}
|
|
|
|
my $read_file = 1;
|
|
|
|
# holds a list of blocked domains found
|
|
my $unique_bdomains = {};
|
|
my $unique_bdomains_np = {};
|
|
|
|
# holds a list of domains found
|
|
my $unique_domains = {};
|
|
my $unique_domains_np = {};
|
|
|
|
# read all log lines in reverse
|
|
my $lines = '';
|
|
my $log_line = '';
|
|
while ( defined( $log_line = $bw->readline )
|
|
&& $read_file )
|
|
{
|
|
my $log_t;
|
|
|
|
# get the timestamp on non-CLF style log lines
|
|
if ( $log_line =~ /^(?<timestamp>\d\d\d\d\-\d\d\-\d\d\ \d\d\:\d\d\:\d\d)/ ) {
|
|
$log_t = Time::Piece->strptime( $+{timestamp}, '%Y-%m-%d %H:%M:%S' );
|
|
}
|
|
|
|
# get the timestamp on CLF style log lines
|
|
elsif ( $log_line =~ /\[(?<timestamp>\d\d\/[A-Za-z]+\/\d\d\d\d\:\d\d\:\d\d\:\d\d)\]/ ) {
|
|
$log_t = Time::Piece->strptime( $+{timestamp}, '%d/%b/%Y:%H:%M:%S' );
|
|
}
|
|
|
|
if ( defined($log_t) ) {
|
|
|
|
# if we have gone beyond where we want to go to, then stop...
|
|
# otherwise add it
|
|
if ( $log_t->epoch < $till ) {
|
|
$read_file = 0;
|
|
}
|
|
else {
|
|
$lines = $log_line . $lines;
|
|
|
|
if ( $log_line =~ /^\d\d\d\d\-\d\d\-\d\d\ \d\d\:\d\d\:\d\d.*Crunch\:\ Blocked\:\ / ) {
|
|
my $log_line_tmp = $log_line;
|
|
$log_line_tmp =~ s/.*Crunch\:\ Blocked\:\ //;
|
|
$unique_bdomains->{$log_line_tmp} = 1;
|
|
$log_line =~ s/\:\d+$//;
|
|
$unique_bdomains_np->{$log_line_tmp} = 1;
|
|
}
|
|
if ( $log_line =~ /^\d\d\d\d\-\d\d\-\d\d\ \d\d\:\d\d\:\d\d.*Connect\:\ to\ / ) {
|
|
my $log_line_tmp = $log_line;
|
|
$log_line_tmp =~ s/.*Connect\:\ to\ //;
|
|
|
|
# if it has a space, it is a line displaying the stating of the connect
|
|
if ( $log_line_tmp !~ /\ / ) {
|
|
$unique_domains->{$log_line_tmp} = 1;
|
|
$log_line =~ s/\:\d+$//;
|
|
$unique_domains_np->{$log_line_tmp} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# if we don't have log_t, just add the line and lot the log parser figure out what it is
|
|
else {
|
|
$lines = $log_line . $lines;
|
|
}
|
|
}
|
|
|
|
my $stdout;
|
|
my $stderr;
|
|
my @cmd = ( 'privoxy-log-parser.pl', '--statistics', '--show-complete-request-distribution' );
|
|
run3( \@cmd, \$lines, \$stdout, \$stderr );
|
|
|
|
my @stdout_split = split( /\n/, $stdout );
|
|
|
|
my $multiline_mode;
|
|
foreach my $line (@stdout_split) {
|
|
|
|
# needed as some lines have white space on the end that makes parsing annoying
|
|
$line =~ s/\ +$//;
|
|
|
|
# start processing lines based on the start of the line
|
|
if ( $line =~ /^Client\ requests\ total\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$to_return->{data}{client_requests} = $line;
|
|
}
|
|
elsif ( $line =~ /^Crunches\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{crunches} = $line;
|
|
}
|
|
elsif ( $line =~ /^Blocks:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{blocks} = $line;
|
|
}
|
|
elsif ( $line =~ /^Fast\ redirections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{fast_redirs} = $line;
|
|
}
|
|
elsif ( $line =~ /^Connection\ timeouts\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{con_timeouts} = $line;
|
|
}
|
|
elsif ( $line =~ /^Connection\ failures\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{con_failures} = $line;
|
|
}
|
|
elsif ( $line =~ /^Outgoing\ requests\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{out_requests} = $line;
|
|
}
|
|
elsif ( $line =~ /^Server keep-alive offers\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{ska_offers} = $line;
|
|
}
|
|
elsif ( $line =~ /^New\ outgoing\ connections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ \-//;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{nog_conns} = $line;
|
|
}
|
|
elsif ( $line =~ /^Reused\ server\ connections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*connections\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{reused_server_cons} = $line;
|
|
}
|
|
elsif ( $line =~ /^Empty\ responses\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{empty_resps} = $line;
|
|
}
|
|
elsif ( $line =~ /^Empty\ responses\ on\ new\ connections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{empty_resps_new} = $line;
|
|
}
|
|
elsif ( $line =~ /^Empty\ responses\ on\ reused\ connections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{empty_resps_reuse} = $line;
|
|
}
|
|
elsif ( $line =~ /^Client\ connections\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{client_cons} = $line;
|
|
}
|
|
elsif ( $line =~ /^Bytes\ of\ content\ transferred\ to\ the\ client\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ //;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{bytes_to_client} = $line;
|
|
}
|
|
elsif ( $line =~ /^Improperly\ accounted\ requests\:/ ) {
|
|
$multiline_mode = '';
|
|
$line =~ s/.*\:\ \~//;
|
|
$line =~ s/\ .*$//;
|
|
$to_return->{data}{imp_accounted} = $line;
|
|
}
|
|
|
|
# match various multi line modes starts
|
|
elsif ( $line =~ /^Client\ requests\ per\ connection\ distribution\:/ ) {
|
|
$multiline_mode = 'requests per con';
|
|
}
|
|
elsif ( $line =~ /^Method\ distribution\:/ ) {
|
|
$multiline_mode = 'method';
|
|
}
|
|
elsif ( $line =~ /^Client HTTP versions:/ ) {
|
|
$multiline_mode = 'version';
|
|
}
|
|
elsif ( $line
|
|
=~ /^HTTP\ status\ codes\ according\ to\ \'debug\ 512\' \(status\ codes\ sent\ by\ the\ server\ may\ differ\)\:/
|
|
)
|
|
{
|
|
$multiline_mode = 'response';
|
|
}
|
|
|
|
# if it starts with a space, it is a multiline mode item
|
|
elsif ( $line =~ /^\ / ) {
|
|
if ( $multiline_mode eq 'requsts per con' ) {
|
|
$line =~ s/.*\:\ //;
|
|
if ( $line > $to_return->{data}{max_reqs} ) {
|
|
$to_return->{data}{max_reqs} = $line;
|
|
}
|
|
}
|
|
elsif ( $multiline_mode eq 'method' ) {
|
|
$line =~ s/^ +//;
|
|
my ( $count, $method ) = split( /\ \:\ /, $line );
|
|
$method = lc($method);
|
|
if ( defined( $to_return->{data}{ 'req_' . $method } ) ) {
|
|
$to_return->{data}{ 'req_' . $method } = $count;
|
|
}
|
|
}
|
|
elsif ( $multiline_mode eq 'version' ) {
|
|
$line =~ s/^ +//;
|
|
my ( $count, $version ) = split( /\ \:\ /, $line );
|
|
$version = lc($version);
|
|
$version =~ s/http\//ver_/;
|
|
$version =~ s/\./_/g;
|
|
if ( defined( $to_return->{data}{$version} ) ) {
|
|
$to_return->{data}{$version} = $count;
|
|
}
|
|
}
|
|
elsif ( $multiline_mode eq 'response' ) {
|
|
$line =~ s/^ +//;
|
|
my ( $count, $response ) = split( /\ \:\ /, $line );
|
|
if ( defined( $to_return->{data}{ 'resp_' . $response } ) ) {
|
|
|
|
$to_return->{data}{ 'resp_' . $response } = $count;
|
|
}
|
|
elsif ( $response =~ /^2\d\d/ ) {
|
|
$to_return->{data}{resp_2xx_other} = $to_return->{data}{resp_2xx_other} + $count;
|
|
}
|
|
elsif ( $response =~ /^3\d\d/ ) {
|
|
$to_return->{data}{resp_3xx_other} = $to_return->{data}{resp_3xx_other} + $count;
|
|
}
|
|
elsif ( $response =~ /^4\d\d/ ) {
|
|
$to_return->{data}{resp_4xx_other} = $to_return->{data}{resp_4xx_other} + $count;
|
|
}
|
|
elsif ( $response =~ /^5\d\d/ ) {
|
|
$to_return->{data}{resp_3xx_other} = $to_return->{data}{resp_5xx_other} + $count;
|
|
}
|
|
|
|
if ( $response =~ /^1\d\d/ ) {
|
|
$to_return->{data}{resp_1xx} = $to_return->{data}{resp_1xx} + $count;
|
|
}
|
|
elsif ( $response =~ /^2\d\d/ ) {
|
|
$to_return->{data}{resp_2xx} = $to_return->{data}{resp_2xx} + $count;
|
|
}
|
|
elsif ( $response =~ /^3\d\d/ ) {
|
|
$to_return->{data}{resp_3xx} = $to_return->{data}{resp_3xx} + $count;
|
|
}
|
|
elsif ( $response =~ /^4\d\d/ ) {
|
|
$to_return->{data}{resp_4xx} = $to_return->{data}{resp_4xx} + $count;
|
|
}
|
|
elsif ( $response =~ /^5\d\d/ ) {
|
|
$to_return->{data}{resp_5xx} = $to_return->{data}{resp_5xx} + $count;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$multiline_mode = '';
|
|
}
|
|
}
|
|
|
|
my @keys_tmp = keys( %{$unique_bdomains} );
|
|
$to_return->{data}{unique_bdomains} = @keys_tmp;
|
|
@keys_tmp = keys( %{$unique_bdomains_np} );
|
|
$to_return->{data}{unique_bdomains_np} = @keys_tmp;
|
|
@keys_tmp = keys( %{$unique_domains} );
|
|
$to_return->{data}{unique_domains} = @keys_tmp;
|
|
@keys_tmp = keys( %{$unique_domains_np} );
|
|
$to_return->{data}{unique_domains_np} = @keys_tmp;
|
|
|
|
if ( $to_return->{data}{unique_domains} > 0 && $to_return->{data}{unique_bdomains} > 0 ) {
|
|
$to_return->{data}{ubd_per} = $to_return->{data}{unique_bdomains} / $to_return->{data}{unique_domains};
|
|
$to_return->{data}{ubd_np_per} = $to_return->{data}{unique_bdomains_np} / $to_return->{data}{unique_domains_np};
|
|
}
|
|
|
|
# percentage of requests blocked
|
|
if ( $to_return->{data}{blocks} > 0 && $to_return->{data}{client_requests} > 0 ) {
|
|
$to_return->{data}{block_percent} = $to_return->{data}{blocks} / $to_return->{data}{client_requests};
|
|
}
|
|
|
|
print $json->encode($to_return);
|
|
if ( !$opts{p} ) {
|
|
print "\n";
|
|
}
|
|
exit 0;
|