pandorafms/pandora_agents/unix/plugins/route_parser

793 lines
21 KiB
Perl

#!/usr/bin/perl
#
# Dynamic route parser
# Combines MTR and Ping features
#
#
use strict;
use warnings;
use POSIX qw(strftime);
use Scalar::Util qw(looks_like_number);
use Socket;
my $HELP=<<EO_HELP;
Pandora FMS plugin for route parse
Usage: $0 -t target [options]
OPTIONS
-c n number of tests (n)
--no-ping 1 disable ping mode
--no-mtr 1 disable mtr mode
-s 1 symmetric routing (1) *default
asymmetric routing (0)
*Warning* in ping mode, the maximum number of steps detected is 9
EO_HELP
################################################################################
# Imported methods
################################################################################
################################################################################
# Mix hashses
################################################################################
sub merge_hashes {
my $_h1 = shift;
my $_h2 = shift;
my %ret = (%{$_h1}, %{$_h2});
return \%ret;
}
################################################################################
# Check if a value is in an array
################################################################################
sub in_array($$){
my ($array, $value) = @_;
if (empty($value)) {
return 0;
}
my %params = map { $_ => 1 } @{$array};
if (exists($params{$value})) {
return 1;
}
return 0;
}
################################################################################
# Check if a given variable contents a number
################################################################################
sub to_number($) {
my $n = shift;
if(empty($n)) {
return undef;
}
if ($n =~ /[\d+,]*\d+\.\d+/) {
# American notation
$n =~ s/,//g;
}
elsif ($n =~ /[\d+\.]*\d+,\d+/) {
# Spanish notation
$n =~ s/\.//g;
$n =~ s/,/./g;
}
if(looks_like_number($n)) {
return $n;
}
return undef;
}
################################################################################
# Erase blank spaces before and after the string
################################################################################
sub trim($){
my $string = shift;
if (empty ($string)){
return "";
}
$string =~ s/\r//g;
chomp ($string);
$string =~ s/^\s+//g;
$string =~ s/\s+$//g;
return $string;
}
################################################################################
# Empty
################################################################################
sub empty($){
my $str = shift;
if (! (defined ($str)) ){
return 1;
}
if(looks_like_number($str)){
return 0;
}
if (ref ($str) eq "ARRAY") {
return (($#{$str}<0)?1:0);
}
if (ref ($str) eq "HASH") {
my @tmp = keys %{$str};
return (($#tmp<0)?1:0);
}
if ($str =~ /^\ *[\n\r]{0,2}\ *$/) {
return 1;
}
return 0;
}
################################################################################
# is Enabled
################################################################################
sub is_enabled($){
my $value = shift;
if ((defined ($value)) && ($value > 0)){
# return true
return 1;
}
#return false
return 0;
}
################################################################################
# print_module
################################################################################
sub print_module ($$;$){
my $config = shift;
my $data = shift;
my $not_print_flag = shift;
if ((ref($data) ne "HASH") || (!defined $data->{name})) {
return undef;
}
my $xml_module = "";
# If not a string type, remove all blank spaces!
if ($data->{type} !~ m/string/){
$data->{value} = trim($data->{value});
}
$data->{tags} = $data->{tags}?$data->{tags}:($config->{MODULE_TAG_LIST}?$config->{MODULE_TAG_LIST}:undef);
$data->{interval} = $data->{interval}?$data->{interval}:($config->{MODULE_INTERVAL}?$config->{MODULE_INTERVAL}:undef);
$data->{module_group} = $data->{module_group}?$data->{module_group}:($config->{MODULE_GROUP}?$config->{MODULE_GROUP}:undef);
# Global instructions (if defined)
$data->{unknown_instructions} = $config->{unknown_instructions} unless (defined($data->{unknown_instructions}) || (!defined($config->{unknown_instructions})));
$data->{warning_instructions} = $config->{warning_instructions} unless (defined($data->{warning_instructions}) || (!defined($config->{warning_instructions})));
$data->{critical_instructions} = $config->{critical_instructions} unless (defined($data->{critical_instructions}) || (!defined($config->{critical_instructions})));
$xml_module .= "<module>\n";
$xml_module .= "\t<name><![CDATA[" . $data->{name} . "]]></name>\n";
$xml_module .= "\t<type>" . $data->{type} . "</type>\n";
if (ref ($data->{value}) eq "ARRAY") {
$xml_module .= "\t<datalist>\n";
foreach (@{$data->{value}}) {
$xml_module .= "\t<data><![CDATA[" . $data->{value} . "]]></data>\n";
}
$xml_module .= "\t</datalist>\n";
}
else {
$xml_module .= "\t<data><![CDATA[" . $data->{value} . "]]></data>\n";
}
if ( !(empty($data->{desc}))) {
$xml_module .= "\t<description><![CDATA[" . $data->{desc} . "]]></description>\n";
}
if ( !(empty ($data->{unit})) ) {
$xml_module .= "\t<unit><![CDATA[" . $data->{unit} . "]]></unit>\n";
}
if (! (empty($data->{interval})) ) {
$xml_module .= "\t<module_interval><![CDATA[" . $data->{interval} . "]]></module_interval>\n";
}
if (! (empty($data->{tags})) ) {
$xml_module .= "\t<tags>" . $data->{tags} . "</tags>\n";
}
if (! (empty($data->{module_group})) ) {
$xml_module .= "\t<module_group>" . $data->{module_group} . "</module_group>\n";
}
if (! (empty($data->{module_parent})) ) {
$xml_module .= "\t<module_parent>" . $data->{module_parent} . "</module_parent>\n";
}
if (! (empty($data->{wmin})) ) {
$xml_module .= "\t<min_warning><![CDATA[" . $data->{wmin} . "]]></min_warning>\n";
}
if (! (empty($data->{wmax})) ) {
$xml_module .= "\t<max_warning><![CDATA[" . $data->{wmax} . "]]></max_warning>\n";
}
if (! (empty ($data->{cmin})) ) {
$xml_module .= "\t<min_critical><![CDATA[" . $data->{cmin} . "]]></min_critical>\n";
}
if (! (empty ($data->{cmax})) ){
$xml_module .= "\t<max_critical><![CDATA[" . $data->{cmax} . "]]></max_critical>\n";
}
if (! (empty ($data->{wstr}))) {
$xml_module .= "\t<str_warning><![CDATA[" . $data->{wstr} . "]]></str_warning>\n";
}
if (! (empty ($data->{cstr}))) {
$xml_module .= "\t<str_critical><![CDATA[" . $data->{cstr} . "]]></str_critical>\n";
}
if (! (empty ($data->{cinv}))) {
$xml_module .= "\t<critical_inverse><![CDATA[" . $data->{cinv} . "]]></critical_inverse>\n";
}
if (! (empty ($data->{winv}))) {
$xml_module .= "\t<warning_inverse><![CDATA[" . $data->{winv} . "]]></warning_inverse>\n";
}
if (! (empty ($data->{max}))) {
$xml_module .= "\t<max><![CDATA[" . $data->{max} . "]]></max>\n";
}
if (! (empty ($data->{min}))) {
$xml_module .= "\t<min><![CDATA[" . $data->{min} . "]]></min>\n";
}
if (! (empty ($data->{post_process}))) {
$xml_module .= "\t<post_process><![CDATA[" . $data->{post_process} . "]]></post_process>\n";
}
if (! (empty ($data->{disabled}))) {
$xml_module .= "\t<disabled><![CDATA[" . $data->{disabled} . "]]></disabled>\n";
}
if (! (empty ($data->{min_ff_event}))) {
$xml_module .= "\t<min_ff_event><![CDATA[" . $data->{min_ff_event} . "]]></min_ff_event>\n";
}
if (! (empty ($data->{status}))) {
$xml_module .= "\t<status><![CDATA[" . $data->{status} . "]]></status>\n";
}
if (! (empty ($data->{timestamp}))) {
$xml_module .= "\t<timestamp><![CDATA[" . $data->{timestamp} . "]]></timestamp>\n";
}
if (! (empty ($data->{custom_id}))) {
$xml_module .= "\t<custom_id><![CDATA[" . $data->{custom_id} . "]]></custom_id>\n";
}
if (! (empty ($data->{critical_instructions}))) {
$xml_module .= "\t<critical_instructions><![CDATA[" . $data->{critical_instructions} . "]]></critical_instructions>\n";
}
if (! (empty ($data->{warning_instructions}))) {
$xml_module .= "\t<warning_instructions><![CDATA[" . $data->{warning_instructions} . "]]></warning_instructions>\n";
}
if (! (empty ($data->{unknown_instructions}))) {
$xml_module .= "\t<unknown_instructions><![CDATA[" . $data->{unknown_instructions} . "]]></unknown_instructions>\n";
}
if (! (empty ($data->{quiet}))) {
$xml_module .= "\t<quiet><![CDATA[" . $data->{quiet} . "]]></quiet>\n";
}
if (! (empty ($data->{module_ff_interval}))) {
$xml_module .= "\t<module_ff_interval><![CDATA[" . $data->{module_ff_interval} . "]]></module_ff_interval>\n";
}
if (! (empty ($data->{crontab}))) {
$xml_module .= "\t<crontab><![CDATA[" . $data->{crontab} . "]]></crontab>\n";
}
if (! (empty ($data->{min_ff_event_normal}))) {
$xml_module .= "\t<min_ff_event_normal><![CDATA[" . $data->{min_ff_event_normal} . "]]></min_ff_event_normal>\n";
}
if (! (empty ($data->{min_ff_event_warning}))) {
$xml_module .= "\t<min_ff_event_warning><![CDATA[" . $data->{min_ff_event_warning} . "]]></min_ff_event_warning>\n";
}
if (! (empty ($data->{min_ff_event_critical}))) {
$xml_module .= "\t<min_ff_event_critical><![CDATA[" . $data->{min_ff_event_critical} . "]]></min_ff_event_critical>\n";
}
if (! (empty ($data->{ff_timeout}))) {
$xml_module .= "\t<ff_timeout><![CDATA[" . $data->{ff_timeout} . "]]></ff_timeout>\n";
}
if (! (empty ($data->{each_ff}))) {
$xml_module .= "\t<each_ff><![CDATA[" . $data->{each_ff} . "]]></each_ff>\n";
}
if (! (empty ($data->{parent_unlink}))) {
$xml_module .= "\t<module_parent_unlink><![CDATA[" . $data->{parent_unlink} . "]]></module_parent_unlink>\n";
}
if (! (empty ($data->{alerts}))) {
foreach my $alert (@{$data->{alerts}}){
$xml_module .= "\t<alert_template><![CDATA[" . $alert . "]]></alert_template>\n";
}
}
if (defined ($config->{global_alerts})){
foreach my $alert (@{$config->{global_alerts}}){
$xml_module .= "\t<alert_template><![CDATA[" . $alert . "]]></alert_template>\n";
}
}
$xml_module .= "</module>\n";
if (empty ($not_print_flag)) {
print $xml_module;
}
return $xml_module;
}
################################################################################
# General arguments parser
################################################################################
sub parse_arguments($) {
my $raw = shift;
my @args;
if (defined($raw)){
@args = @{$raw};
}
else {
return {};
}
my %data;
for (my $i = 0; $i < $#args; $i+=2) {
my $key = trim($args[$i]);
$key =~ s/^-//;
$data{$key} = trim($args[$i+1]);
}
return \%data;
}
################################################################################
# General configuration file parser
#
# log=/PATH/TO/LOG/FILE
#
################################################################################
sub parse_configuration($;$$){
my $conf_file = shift;
my $separator;
$separator = shift or $separator = "=";
my $custom_eval = shift;
my $_CFILE;
my $_config;
if (empty($conf_file)) {
return {
error => "Configuration file not specified"
};
}
if( !open ($_CFILE,"<", "$conf_file")) {
return {
error => "Cannot open configuration file"
};
}
while (my $line = <$_CFILE>){
if (($line =~ /^ *\r*\n*$/)
|| ($line =~ /^#/ )){
# skip blank lines and comments
next;
}
my @parsed = split /$separator/, $line, 2;
if ($line =~ /^\s*global_alerts/){
push (@{$_config->{global_alerts}}, trim($parsed[1]));
next;
}
if (ref ($custom_eval) eq "ARRAY") {
my $f = 0;
foreach my $item (@{$custom_eval}) {
if ($line =~ /$item->{exp}/) {
$f = 1;
my $aux;
eval {
$aux = $item->{target}->($item->{exp},$line);
};
if (empty($_config)) {
$_config = $aux;
}
elsif (!empty($aux) && (ref ($aux) eq "HASH")) {
$_config = merge_hashes($_config, $aux);
}
}
}
if (is_enabled($f)){
next;
}
}
$_config->{trim($parsed[0])} = trim($parsed[1]);
}
close ($_CFILE);
return $_config;
}
################################################################################
# End of import
################################################################################
##########################################################################
# Show a message to STDERR
##########################################################################
sub msg {
my $msg = shift;
print STDERR strftime ("%Y-%m-%d %H:%M:%S", localtime()) . ": $msg\n";
}
sub get_next {
my ($route, $step) = @_;
return $route->{'next'}->{$step};
}
##########################################################################
# Extract route steps & timming from mtr output
##########################################################################
sub get_steps {
my ($conf) = @_;
my $target = $conf->{'t'};
return [] if empty($target);
my $mtr_r = "";
my $ping_r = "";
my @route_raw;
my @ping_raw;
if ($^O =~ /win/i){
$ping_r = trim(`ping -r 9 $target -n 1 | tr "Routea:->-" " " | gawk "/^[0-9\. ]*\$/ {if (\$1 != \\"\\"){ print \$1\";\"0}}"`) unless is_enabled($conf->{'-no-ping'});
@ping_raw = split /\n/, $ping_r;
if ($#ping_raw < 0) {
$mtr_r = trim(`mtr -n -o A -c $conf->{'c'} -r $target 2>/NUL | gawk "{print \$2";"\$3}"`) unless is_enabled($conf->{'-no-mtr'});
}
}
else {
$ping_r = trim(`for x in \$(ping -n -c 1 -R $target 2>/dev/null | tr -s "R:" " " | awk '/^[0-9\. \t]*\$/ {if (\$1 != ""){print \$1}}'); do echo -n \$x";"; ping -c $conf->{'c'} \$x 2>/dev/null | grep rtt |awk '{print \$4}'| cut -f2 -d"/"; done`) unless is_enabled($conf->{'-no-ping'});
@ping_raw = split /\n/, $ping_r;
if ($#ping_raw < 0) {
$mtr_r = trim(`mtr -n -c $conf->{'c'} -r $target -o A 2>/dev/null | awk '/^[0-9\\|\\-\\. \\t]*\$/ {print \$2\";\"\$3}'`) unless is_enabled($conf->{'-no-mtr'});
}
}
@route_raw = split /\n/, $mtr_r;
my @modules;
my @steps;
my $route;
if ($#ping_raw >= 0) {
# PING mode
my $rng = scalar @ping_raw;
my $checked;
my $j;
if (is_enabled($conf->{'s'})) {
# Symmetric routing
if ($^O =~ /win/i){
$j = 1;
}
else {
$j = 0;
}
for (my $i=0; $i< ($rng/2); $i++) {
my ($step,$time) = split /;/, $ping_raw[$i];
my $_r;
if (defined($checked->{$step})) {
$j-=2;
next;
}
$checked->{$step} = 1;
$_r->{'step'} = $step;
if ($^O =~ /win/i) {
$_r->{'time'} = trim(`ping -n $conf->{'c'} $_r->{'step'} | grep -e "Av" -e "Me" | gawk "{print \$NF}" | tr -d "ms"`) unless ((!defined($_r->{'step'}) || ($_r->{'step'} eq "")));
}
else {
$_r->{'time'} = $time;
}
if ((!defined($_r->{'step'}) || ($_r->{'step'} eq ""))) {
$_r->{'step'} = "???";
}
$steps[$j] = $_r;
$j+=2;
}
if ($^O =~ /win/i){
$j = 0;
}
else {
$j = 1;
}
for (my $i=$rng-1; $i>= ($rng/2); $i--) {
my ($step,$time) = split /;/, $ping_raw[$i];
my $_r;
if (defined($checked->{$step})) {
$j-=2 if $j>2;
next;
}
$_r->{'step'} = $step;
if ($^O =~ /win/i) {
$_r->{'time'} = trim(`ping -n $conf->{'c'} $_r->{'step'} | grep -e "Av" -e "Me" | gawk "{print \$NF}" | tr -d "ms"`) unless ((!defined($_r->{'step'}) || ($_r->{'step'} eq "")));
}
else {
$_r->{'time'} = $time;
}
if ((!defined($_r->{'step'}) || ($_r->{'step'} eq ""))) {
$_r->{'step'} = "???";
}
$steps[$j] = $_r;
$j+=2;
}
}
else {
# Asymmetric routing
for (my $i=0; $i< $rng; $i++) {
my ($step,$time) = split /;/, $ping_raw[$i];
my $_r;
if (defined($checked->{$step})) {
# target reached
last;
}
$checked->{$step} = 1;
$_r->{'step'} = $step;
if ($^O =~ /win/i) {
$_r->{'time'} = trim(`ping -n $conf->{'c'} $_r->{'step'} | grep -e "Av" -e "Me" | gawk "{print \$NF}" | tr -d "ms"`) unless ((!defined($_r->{'step'}) || ($_r->{'step'} eq "")));
}
else {
$_r->{'time'} = $time;
}
if ((!defined($_r->{'step'}) || ($_r->{'step'} eq ""))) {
$_r->{'step'} = "???";
}
$steps[$i] = $_r;
}
}
my $__origin;
if ($^O !~ /win/i){
$__origin = shift @steps;
}
my $gw;
if ($^O =~ /win/i) {
($gw->{'step'},$__origin->{'step'}) = split /;/, trim(`route print -4 | gawk "BEGIN {min=10000} /^\\ *0.0.0.0/ {met=\$NF;if(met<min){min=met; gw=\$3\\\";\\\"\$4}} END {print gw}"`);
$gw->{'time'} = trim(`ping -n $conf->{'c'} $gw->{'step'} 2>/NUL | grep ms | grep -v TTL | gawk "{print \$NF}" | tr -d "ms"`);
$__origin->{'time'} = 0;
}
else {
$gw->{'step'} = trim(`route -n | awk 'BEGIN {min=100000} /^0/ {met=\$5; if(min>met){gw=\$2;min=met} } END { print gw}'`);
$gw->{'time'} = trim(`ping -c $conf->{'c'} $gw->{'step'} 2>/dev/null | grep rtt |awk '{print \$4}'| cut -f2 -d"/"`);
}
unshift (@steps,($__origin,$gw));
my $unknown_count = 0;
my $previous = undef;
for(my $i=0; $i <= $#steps; $i++) {
my $host = $steps[$i]->{'step'};
my $time = to_number($steps[$i]->{'time'});
my $preffix = 'RouteStep_';
my $desc = '';
if (!defined($time)) {
next;
}
if ($host eq "???") {
$host = "Hidden_" . (++$unknown_count);
}
if (($i == $#steps) && in_array($conf->{'target_ip'},$host)) {
$preffix = 'RouteStepTarget_';
}
elsif($i == $#steps) {
$desc = 'Step unreachable';
}
my $name = $preffix . $host;
if (defined $previous && $name eq $previous) {
next;
}
push @modules, {
name => $preffix . $host,
type => "generic_data",
value => $time,
unit => 'ms',
desc => $desc,
module_parent => $previous,
parent_unlink => (empty($previous)?'1':undef)
};
$previous = $preffix . $host;
}
return \@modules;
}
else {
# MTR mode
if ($#route_raw < 0) {
# Empty output
msg("Failed to analyze [$target]");
return [];
}
for (my $i=0; $i <= $#route_raw; $i++) {
my $line = $route_raw[$i];
if (trim($line) =~ /(.*?);(.*)/) {
my $host = $1;
my $time = to_number($2);
my $preffix = 'RouteStep_';
my $desc = '';
my $item;
my $_r;
if (!defined($time)) {
next;
}
$_r->{'step'} = $host;
$_r->{'time'} = $time;
push @steps, $_r;
}
}
my $__origin;
if ($^O !~ /win/i){
$__origin = shift @steps;
}
my $gw;
if ($^O =~ /win/i) {
($gw->{'step'},$__origin->{'step'}) = split /;/, trim(`route print -4 | gawk "BEGIN {min=10000} /^\\ *0.0.0.0/ {met=\$NF;if(met<min){min=met; gw=\$3\\\";\\\"\$4}} END {print gw}"`);
$gw->{'time'} = trim(`ping -n $conf->{'c'} $gw->{'step'} 2>/NUL | grep ms | grep -v TTL | gawk "{print \$NF}" | tr -d "ms"`);
$__origin->{'time'} = 0;
}
else {
$gw->{'step'} = trim(`route -n | awk 'BEGIN {min=100000} /^0/ {met=\$5; if(min>met){gw=\$2;min=met} } END { print gw}'`);
$gw->{'time'} = trim(`ping -c $conf->{'c'} $gw->{'step'} 2>/dev/null | grep rtt |awk '{print \$4}'| cut -f2 -d"/"`);
my $__xorigin = trim(`ip a show dev \`route -n | awk 'BEGIN {min=100000} /^0/ {met=\$5; if(min>met){iface=\$NF;min=met} } END { print iface}'\` | grep -w inet | awk '{print \$2}' | cut -d'/' -f1`);
if ($__xorigin ne $__origin->{'step'}) {
unshift(@steps, $__origin);
$__origin = {};
$__origin->{'step'} = $__xorigin;
$__origin->{'time'} = 0;
}
}
unshift (@steps,($__origin,$gw));
my $unknown_count = 0;
my $previous = undef;
for(my $i=0; $i <= $#steps; $i++) {
my $host = $steps[$i]->{'step'};
my $time = to_number($steps[$i]->{'time'});
my $preffix = 'RouteStep_';
my $desc = '';
if (!defined($time)) {
next;
}
if ($host eq "???") {
$host = "Hidden_" . (++$unknown_count);
}
if (($i == $#steps) && in_array($conf->{'target_ip'},$host)) {
$preffix = 'RouteStepTarget_';
}
elsif($i == $#steps) {
$desc = 'Step unreachable';
}
my $name = $preffix . $host;
if (defined $previous && $name eq $previous) {
next;
}
push @modules, {
name => $preffix . $host,
type => "generic_data",
value => $time,
unit => 'ms',
desc => $desc,
module_parent => $previous,
parent_unlink => (empty($previous)?'1':undef)
};
$previous = $preffix . $host;
}
}
return \@modules;
}
##########################################################################
##########################################################################
# MAIN
##########################################################################
##########################################################################
if ($#ARGV < 0) {
print STDERR $HELP;
exit 1;
}
my $conf;
my $file_conf = {};
my $args_conf = {};
if (-e $ARGV[0]) {
$file_conf = parse_configuration($ARGV[0]);
shift @ARGV;
}
$args_conf = parse_arguments(\@ARGV);
$conf = merge_hashes($file_conf,$args_conf);
if (!defined $conf->{'t'}) {
print STDERR $HELP;
exit 1;
}
my @targets = gethostbyname($conf->{'t'});
@targets = map { inet_ntoa($_) } @targets[4 .. $#targets];
if (empty(\@targets)) {
print STDERR "Cannot resolve $conf->{'t'} \n";
exit 2;
}
$conf->{'target_ip'} = \@targets;
$conf->{'c'} = 4 unless looks_like_number($conf->{'c'});
$conf->{'s'} = 1 unless looks_like_number($conf->{'s'});
my $results = get_steps($conf);
foreach (@{$results}) {
print_module($conf, $_);
}