2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
26 #include <boost/asio.hpp>
27 #include <boost/foreach.hpp>
28 #include <boost/shared_ptr.hpp>
29 #include <boost/math/special_functions/round.hpp>
30 #include <boost/numeric/conversion/cast.hpp>
31 #include <boost/date_time/posix_time/posix_time_types.hpp>
33 #include <daemonfunc.hpp>
34 #include <logfunc.hpp>
36 #include "boost_assert_handler.h"
37 #include "config/configurationreader.h"
38 #include "config/host.h"
39 #include "link/linkstatus.h"
40 #include "host/loglevel.h"
41 #include "host/pingprotocol.h"
42 #include "host/pingscheduler.h"
43 #include "icmp/icmppinger.h" // contains IcmpPacketDistributor
44 #include "dns/dnsmaster.h"
48 using boost::shared_ptr;
49 using boost::posix_time::time_duration;
50 using I2n::Logger::GlobalLogger;
52 // a map from interval (in seconds) to delay (in seconds)
53 typedef std::pair<int, float> IntervalCountPair;
54 typedef std::map<int, float> DelayMap;
55 typedef shared_ptr<boost::asio::deadline_timer> TimerItem;
57 const boost::posix_time::time_duration SIGNAL_CHECK_INTERVAL = boost::posix_time::seconds(1);
59 //-----------------------------------------------------------------------------
61 //-----------------------------------------------------------------------------
63 typedef std::pair<bool, ConfigurationItem> GetConfigReturnType;
64 GetConfigReturnType get_configuration(int, const char**);
65 LinkStatusItem get_status_notifier(const ConfigurationItem&);
67 void set_log_output(const ConfigurationItem &);
68 DelayMap calc_pinger_delays(const HostList &hosts);
69 bool init_pingers(const IoServiceItem, const ConfigurationItem&,
70 const LinkStatusItem&, PingSchedulerList*);
71 void start_pingers(const PingSchedulerList&);
72 void stop_pingers(const PingSchedulerList&);
74 void signal_handler_int(int param);
75 void signal_handler_term(int param);
76 void signal_handler_usr1(int param);
77 void signal_handler_usr2(int param);
78 void signal_checker( const boost::system::error_code &error );
79 void install_signal_handlers( const IoServiceItem io_service, const int config_log_level );
80 void reset_signal_handlers();
82 // data required for signal handling (SIGINT, SIGTERM, ... )
83 struct signal_data_struct
85 volatile sig_atomic_t signaled_flag_int;
86 volatile sig_atomic_t signaled_flag_term;
87 volatile sig_atomic_t signaled_flag_usr1;
88 volatile sig_atomic_t signaled_flag_usr2;
89 IoServiceItem io_service;
90 void (*old_handler_int )(int);
91 void (*old_handler_term)(int);
92 void (*old_handler_usr1)(int);
93 void (*old_handler_usr2)(int);
95 TimerItem check_timer;
99 signaled_flag_int( 0 ),
100 signaled_flag_term( 0 ),
101 signaled_flag_usr1( 0 ),
102 signaled_flag_usr2( 0 ),
104 old_handler_int( 0 ),
105 old_handler_term( 0 ),
106 old_handler_usr1( 0 ),
107 old_handler_usr2( 0 ),
110 config_log_level( I2n::Logger::LogLevel::Notice )
114 //-----------------------------------------------------------------------------
116 //-----------------------------------------------------------------------------
118 GetConfigReturnType get_configuration(
123 ConfigurationReader config_reader;
124 bool parsed_success = config_reader.parse( argc, argv );
125 Configuration config_obj = config_reader.get_configuration();
127 ConfigurationItem configuration( new Configuration( config_obj ) );
128 GetConfigReturnType return_val( parsed_success, configuration );
132 LinkStatusItem get_status_notifier(
133 const ConfigurationItem &configuration
136 int hosts_down_limit = configuration->get_hosts_down_limit();
137 int link_up_interval_in_min = configuration->get_link_up_interval_in_min();
138 int link_down_interval_in_min = configuration->get_link_down_interval_in_min();
139 string status_notifier_cmd = configuration->get_status_notifier_cmd();
140 LinkStatusItem link_analyzer(
143 link_up_interval_in_min,
144 link_down_interval_in_min,
149 return link_analyzer;
154 // set default: log at level NOTICE to syslog and stderr
155 // to ensure that in case of faulty config, the error is noticed
156 I2n::Logger::enable_syslog( I2n::Logger::Facility::User );
157 I2n::Logger::enable_stderr_log( true );
158 I2n::Logger::set_log_level( I2n::Logger::LogLevel::Notice );
162 const ConfigurationItem &configuration
165 LogOutput log_output = configuration->get_log_output();
166 string log_file_name = configuration->get_log_file();
169 case LogOutput_UNDEFINED:
170 GlobalLogger.warning() << "Unknown output target -- use syslog";
171 case LogOutput_SYSLOG:
172 GlobalLogger.info() << "Setting log output target to syslog" << endl;
173 I2n::Logger::enable_syslog(true);
174 I2n::Logger::enable_stderr_log(false);
175 I2n::Logger::enable_log_file(false);
176 GlobalLogger.info() << "Set log output target to syslog" << endl;
178 case LogOutput_TERMINAL:
179 GlobalLogger.info() << "Setting log output target to terminal" << endl;
180 I2n::Logger::enable_syslog(false);
181 I2n::Logger::enable_stderr_log(true);
182 I2n::Logger::enable_log_file(false);
183 GlobalLogger.info() << "Set log output target to terminal" << endl;
184 GlobalLogger.info() << "(check syslog for earlier messages)" << endl;
187 GlobalLogger.info() << "Setting log output target to file "
188 << log_file_name << endl;
189 I2n::Logger::enable_syslog(false);
190 I2n::Logger::enable_stderr_log(false);
191 I2n::Logger::enable_log_file(log_file_name);
192 GlobalLogger.info() << "Set log output target to file "
193 << log_file_name << endl;
194 GlobalLogger.info() << "(check syslog for earlier messages)" << endl;
197 GlobalLogger.error() << "Unknown log output target!" << endl;
203 * @brief calculate delay between pingers to evenly distribute them in time
205 * If there are many pingers with same interval, will get bursts of pings
206 * and none in-between. This function calculates delays for large numbers
207 * of hosts with same ping intervals, to distribute them as evenly as
208 * possible, right from the start (might diverge over time, anyway).
210 * Will not do much good for pingers with many different intervals, but
211 * then is not required anyway and does no(t much) harm.
213 * Called by init_pingers with
214 * @param hosts list of hosts as obtained from configuration
215 * @returns a map from ping interval to delay between pingers of that interval
217 DelayMap calc_pinger_delays(const HostList &hosts)
219 // first step: count number of hosts with same intervals
220 DelayMap delay_shifts;
222 BOOST_FOREACH( const HostItem &host, hosts )
224 curr_interval = host->get_interval_in_sec();
226 delay_shifts[curr_interval] = 1.0f;
228 delay_shifts[curr_interval] += 1.0f;
231 // second step: divide intervals by counts, round to int
232 // --> for 18 pingers with a 30s interval, get 30s/18 = 1.66667
233 BOOST_FOREACH( IntervalCountPair interval_and_count, delay_shifts )
234 delay_shifts[interval_and_count.first] =
235 boost::numeric_cast<float>(interval_and_count.first) /
236 interval_and_count.second;
242 const IoServiceItem io_service,
243 const ConfigurationItem &configuration,
244 const LinkStatusItem &status_notifier,
245 PingSchedulerList *scheduler_list
248 string default_network_interface = configuration->get_source_network_interface();
249 int ping_fail_limit = configuration->get_ping_fail_limit();
250 int ping_reply_timeout = configuration->get_ping_reply_timeout();
252 // remove some hosts at random
253 configuration->randomize_hosts();
255 // calculate delays between pingers of same interval
256 DelayMap delay_shifts = calc_pinger_delays(configuration->get_hosts());
258 // setup memory for assigned delays
260 BOOST_FOREACH( IntervalCountPair interval_and_delay, delay_shifts )
261 delays[interval_and_delay.first] = 0.0f;
263 HostList hosts = configuration->get_hosts();
268 BOOST_FOREACH( const HostItem &host, hosts )
270 string destination_address = host->get_address();
271 uint16_t destination_port = host->get_port();
272 string host_network_interface = host->get_source_network_interface();
273 string network_interface = ( host_network_interface == "default" ) ?
274 default_network_interface :
275 host_network_interface;
276 PingProtocolList protocol_list = host->get_ping_protocol_list();
277 int ping_interval_in_sec = host->get_interval_in_sec();
279 // get delay for this scheduler and update assigned delays
280 int current_delay = boost::math::iround(delays[ping_interval_in_sec]);
281 delays[ping_interval_in_sec] += delay_shifts[ping_interval_in_sec];
282 int n_parallel_pings = 10;
283 int parallel_ping_delay = 100; // ms
285 PingSchedulerItem scheduler(
292 ping_interval_in_sec,
301 scheduler_list->push_back( scheduler );
308 const PingSchedulerList &scheduler_list
311 // start each ping scheduler
312 BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
313 scheduler->start_pinging();
317 const PingSchedulerList &scheduler_list
320 // Stop each ping scheduler
321 GlobalLogger.info() << "Telling all pingers to stop";
322 BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
324 scheduler->stop_pinging();
327 IcmpPacketDistributor::clean_up_all();
331 // the one instance of signal_data_struct
332 signal_data_struct signal_data;
335 /// registered as signal handler; just sets signal_data.signaled_flag
336 void signal_handler_int(int param)
338 signal_data.signaled_flag_int = 1;
340 void signal_handler_term(int param)
342 signal_data.signaled_flag_term = 1;
344 void signal_handler_usr1(int param)
346 signal_data.signaled_flag_usr1 = 1;
348 void signal_handler_usr2(int param)
350 signal_data.signaled_flag_usr2 = 1;
354 /// called regularly from io_service; checks signal_data.signal_flag
355 void signal_checker( const boost::system::error_code &error )
357 bool want_stop = false;
360 { // there was an error in the timer
361 if ( error == boost::asio::error::operation_aborted )
363 GlobalLogger.error() << "Signal check timer was cancelled! Stopping io_service" << endl;
368 GlobalLogger.error() << "Signal check timer handler received error code " << error
369 << "! Stopping io_service" << endl;
374 if ( signal_data.signaled_flag_int )
376 signal_data.signaled_flag_int = 0;
377 GlobalLogger.notice() << "Received signal SIGINT --> will stop" << endl;
380 else if ( signal_data.signaled_flag_term )
382 signal_data.signaled_flag_term = 0;
383 GlobalLogger.notice() << "Received signal SIGTERM --> will stop" << endl;
386 else if ( signal_data.signaled_flag_usr1 )
388 signal_data.signaled_flag_usr1 = 0;
389 int new_log_level = I2n::Logger::get_log_level()+1;
390 I2n::Logger::set_log_level( new_log_level );
391 GlobalLogger.info() << "Received SIGUSR1 -- increased log level to "
392 << I2n::Logger::get_log_level_string();
394 else if ( signal_data.signaled_flag_usr2 )
396 signal_data.signaled_flag_usr2 = 0;
397 I2n::Logger::set_log_level( signal_data.config_log_level );
398 GlobalLogger.info() << "Received SIGUSR2 -- reset log level to normal ("
399 << I2n::Logger::get_log_level_string() << ")";
404 { // interrupt infinite loop in main and asio event loop
405 signal_data.stopped = true;
406 signal_data.io_service->stop();
409 { // re-schedule timer
410 signal_data.check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
411 signal_data.check_timer->async_wait( signal_checker );
415 /// register own signal handlers; see reset_signal_handlers for undo
416 void install_signal_handlers( const IoServiceItem io_service, const int config_log_level )
418 signal_data.signaled_flag_int = 0;
419 signal_data.signaled_flag_term = 0;
420 signal_data.signaled_flag_usr1 = 0;
421 signal_data.signaled_flag_usr2 = 0;
422 signal_data.config_log_level = config_log_level;
424 // install own signal handlers
425 signal_data.old_handler_int = signal(SIGINT, signal_handler_int);
426 signal_data.old_handler_term = signal(SIGTERM, signal_handler_term);
427 signal_data.old_handler_usr1 = signal(SIGUSR1, signal_handler_usr1);
428 signal_data.old_handler_usr2 = signal(SIGUSR2, signal_handler_usr2);
429 if ( signal_data.old_handler_int == SIG_ERR ||
430 signal_data.old_handler_term == SIG_ERR ||
431 signal_data.old_handler_usr1 == SIG_ERR ||
432 signal_data.old_handler_usr2 == SIG_ERR )
433 throw runtime_error( string("Failed to install signal handler: ") + string(strerror(errno)) );
435 // create a timer and a shared pointer to it, so it does not get out of scope
436 TimerItem check_timer( new boost::asio::deadline_timer( *io_service ) );
438 // remember the io_service and the timer
439 signal_data.io_service = io_service;
440 signal_data.check_timer = check_timer;
443 check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
444 check_timer->async_wait( signal_checker );
445 GlobalLogger.debug() << "signal timer set" << endl;
448 /// reset handlers to the ones saved in install_signal_handlers
449 void reset_signal_handlers()
451 void (*old_handler_int)(int) = 0;
452 void (*old_handler_term)(int) = 0;
453 void (*old_handler_usr1)(int) = 0;
454 void (*old_handler_usr2)(int) = 0;
455 if (signal_data.old_handler_int != 0 )
456 old_handler_int = signal(SIGINT , signal_data.old_handler_int);
457 if (signal_data.old_handler_term != 0 )
458 old_handler_term = signal(SIGTERM, signal_data.old_handler_term);
459 if (signal_data.old_handler_usr1 != 0 )
460 old_handler_usr1 = signal(SIGUSR1, signal_data.old_handler_usr1);
461 if (signal_data.old_handler_usr2 != 0 )
462 old_handler_usr2 = signal(SIGUSR2, signal_data.old_handler_usr2);
464 if ( old_handler_int == SIG_ERR ||
465 old_handler_term == SIG_ERR ||
466 old_handler_usr1 == SIG_ERR ||
467 old_handler_usr2 == SIG_ERR )
468 throw runtime_error( string("Failed to reset signal handler: ") + string(strerror(errno)) );
472 int main( int argc, const char *argv[] )
475 GlobalLogger.debug() << "logger initiated with default config";
477 PingSchedulerList scheduler_list;
478 IoServiceItem io_service;
480 unsigned n_exceptions = 0;
481 unsigned max_exceptions = 0;
485 GetConfigReturnType success_and_config = get_configuration( argc, argv );
486 ConfigurationItem configuration = success_and_config.second;
488 if ( configuration->get_print_version() ) // do this even if parsing of config failed
490 GlobalLogger.debug() << "Printing version info ("
491 << VERSION_STRING << "." << VERSION_REVISION_STRING
492 << " build " << __DATE__
493 << ") and exit" << endl;
494 cout << PROJECT_NAME << " version "
495 << VERSION_STRING << "." << VERSION_REVISION_STRING
496 << " build " << __DATE__
501 if ( ! success_and_config.first )
503 GlobalLogger.error() << "Could not read/parse configuration!";
504 GlobalLogger.debug() << "Return 1 immediately" << endl;
507 GlobalLogger.debug() << "Start setup" << endl;
509 int log_level = configuration->get_log_level();
510 I2n::Logger::set_log_level( log_level );
511 GlobalLogger.info() << "Set LogLevel to " << I2n::Logger::get_log_level_string() << endl;
513 set_log_output( configuration );
514 GlobalLogger.notice() << "started pingcheck version "
515 << VERSION_STRING << "." << VERSION_REVISION_STRING
516 << " build " << __DATE__
519 bool daemon_mode = configuration->get_daemon();
522 I2n::Daemon::daemonize();
525 LinkStatusItem status_notifier = get_status_notifier( configuration );
527 IoServiceItem io_service_temp( new boost::asio::io_service() );
528 io_service_temp.swap( io_service );
529 io_service_temp.reset();
532 boost::asio::ip::address name_server_ip =
533 boost::asio::ip::address::from_string(
534 configuration->get_nameserver() );
535 int max_recursion_count = 10; // could make a config var some time
536 DnsMaster::create_master(
539 configuration->get_resolved_ip_ttl_threshold(),
540 configuration->get_min_time_between_resolves(),
541 configuration->get_max_address_resolution_attempts(),
543 configuration->get_dns_cache_file() );
545 if ( !init_pingers(io_service, configuration, status_notifier,
548 GlobalLogger.error() << "Could not initialize pingers or no hosts "
549 << "given to ping --> exit";
553 install_signal_handlers( io_service, log_level );
555 start_pingers( scheduler_list );
557 catch ( const std::exception &ex )
559 GlobalLogger.error() << "Uncaught exception. " << ex.what() << endl;
564 GlobalLogger.error() << "Caught unknown exception!" << endl;
571 GlobalLogger.info() << "starting io_service main loop" << endl;
573 if (max_exceptions > 0)
574 GlobalLogger.warning() << "Limited number of acceptable exceptions,"
575 << " this is a debugging option!";
577 // call boost::asio main event loop, catching exceptions
578 while ( !signal_data.stopped )
584 catch ( const std::exception &ex )
587 GlobalLogger.error() << "Caught exception, will continue. " << ex.what() << endl;
591 GlobalLogger.error() << "Caught unknown exception, will continue!" << endl;
594 if (max_exceptions > 0 && n_exceptions >= max_exceptions)
596 GlobalLogger.info() << "reached max number of exceptions allowed in main loop" << endl;
598 signal_data.stopped = true;
601 else if ( signal_data.stopped )
602 GlobalLogger.info() << "exiting io_service main loop" << endl;
603 // ( io_service->stop() has been called already in signal handler)
605 GlobalLogger.info() << "continuing io_service main loop" << endl;
612 GlobalLogger.info() << "Cleaning up" << endl;
613 stop_pingers( scheduler_list );
614 reset_signal_handlers();
616 catch ( const std::exception &ex )
618 GlobalLogger.error() << "Uncaught exception while cleaning up: " << ex.what() << endl;
622 GlobalLogger.error() << "Caught unknown exception while cleaning up!" << endl;
626 GlobalLogger.notice() << "Pingcheck done " << endl;
627 GlobalLogger.debug() << "In main loop, had " << n_exceptions << " exception[s]" << endl;