Merge branch 'pandora_enterprise#2333-intracom-snmp-trap-buffer-subsystem' into 'develop'

Add support for trap data coming from XML files.

See merge request artica/pandorafms!1667
This commit is contained in:
vgilc 2018-08-08 11:40:48 +02:00
commit 323599f50e
4 changed files with 374 additions and 74 deletions

View File

@ -0,0 +1,250 @@
#!/usr/bin/perl
###############################################################################
#
# Copyright (c) 2018 Artica Soluciones Tecnologicas S.L.
#
# grep_log Perl script to search log files for a matching pattern. The last
# searched position is saved in an index file so that consecutive
# runs do not return the same results. The log file inode number is
# also saved to detect log rotation.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
###############################################################################
use strict;
use File::Basename;
use IO::Compress::Zip qw(zip $ZipError);
use MIME::Base64;
# Be verbose
my $Verbose = 0;
# Index file storage directory, with a trailing '/'
my $Idx_dir=($^O =~ /win/i)?'.\\':'/tmp/';
# Log file
my $Log_file = '';
# Index file
my $Idx_file = '';
# Log file position index
my $Idx_pos = 0;
# Log file inode number
my $Idx_ino = '';
# Log file size
my $Idx_size = 0;
###############################################################################
# SUB error_msg
# Print an error message and exit.
###############################################################################
sub error_msg ($) {
my $err_msg = $_[0];
if (! -z $err_msg) {
print(STDERR "[error] $err_msg.\n");
}
exit 1;
}
###############################################################################
# SUB print_help
# Print a help message.
###############################################################################
sub print_help () {
print "Usage: $0 <snmptrapd log file>\n";
}
###############################################################################
# SUB log_msg
# Print a log message.
###############################################################################
sub log_msg ($) {
my $log_msg = $_[0];
if (! -z $log_msg && $Verbose == 1) {
print(STDOUT "[log] $log_msg.\n");
}
}
###############################################################################
# SUB load_idx
# Load index file.
###############################################################################
sub load_idx () {
my $line;
my $current_ino;
my $current_size;
log_msg("Loading index file $Idx_file");
open(IDXFILE, $Idx_file) || error_msg("Error opening file $Idx_file: " .
$!);
# Read position and date
$line = <IDXFILE>;
($Idx_pos, $Idx_ino, $Idx_size) = split(' ', $line);
close(IDXFILE);
# Reset the file index if the file has changed
$current_ino = (stat($Log_file))[1];
$current_size = -s "$Log_file";
if ($current_ino != $Idx_ino || $current_size < $Idx_size) {
log_msg("File changed, resetting index");
$Idx_pos = 0;
$Idx_ino = $current_ino;
}
$Idx_size = $current_size;
return;
}
###############################################################################
# SUB save_idx
# Save index file.
###############################################################################
sub save_idx () {
log_msg("Saving index file $Idx_file");
open(IDXFILE, "> $Idx_file") || error_msg("Error opening file $Idx_file: "
. $!);
print (IDXFILE $Idx_pos . " " . $Idx_ino . " " . $Idx_size);
close(IDXFILE);
return;
}
###############################################################################
# SUB create_idx
# Create index file.
###############################################################################
sub create_idx () {
my $first_line;
log_msg("Creating index file $Idx_file");
open(LOGFILE, $Log_file) || error_msg("Error opening file $Log_file: " .
$!);
# Go to EOF and save the position
seek(LOGFILE, 0, 2);
$Idx_pos = tell(LOGFILE);
close(LOGFILE);
# Save the file inode number
$Idx_ino = (stat($Log_file))[1];
# Save the index file
save_idx();
return;
}
###############################################################################
# SUB parse_log
# Parse log file starting from position $Idx_pos.
###############################################################################
sub parse_log () {
my $line;
log_msg("Parsing log file $Log_file");
# Open log file for reading
open(LOGFILE, $Log_file) || error_msg("Error opening file $Log_file: " .
$!);
# Go to starting position.
seek(LOGFILE, $Idx_pos, 0);
# Parse log file
my $data;
# Matched line id
my $matched_line = 0;
$/ = undef;
$data = <LOGFILE>;
$Idx_pos = tell(LOGFILE);
close(LOGFILE);
# Save the index file
save_idx();
return \$data;
}
###############################################################################
# SUB parse_log
# Print log data to STDOUT.
###############################################################################
sub print_log ($) {
my $data = shift;
my $zdata;
return unless defined($data) and $$data ne '';
if (!zip($data => \$zdata)) {
error_msg("Compression error: $ZipError");
return;
}
print STDOUT "<trap_data><![CDATA[" . encode_base64($zdata, '') . "]]></trap_data>\n";
}
###############################################################################
###############################################################################
## Main
###############################################################################
###############################################################################
# Check command line parameters
if ($#ARGV < 0) {
print_help();
exit 1;
}
$Log_file = $ARGV[0];
# Create index file storage directory
if ( ! -d $Idx_dir) {
mkdir($Idx_dir) || error_msg("Error creating directory $Idx_dir: "
. $!);
}
# Check that log file exists
if (! -e $Log_file) {
error_msg("File $Log_file does not exist");
}
# Create index file if it does not exist
$Idx_file=$Idx_dir . basename($Log_file) . ".idx";
if (! -e $Idx_file) {
create_idx();
exit 0;
}
# Load index file
load_idx();
# Parse log file
my $data = parse_log();
# Print output to STDOUT
print_log ($data);
exit 0;

View File

@ -485,8 +485,10 @@ sub pandora_load_config {
$pa_config->{"provisioningserver"} = 1; # 7.0 720
$pa_config->{"provisioningserver_threads"} = 1; # 7.0 720
$pa_config->{"provisioning_cache_interval"} = 300; # 7.0 720
$pa_config->{"autoconfigure_agents"} = 1; # 7.0 725
$pa_config->{'snmp_extlog'} = ""; # 7.0 726
# Check for UID0
if ($pa_config->{"quiet"} != 0){
@ -1118,6 +1120,9 @@ sub pandora_load_config {
elsif ($parametro =~ m/^autoconfigure_agents\s+([0-1])/i){
$pa_config->{'autoconfigure_agents'}= clean_blank($1);
}
elsif ($parametro =~ m/^snmp_extlog\s(.*)/i) {
$pa_config->{'snmp_extlog'} = clean_blank($1);
}
} # end of loop for parameter #
# Set to RDBMS' standard port

View File

@ -28,6 +28,7 @@ use Time::Local;
use XML::Parser::Expat;
use XML::Simple;
use POSIX qw(setsid strftime);
use IO::Uncompress::Unzip;
# For Reverse Geocoding
use LWP::Simple;
@ -592,6 +593,9 @@ sub process_xml_data ($$$$$) {
# Process log modules
enterprise_hook('process_log_data', [$pa_config, $data, $server_id, $agent_name,
$interval, $timestamp, $dbh]);
# Process snmptrapd modules
enterprise_hook('process_snmptrap_data', [$pa_config, $data, $server_id, $dbh]);
}
##########################################################################

View File

@ -48,8 +48,9 @@ my $TaskSem :shared;
# Trap statistics by agent
my %AGENTS = ();
# Index file management
my ($IDX_FILE, $LAST_LINE, $LAST_SIZE) = ();
# Index and buffer management for trap log files
my $SNMPTRAPD = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
my $DATASERVER = { 'log_file' => '', 'fd' => undef, 'idx_file' => '', 'last_line' => 0, 'last_size' => 0, 'read_ahead_line' => '', 'read_ahead_pos' => 0 };
########################################################################################
# SNMP Server class constructor.
@ -65,33 +66,27 @@ sub new ($$$) {
}
# Wait for the SNMP log file to be available
my $log_file = $config->{'snmp_logfile'};
sleep ($config->{'server_threshold'}) if (! -e $log_file);
if (!open (SNMPLOGFILE, $log_file)) {
logger ($config, ' [E] Could not open the SNMP log file ' . $config->{'snmp_logfile'} . ".", 1);
print_message ($config, ' [E] Could not open the SNMP log file ' . $config->{'snmp_logfile'} . ".", 1);
$SNMPTRAPD->{'log_file'} = $config->{'snmp_logfile'};
sleep ($config->{'server_threshold'}) if (! -e $SNMPTRAPD->{'log_file'});
if (!open ($SNMPTRAPD->{'fd'}, $SNMPTRAPD->{'log_file'})) {
logger ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
print_message ($config, ' [E] Could not open the SNMP log file ' . $SNMPTRAPD->{'log_file'} . ".", 1);
return 1;
}
init_log_file($config, $SNMPTRAPD);
# Process index file, if available
($IDX_FILE, $LAST_LINE, $LAST_SIZE) = ($log_file . '.index', 0, 0);
if (-e $IDX_FILE) {
open (INDEXFILE, $IDX_FILE) or return;
my $idx_data = <INDEXFILE>;
close INDEXFILE;
($LAST_LINE, $LAST_SIZE) = split(/\s+/, $idx_data);
}
my $log_size = (stat ($log_file))[7];
# New SNMP log file found
if ($log_size < $LAST_SIZE) {
unlink ($IDX_FILE);
($LAST_LINE, $LAST_SIZE) = (0, 0);
# Create the Data Server SNMP log file if it does not exist.
if (defined($config->{'snmp_extlog'}) && $config->{'snmp_extlog'} ne '') {
$DATASERVER->{'log_file'} = $config->{'snmp_extlog'};
open(TMPFD, '>', $DATASERVER->{'log_file'}) && close(TMPFD) if (! -e $DATASERVER->{'log_file'});
if (!open ($DATASERVER->{'fd'}, $DATASERVER->{'log_file'})) {
logger ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
print_message ($config, ' [E] Could not open the Data Server SNMP log file ' . $DATASERVER->{'log_file'} . ".", 1);
return 1;
}
init_log_file($config, $DATASERVER);
}
# Skip already processed lines
read_snmplogfile() for (1..$LAST_LINE);
# Initialize semaphores and queues
@TaskQueue = ();
%PendingTasks = ();
@ -140,41 +135,45 @@ sub data_producer ($) {
%AGENTS = ();
}
while (my $line_with_pos = read_snmplogfile()) {
my $line;
$LAST_LINE++;
($LAST_SIZE, $line) = @$line_with_pos;
chomp ($line);
# Update index file
open INDEXFILE, '>' . $IDX_FILE;
print INDEXFILE $LAST_LINE . ' ' . $LAST_SIZE;
close INDEXFILE;
set_file_permissions($pa_config, $IDX_FILE, "0666");
# Skip lines other than SNMP Trap logs
next unless ($line =~ m/^SNMPv[12]\[\*\*\]/);
# Storm protection.
my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $line, 5);
next unless defined ($source);
if (! defined ($AGENTS{$source})) {
$AGENTS{$source}{'count'} = 1;
$AGENTS{$source}{'event'} = 0;
} else {
$AGENTS{$source}{'count'} += 1;
}
if ($pa_config->{'snmp_storm_protection'} > 0 && $AGENTS{$source}{'count'} > $pa_config->{'snmp_storm_protection'}) {
if ($AGENTS{$source}{'event'} == 0) {
pandora_event ($pa_config, "Too many traps coming from $source. Silenced for " . int ($pa_config->{"snmp_storm_timeout"} / 60) . " minutes.", 0, 0, 4, 0, 0, 'system', 0, $dbh);
for my $fs (($SNMPTRAPD, $DATASERVER)) {
next unless defined($fs->{'fd'});
reset_if_truncated($pa_config, $fs);
while (my $line_with_pos = read_snmplogfile($fs)) {
my $line;
$fs->{'last_line'}++;
($fs->{'last_size'}, $line) = @$line_with_pos;
chomp ($line);
# Update index file
open(my $idxfd, '>' . $fs->{'idx_file'});
print $idxfd $fs->{'last_line'} . ' ' . $fs->{'last_size'};
close $idxfd;
set_file_permissions($pa_config, $fs->{'idx_file'}, "0666");
# Skip lines other than SNMP Trap logs
next unless ($line =~ m/^SNMPv[12]\[\*\*\]/);
# Storm protection.
my ($ver, $date, $time, $source, $null) = split(/\[\*\*\]/, $line, 5);
next unless defined ($source);
if (! defined ($AGENTS{$source})) {
$AGENTS{$source}{'count'} = 1;
$AGENTS{$source}{'event'} = 0;
} else {
$AGENTS{$source}{'count'} += 1;
}
$AGENTS{$source}{'event'} = 1;
next;
if ($pa_config->{'snmp_storm_protection'} > 0 && $AGENTS{$source}{'count'} > $pa_config->{'snmp_storm_protection'}) {
if ($AGENTS{$source}{'event'} == 0) {
pandora_event ($pa_config, "Too many traps coming from $source. Silenced for " . int ($pa_config->{"snmp_storm_timeout"} / 60) . " minutes.", 0, 0, 4, 0, 0, 'system', 0, $dbh);
}
$AGENTS{$source}{'event'} = 1;
next;
}
push (@tasks, $line);
}
push (@tasks, $line);
}
return @tasks;
@ -437,23 +436,21 @@ sub start_snmptrapd ($) {
# Read SNMP Log file with buffering (to handle multi-line Traps).
# Return reference of array (file-pos, line-data) if successful, undef othersise.
###############################################################################
my $read_ahead_line; # buffer to save fetched ahead line
my $read_ahead_pos;
sub read_snmplogfile()
{
sub read_snmplogfile($) {
my ($fs) = @_;
my $line;
my $pos;
if(defined($read_ahead_line)) {
if(defined($fs->{'read_ahead_line'})) {
# Restore saved line
$line = $read_ahead_line;
$pos = $read_ahead_pos;
$line = $fs->{'read_ahead_line'};
$pos = $fs->{'read_ahead_pos'};
}
else {
# No saved line
$line = <SNMPLOGFILE>;
$pos = tell(SNMPLOGFILE);
my $fd = $fs->{'fd'};
$line = <$fd>;
$pos = tell($fs->{'fd'});
}
return undef if (! defined($line));
@ -462,20 +459,21 @@ sub read_snmplogfile()
# More lines ?
while(1) {
while($read_ahead_line = <SNMPLOGFILE>) {
my $fd = $fs->{'fd'};
while($fs->{'read_ahead_line'} = <$fd>) {
# Get current file position
$read_ahead_pos = tell(SNMPLOGFILE);
$fs->{'read_ahead_pos'} = tell($fs->{'fd'});
# Get out of the loop if you find another Trap
last if($read_ahead_line =~ /^SNMP/ );
last if($fs->{'read_ahead_line'} =~ /^SNMP/ );
# $read_ahead_line looks continued line...
# $fs->{'read_ahead_line'} looks continued line...
# Append to the line and correct the position
chomp($line);
$line .= "$read_ahead_line";
$pos = $read_ahead_pos;
$line .= "$fs->{'read_ahead_line'}";
$pos = $fs->{'read_ahead_pos'};
}
# if $line looks incomplete, try to get continued line
@ -490,6 +488,49 @@ sub read_snmplogfile()
return [$pos, $line];
}
###############################################################################
# Initialize the fs structure for a trap log file.
###############################################################################
sub init_log_file($$$) {
my ($config, $fs) = @_;
# Process index file, if available
($fs->{'idx_file'}, $fs->{'last_line'}, $fs->{'last_size'}) = ($fs->{'log_file'} . '.index', 0, 0);
if (-e $fs->{'idx_file'}) {
open (my $idxfd, $fs->{'idx_file'}) or return;
my $idx_data = <$idxfd>;
close $idxfd;
($fs->{'last_line'}, $fs->{'last_size'}) = split(/\s+/, $idx_data);
}
my $log_size = (stat ($fs->{'log_file'}))[7];
# New SNMP log file found
if ($log_size < $fs->{'last_size'}) {
unlink ($fs->{'idx_file'});
($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
}
# Skip already processed lines
read_snmplogfile($fs) for (1..$fs->{'last_line'});
}
###############################################################################
# Reset the index if the file has been truncated.
###############################################################################
sub reset_if_truncated($$) {
my ($pa_config, $fs) = @_;
my $log_size = (stat ($fs->{'log_file'}))[7];
# New SNMP log file found
if ($log_size < $fs->{'last_size'}) {
logger ($pa_config, 'File ' . $fs->{'log_file'} . ' was truncated.', 10);
unlink ($fs->{'idx_file'});
($fs->{'last_line'}, $fs->{'last_size'}) = (0, 0);
seek($fs->{'fd'}, 0, 0);
}
}
###############################################################################
# Clean-up when the server is destroyed.
###############################################################################