diff --git a/pandora_server/ChangeLog b/pandora_server/ChangeLog index 9fca7a6ff0..eac18e7788 100644 --- a/pandora_server/ChangeLog +++ b/pandora_server/ChangeLog @@ -1,3 +1,15 @@ +2008-06-18 Sancho Lerena + + * pandora_dbstress.pl: Added suppor for "text" moduletypes. + + * pandora_recon: Now is multithreaded. Better management of already + registered IP's. Better status update. Some fixes (xprobe2 crash). + + * Config.pm: Added support for recon_threads token. + + * Tools.pm: added pandora_trash_ascii(), autocreate + set agent in learning mode automatically. + 2008-06-17 Sancho Lerena * config.pm: Added options for xprobe2, and autocreate. diff --git a/pandora_server/bin/pandora_recon b/pandora_server/bin/pandora_recon index 7bcc1ff6d9..7af2ba670f 100755 --- a/pandora_server/bin/pandora_recon +++ b/pandora_server/bin/pandora_recon @@ -2,8 +2,8 @@ ########################################################################## # Pandora FMS Recon Server ########################################################################## -# Copyright (c) 2007 Sancho Lerena, slerena@gmail.com -# Copyright (c) 2007 Artica Soluciones Tecnologicas S.L +# Copyright (c) 2007-2008 Sancho Lerena, slerena@gmail.com +# Copyright (c) 2007-2008 Artica Soluciones Tecnologicas S.L # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -37,6 +37,13 @@ use PandoraFMS::Config; use PandoraFMS::Tools; use PandoraFMS::DB; +# Queue management +my @pending_task : shared; +my %pending_task_hash : shared; +my %current_task_hash : shared; +my $queue_lock : shared; + + # FLUSH in each IO (only for debug, very slooow) # ENABLED in DEBUGMODE # DISABLE FOR PRODUCTION @@ -67,8 +74,15 @@ if ( $pa_config{"daemon"} eq "1" ){ &pandora_daemonize ( \%pa_config); } -# Runs main program (have a infinite loop inside) -threads->new( \&pandora_recon_subsystem, \%pa_config); +# Launch now all plugin threads +# $ax is local thread id for this server + +for (my $ax=0; $ax < $pa_config{'recon_threads'}; $ax++){ + threads->new( \&pandora_recon_consumer, \%pa_config, $ax); +} + +# Launch now the producer thread +threads->new( \&pandora_recon_producer, \%pa_config); sleep(1); @@ -96,45 +110,110 @@ while ( 1 ){ #------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------ -########################################################################## -# SUB pandora_recon_subsystem -# This module runs each X seconds (server threshold) checking for new -# recon tasks pending to do -########################################################################## - -sub pandora_recon_subsystem { - # Init vars +sub pandora_recon_producer ($) { my $pa_config = $_[0]; + print " [*] Starting up Recon Producer Thread ...\n"; + my $dbh = DBI->connect("DBI:mysql:$pa_config->{'dbname'}:$pa_config->{'dbhost'}:3306", $pa_config->{'dbuser'}, $pa_config->{'dbpass'}, { RaiseError => 1, AutoCommit => 1 }); - my $server_id = dame_server_id($pa_config, $pa_config->{'servername'}."_Recon", $dbh); - my $query_sql; # for use in SQL - my $exec_sql; # for use in SQL - my @sql_data; # for use in SQL - while ( 1 ) { - logger ($pa_config, "Loop in Recon Module Subsystem", 10); - $query_sql = "SELECT * FROM trecon_task WHERE id_network_server = $server_id AND status = -1"; - $exec_sql = $dbh->prepare($query_sql); - $exec_sql->execute; - while (@sql_data = $exec_sql->fetchrow_array()) { - my $interval = $sql_data[11]; - my $my_timestamp = &UnixDate("today","%Y-%m-%d %H:%M:%S"); - my $my_utimestamp = &UnixDate($my_timestamp, "%s"); # convert from human to integer - my $utimestamp = $sql_data[9]; - my $id_task = $sql_data[0]; - my $task_name = $sql_data[1]; - # Need to exec this task ? - if (($utimestamp + $interval) < $my_utimestamp){ - logger($pa_config,"Recon Server: Executing task [$task_name]",8); - # EXEC TASK and mark as "in progress" != -1 - pandora_update_reconstatus ($pa_config, $dbh, $id_task, 0); - pandora_recon_exec_task ($pa_config, $id_task); + my $server_id = $pa_config->{'server_id'}; + + # Initialize variables for posterior usage + my $query_sql; + my @sql_data1; + my $data_id_task; + my $exec_sql1; + + while (1) { + $query_sql = "SELECT * FROM trecon_task + WHERE + id_network_server = $server_id + AND + status = -1 + AND + (utimestamp + interval_sweep) < UNIX_TIMESTAMP() + "; + + $exec_sql1 = $dbh->prepare($query_sql); + $exec_sql1 ->execute; + while (@sql_data1 = $exec_sql1->fetchrow_array()) { + $data_id_task = $sql_data1[0]; + + # Skip modules already queued + if ((!defined($pending_task_hash{$data_id_task})) && + (!defined($current_task_hash{$data_id_task}))) { + pandora_update_reconstatus ($pa_config, $dbh, $data_id_task, 0); + # Locking scope, do not remove redundant { } + { + lock $queue_lock; + push (@pending_task, $data_id_task); + $pending_task_hash {$data_id_task}=1; + } } - } - $exec_sql->finish(); + } + $exec_sql1->finish(); + threads->yield; sleep($pa_config->{"server_threshold"}); + } # Main loop +} + +########################################################################## +# SUB pandora_recon_consumer +# This module runs each X seconds (server threshold) checking for recon task +########################################################################## +sub pandora_recon_consumer ($$) { + my $pa_config = $_[0]; + my $thread_id = $_[1]; + + if ($pa_config->{"quiet"} == 0){ + print " [*] Starting up Recon Consumer Thread # $thread_id \n"; + } + + my $data_id_task; + # Create Database handler + my $dbh = DBI->connect("DBI:mysql:$pa_config->{'dbname'}:$pa_config->{'dbhost'}:3306", $pa_config->{'dbuser'}, $pa_config->{'dbpass'}, { RaiseError => 1, AutoCommit => 1 }); + my $counter =0; + + LOOP: while (1) { + if ($counter > 10) { + threads->yield; + $counter = 0; + } + + # Take the first element on the shared queue + # Insert this element on the current task hash + { + lock $queue_lock; + if (scalar(@pending_task) == 0){ + $counter++; + next LOOP; + } + + $data_id_task = shift(@pending_task); + delete($pending_task_hash{$data_id_task}); + $current_task_hash{$data_id_task}=1; + } + + # Executing recon task with unmanaged error trapping + eval { + pandora_recon_exec_task ($pa_config, $data_id_task, $dbh); + }; + if ($@){ + logger ($pa_config, "[ERROR] Recon Task for Task ID $data_id_task causes a system exception", 0); + logger ($pa_config, "ERROR Code: $@", 1); + } + + # Remove from queue. If catch an error, probably data is + # not been processed, but has been freed from task queue + { + lock $queue_lock; + delete($current_task_hash{$data_id_task}); + } + $counter = 0; + threads->yield; } } + ########################################################################## # SUB pandora_detect_os (paconfig, host) # Detect OS using xprobe2 tool. Return tconfig_os id code. @@ -146,9 +225,15 @@ sub pandora_detect_os { my $xprobe2 = $pa_config->{"xprobe2"}; if (! -e $xprobe2){ - return 10; + return 10; # other } - my $command = `$xprobe2 $host 2> /dev/null | grep "Running OS" | head -1`; + my $command= ""; + eval { + $command = `$xprobe2 $host 2> /dev/null | grep "Running OS" | head -1`; + }; + if ($@){ + return 10; + } return pandora_get_os ($command); } ########################################################################## @@ -158,14 +243,15 @@ sub pandora_detect_os { sub pandora_recon_exec_task { my $pa_config = $_[0]; my $id_task = $_[1]; + my $dbh = $_[2]; + my $target_ip; # Real ip to check my @ip2; # temp array for NetAddr::IP my $space; # temp var to store space of ip's for netaddr::ip my $query_sql; # for use in SQL my $exec_sql; # for use in SQL my @sql_data; # for use in SQL - my $dbh = DBI->connect("DBI:mysql:$pa_config->{'dbname'}:$pa_config->{'dbhost'}:3306", $pa_config->{'dbuser'}, $pa_config->{'dbpass'}, { RaiseError => 1, AutoCommit => 1 }); - + $query_sql = "SELECT * FROM trecon_task WHERE id_rt = $id_task"; $exec_sql = $dbh->prepare($query_sql); $exec_sql ->execute; @@ -209,20 +295,21 @@ sub pandora_recon_exec_task { $space++; $position++; $add_host = 0; - # Check ICMP for this IP - if (($task_type == 1) && (scan_icmp ($target_ip, $pa_config->{'networktimeout'}) == 1)){ - $add_host = 1; - } - # Check TCP port for this IP - elsif (($task_type == 2) && (scan_icmp ($target_ip, $pa_config->{'networktimeout'}) == 1)) { - if (scan_tcp ($target_ip, $pa_config->{'networktimeout'}, $extended_value) == 1){ + # Is this IP listed for any agent ? + if (pandora_check_ip ($pa_config, $dbh, $target_ip) == 0){ + + # Check ICMP for this IP + if (($task_type == 1) && (scan_icmp ($target_ip, $pa_config->{'networktimeout'}) == 1)){ $add_host = 1; } - } - - if ($add_host == 1){ - # Is this IP listed for any agent ? - if (pandora_check_ip ($pa_config, $dbh, $target_ip) == 0){ + # Check TCP port for this IP + elsif (($task_type == 2) && (scan_icmp ($target_ip, $pa_config->{'networktimeout'}) == 1)) { + if (scan_tcp ($target_ip, $pa_config->{'networktimeout'}, $extended_value) == 1){ + $add_host = 1; + } + } + + if ($add_host == 1){ $host_found ++; my $target_ip_resolved = resolv_ip2name($target_ip); $list_ip = $list_ip." ".$target_ip; @@ -248,7 +335,8 @@ sub pandora_recon_exec_task { } my $progress = ceil($position / ($total_hosts / 100)); - pandora_update_reconstatus ($pa_config, $dbh, $id_task, $progress); + pandora_update_reconstatus ($pa_config, $dbh, $id_task, $progress); + pandora_task_set_utimestamp ($pa_config, $dbh, $id_task); } while ($space < $space->broadcast); # fin del buclie principal de iteracion de Ips # Create incident @@ -276,27 +364,27 @@ sub scan_icmp { my $l_timeout = $_[1]; # temporal vars. my $result = 0; - my $result2 = 0; my $p; # Check for valid destination if (!defined($dest)) { return 0; } + # Thread safe # Some hosts don't accept ICMP with too small payload. Use 16 Bytes - $p = Net::Ping->new("icmp",$l_timeout,16); - $p->source_verify(1); - - $result = $p->ping($dest); - $result2 = $p->ping($dest); + { + $p = Net::Ping->new("icmp",$l_timeout,16); + $p->source_verify(1); + $result = $p->ping($dest); + } # Check for valid result - if ((!defined($result)) || (!defined($result2))) { + if (!defined($result)) { return 0; } # Lets see the result - if (($result == 1) && ($result2 == 1)) { + if ($result == 1) { $p->close(); return 1; } else { @@ -326,6 +414,9 @@ sub scan_tcp { } alarm 0; }; + if ($@){ + return 0; + } return $opened; } @@ -530,14 +621,7 @@ sub pandora_getparent ($$){ ); my $success = 0; - eval { - alarm $pa_config->{"networktimeout"}; - $success = $t->traceroute(); - alarm 0; - }; - if ($@){ - return 0; - } + $success = $t->traceroute(); if ($t->hops > 1){ if ($success){ my $parent_ip = $t->hop_query_host($t->hops-1,0); diff --git a/pandora_server/conf/pandora_server.conf b/pandora_server/conf/pandora_server.conf index cd318c6e7f..8d0cc5aad6 100755 --- a/pandora_server/conf/pandora_server.conf +++ b/pandora_server/conf/pandora_server.conf @@ -176,3 +176,7 @@ autocreate_group 2 # set to 0 to disable autocreate 1 + +# recon_threads (3 by default) + +recon_threads 3 \ No newline at end of file diff --git a/pandora_server/lib/PandoraFMS/Config.pm b/pandora_server/lib/PandoraFMS/Config.pm index 932f24e100..de8794a7c7 100644 --- a/pandora_server/lib/PandoraFMS/Config.pm +++ b/pandora_server/lib/PandoraFMS/Config.pm @@ -175,6 +175,7 @@ sub pandora_loadconfig { $pa_config->{"tcp_timeout"} = 20; # Introduced on 1.3.1 $pa_config->{"snmp_proc_deadresponse"} = 0; # Introduced on 1.3.1 10 Feb08 $pa_config->{"plugin_threads"} = 3; # Introduced on 2.0 + $pa_config->{"recon_threads"} = 3; # Introduced on 2.0 $pa_config->{"prediction_threads"} = 3; # Introduced on 2.0 $pa_config->{"plugin_timeout"} = 5; # Introduced on 2.0 $pa_config->{"wmi_threads"} = 3; # Introduced on 2.0 @@ -194,6 +195,7 @@ sub pandora_loadconfig { $pa_config->{"xprobe2"} = "/usr/bin/xprobe2"; $pa_config->{'autocreate_group'} = 2; $pa_config->{'autocreate'} = 1; + $pa_config{'recon_threads'} = 3; # Check for UID0 if ($pa_config->{"quiet"} != 0){ @@ -397,6 +399,9 @@ sub pandora_loadconfig { elsif ($parametro =~ m/^autocreate_group\s([0-9*]*)/i) { $pa_config->{'autocreate_group'}= clean_blank($1); } + elsif ($parametro =~ m/^recon_threads\s([0-9]*)/i) { + $pa_config->{'recon_threads'}= clean_blank($1); + } } # end of loop for parameter # diff --git a/pandora_server/lib/PandoraFMS/DB.pm b/pandora_server/lib/PandoraFMS/DB.pm index a1d69ea2b8..c60338a759 100644 --- a/pandora_server/lib/PandoraFMS/DB.pm +++ b/pandora_server/lib/PandoraFMS/DB.pm @@ -560,7 +560,8 @@ sub execute_alert (%$$$$$$$$$$$$$$$) { ########################################################################## -## SUB pandora_writestate (pa_config, nombre_agente,tipo_modulo,nombre_modulo,valor_datos, estado) +## SUB pandora_writestate (pa_config, nombre_agente,tipo_modulo, +# nombre_modulo,valor_datos, estado, dbh, needupdate) ## Alter data, chaning status of modules in state table ########################################################################## @@ -1001,9 +1002,9 @@ sub module_generic_data_string (%$$$$$) { my $m_data = $datos->{data}->[0]; my $a_desc = $datos->{description}->[0]; my $a_max = $datos->{max}->[0]; - my $a_min = $datos->{min}->[0]; + my $a_min = $datos->{min}->[0]; if (ref($m_data) eq "HASH") { - $m_data = XMLout($m_data, RootName=>undef); + $m_data = XMLout($m_data, RootName=>undef); } if (ref($a_max) eq "HASH") { $a_max = ""; @@ -1018,19 +1019,20 @@ sub module_generic_data_string (%$$$$$) { ########################################################################## -## SUB pandora_writedata (pa_config, timestamp,nombre_agente,tipo_modulo,nombre_modulo,datos) +## SUB pandora_writedata (pa_config, timestamp,nombre_agente,tipo_modulo, +# nombre_modulo, datos, max, min, descripcion, dbh, update) ## Insert data in main table: tagente_datos ########################################################################## sub pandora_writedata (%$$$$$$$$$$){ my $pa_config = $_[0]; - my $timestamp = $_[1]; - my $nombre_agente = $_[2]; - my $tipo_modulo = $_[3]; - my $nombre_modulo = $_[4]; - my $datos = $_[5]; - my $max = $_[6]; + my $timestamp = $_[1]; + my $nombre_agente = $_[2]; + my $tipo_modulo = $_[3]; + my $nombre_modulo = $_[4]; + my $datos = $_[5]; + my $max = $_[6]; my $min = $_[7]; my $descripcion = $_[8]; my $dbh = $_[9]; diff --git a/pandora_server/lib/PandoraFMS/Tools.pm b/pandora_server/lib/PandoraFMS/Tools.pm index 9269d31dd3..6cea116ec9 100644 --- a/pandora_server/lib/PandoraFMS/Tools.pm +++ b/pandora_server/lib/PandoraFMS/Tools.pm @@ -41,8 +41,25 @@ our @EXPORT = qw( pandora_create_agent pandora_get_os pandora_event + pandora_trash_ascii ); +########################################################################## +## SUB pandora_trash_ascii +# Generate random ascii strings with variable lenght +########################################################################## + +sub pandora_trash_ascii { + my $config_depth = $_[0]; + my $a; + my $output; + + for ($a=0;$a<$config_depth;$a++){ + $output = $output.chr(int(rand(25)+97)); + } + return $output +} + ########################################################################## ## SUB pandora_event ## Write in internal audit system an entry. @@ -157,7 +174,7 @@ sub pandora_create_agent { my $server = $pa_config->{'servername'}.$pa_config->{"servermode"}; logger($pa_config,"$server: Creating agent $name $target_ip ", 1); - my $query_sql2 = "INSERT INTO tagente (nombre, direccion, comentarios, id_grupo, id_os, id_network_server, intervalo, id_parent) VALUES ('$name', '$target_ip', 'Created by $server', $id_group, $id_os, $id_server, 300, $id_parent)"; + my $query_sql2 = "INSERT INTO tagente (nombre, direccion, comentarios, id_grupo, id_os, id_network_server, intervalo, id_parent, modo) VALUES ('$name', '$target_ip', 'Created by $server', $id_group, $id_os, $id_server, 300, $id_parent, 1)"; $dbh->do ($query_sql2); my $lastid = $dbh->{'mysql_insertid'}; diff --git a/pandora_server/util/pandora_dbstress.pl b/pandora_server/util/pandora_dbstress.pl index 4cbbf5234b..9eaaf77920 100755 --- a/pandora_server/util/pandora_dbstress.pl +++ b/pandora_server/util/pandora_dbstress.pl @@ -197,6 +197,23 @@ sub process_module(){ } } + + # Generate pseudo-random data for boolean data + if ( $target_name =~ /text/i ){ + for ($a=1;$a<$iterations;$a++){ + $valor = pandora_trash_ascii (rand(100)+50); + $m_timestamp = DateCalc($m_timestamp,"+ $target_interval seconds",\$err); + $mysql_date = &UnixDate($m_timestamp,"%Y-%m-%d %H:%M:%S"); + if ($a % 20 eq 0) { + print "\r -> ".int($a / ($iterations / 100))."% generated for ($target_name) "; + } + pandora_lastagentcontact($pa_config, $mysql_date, $agent_name, "none","1.2", $target_interval, $dbh); + #print LOG $mysql_date, $target_name, $valor, "\n"; + pandora_writedata($pa_config,$mysql_date,$agent_name,$target_type,$target_name,$valor,0,0,"",$dbh,\$bUpdateDatos); + pandora_writestate ($pa_config,$agent_name,$target_type,$target_name,$valor,100,$dbh,$bUpdateDatos); + } + } + close (LOG); print "\n"; }