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 void 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_SYSLOG:
170 GlobalLogger.info() << "Setting log output target to syslog" << endl;
171 I2n::Logger::enable_syslog(true);
172 I2n::Logger::enable_stderr_log(false);
173 I2n::Logger::enable_log_file(false);
174 GlobalLogger.info() << "Set log output target to syslog" << endl;
176 case LogOutput_TERMINAL:
177 GlobalLogger.info() << "Setting log output target to terminal" << endl;
178 I2n::Logger::enable_syslog(false);
179 I2n::Logger::enable_stderr_log(true);
180 I2n::Logger::enable_log_file(false);
181 GlobalLogger.info() << "Set log output target to terminal" << endl;
182 GlobalLogger.info() << "(check syslog for earlier messages)" << endl;
185 GlobalLogger.info() << "Setting log output target to file "
186 << log_file_name << endl;
187 I2n::Logger::enable_syslog(false);
188 I2n::Logger::enable_stderr_log(false);
189 I2n::Logger::enable_log_file(log_file_name);
190 GlobalLogger.info() << "Set log output target to file "
191 << log_file_name << endl;
192 GlobalLogger.info() << "(check syslog for earlier messages)" << endl;
195 GlobalLogger.error() << "Unknown log output target!" << endl;
201 * @brief calculate delay between pingers to evenly distribute them in time
203 * If there are many pingers with same interval, will get bursts of pings
204 * and none in-between. This function calculates delays for large numbers
205 * of hosts with same ping intervals, to distribute them as evenly as
206 * possible, right from the start (might diverge over time, anyway).
208 * Will not do much good for pingers with many different intervals, but
209 * then is not required anyway and does no(t much) harm.
211 * Called by init_pingers with
212 * @param hosts list of hosts as obtained from configuration
213 * @returns a map from ping interval to delay between pingers of that interval
215 DelayMap calc_pinger_delays(const HostList &hosts)
217 // first step: count number of hosts with same intervals
218 DelayMap delay_shifts;
220 BOOST_FOREACH( const HostItem &host, hosts )
222 curr_interval = host->get_interval_in_sec();
224 delay_shifts[curr_interval] = 1.0f;
226 delay_shifts[curr_interval] += 1.0f;
229 // second step: divide intervals by counts, round to int
230 // --> for 18 pingers with a 30s interval, get 30s/18 = 1.66667
231 BOOST_FOREACH( IntervalCountPair interval_and_count, delay_shifts )
232 delay_shifts[interval_and_count.first] =
233 boost::numeric_cast<float>(interval_and_count.first) /
234 interval_and_count.second;
240 const IoServiceItem io_service,
241 const ConfigurationItem &configuration,
242 const LinkStatusItem &status_notifier,
243 PingSchedulerList *scheduler_list
246 string default_network_interface = configuration->get_source_network_interface();
247 int ping_fail_limit = configuration->get_ping_fail_limit();
248 int ping_reply_timeout = configuration->get_ping_reply_timeout();
250 // remove some hosts at random
251 configuration->randomize_hosts();
253 // calculate delays between pingers of same interval
254 DelayMap delay_shifts = calc_pinger_delays(configuration->get_hosts());
256 // setup memory for assigned delays
258 BOOST_FOREACH( IntervalCountPair interval_and_delay, delay_shifts )
259 delays[interval_and_delay.first] = 0.0f;
261 HostList hosts = configuration->get_hosts();
262 BOOST_FOREACH( const HostItem &host, hosts )
264 string destination_address = host->get_address();
265 uint16_t destination_port = host->get_port();
266 string host_network_interface = host->get_source_network_interface();
267 string network_interface = ( host_network_interface == "default" ) ?
268 default_network_interface :
269 host_network_interface;
270 PingProtocolList protocol_list = host->get_ping_protocol_list();
271 int ping_interval_in_sec = host->get_interval_in_sec();
273 // get delay for this scheduler and update assigned delays
274 int current_delay = boost::math::iround(delays[ping_interval_in_sec]);
275 delays[ping_interval_in_sec] += delay_shifts[ping_interval_in_sec];
277 PingSchedulerItem scheduler(
284 ping_interval_in_sec,
291 scheduler_list->push_back( scheduler );
296 const PingSchedulerList &scheduler_list
299 // start each ping scheduler
300 BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
301 scheduler->start_pinging();
305 const PingSchedulerList &scheduler_list
308 // Stop each ping scheduler
309 GlobalLogger.info() << "Telling all pingers to stop";
310 BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
312 scheduler->stop_pinging();
315 IcmpPacketDistributor::clean_up_all();
319 // the one instance of signal_data_struct
320 signal_data_struct signal_data;
323 /// registered as signal handler; just sets signal_data.signaled_flag
324 void signal_handler_int(int param)
326 signal_data.signaled_flag_int = 1;
328 void signal_handler_term(int param)
330 signal_data.signaled_flag_term = 1;
332 void signal_handler_usr1(int param)
334 signal_data.signaled_flag_usr1 = 1;
336 void signal_handler_usr2(int param)
338 signal_data.signaled_flag_usr2 = 1;
342 /// called regularly from io_service; checks signal_data.signal_flag
343 void signal_checker( const boost::system::error_code &error )
345 bool want_stop = false;
348 { // there was an error in the timer
349 if ( error == boost::asio::error::operation_aborted )
351 GlobalLogger.error() << "Signal check timer was cancelled! Stopping io_service" << endl;
356 GlobalLogger.error() << "Signal check timer handler received error code " << error
357 << "! Stopping io_service" << endl;
362 if ( signal_data.signaled_flag_int )
364 signal_data.signaled_flag_int = 0;
365 GlobalLogger.notice() << "Received signal SIGINT --> will stop" << endl;
368 else if ( signal_data.signaled_flag_term )
370 signal_data.signaled_flag_term = 0;
371 GlobalLogger.notice() << "Received signal SIGTERM --> will stop" << endl;
374 else if ( signal_data.signaled_flag_usr1 )
376 signal_data.signaled_flag_usr1 = 0;
377 int new_log_level = I2n::Logger::get_log_level()+1;
378 I2n::Logger::set_log_level( new_log_level );
379 GlobalLogger.info() << "Received SIGUSR1 -- increased log level to "
380 << I2n::Logger::get_log_level_string();
382 else if ( signal_data.signaled_flag_usr2 )
384 signal_data.signaled_flag_usr2 = 0;
385 I2n::Logger::set_log_level( signal_data.config_log_level );
386 GlobalLogger.info() << "Received SIGUSR2 -- reset log level to normal ("
387 << I2n::Logger::get_log_level_string() << ")";
392 { // interrupt infinite loop in main and asio event loop
393 signal_data.stopped = true;
394 signal_data.io_service->stop();
397 { // re-schedule timer
398 signal_data.check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
399 signal_data.check_timer->async_wait( signal_checker );
403 /// register own signal handlers; see reset_signal_handlers for undo
404 void install_signal_handlers( const IoServiceItem io_service, const int config_log_level )
406 signal_data.signaled_flag_int = 0;
407 signal_data.signaled_flag_term = 0;
408 signal_data.signaled_flag_usr1 = 0;
409 signal_data.signaled_flag_usr2 = 0;
410 signal_data.config_log_level = config_log_level;
412 // install own signal handlers
413 signal_data.old_handler_int = signal(SIGINT, signal_handler_int);
414 signal_data.old_handler_term = signal(SIGTERM, signal_handler_term);
415 signal_data.old_handler_usr1 = signal(SIGUSR1, signal_handler_usr1);
416 signal_data.old_handler_usr2 = signal(SIGUSR2, signal_handler_usr2);
417 if ( signal_data.old_handler_int == SIG_ERR ||
418 signal_data.old_handler_term == SIG_ERR ||
419 signal_data.old_handler_usr1 == SIG_ERR ||
420 signal_data.old_handler_usr2 == SIG_ERR )
421 throw runtime_error( string("Failed to install signal handler: ") + string(strerror(errno)) );
423 // create a timer and a shared pointer to it, so it does not get out of scope
424 TimerItem check_timer( new boost::asio::deadline_timer( *io_service ) );
426 // remember the io_service and the timer
427 signal_data.io_service = io_service;
428 signal_data.check_timer = check_timer;
431 check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
432 check_timer->async_wait( signal_checker );
433 GlobalLogger.debug() << "signal timer set" << endl;
436 /// reset handlers to the ones saved in install_signal_handlers
437 void reset_signal_handlers()
439 void (*old_handler_int)(int) = 0;
440 void (*old_handler_term)(int) = 0;
441 void (*old_handler_usr1)(int) = 0;
442 void (*old_handler_usr2)(int) = 0;
443 if (signal_data.old_handler_int != 0 )
444 old_handler_int = signal(SIGINT , signal_data.old_handler_int);
445 if (signal_data.old_handler_term != 0 )
446 old_handler_term = signal(SIGTERM, signal_data.old_handler_term);
447 if (signal_data.old_handler_usr1 != 0 )
448 old_handler_usr1 = signal(SIGUSR1, signal_data.old_handler_usr1);
449 if (signal_data.old_handler_usr2 != 0 )
450 old_handler_usr2 = signal(SIGUSR2, signal_data.old_handler_usr2);
452 if ( old_handler_int == SIG_ERR ||
453 old_handler_term == SIG_ERR ||
454 old_handler_usr1 == SIG_ERR ||
455 old_handler_usr2 == SIG_ERR )
456 throw runtime_error( string("Failed to reset signal handler: ") + string(strerror(errno)) );
460 int main( int argc, const char *argv[] )
463 GlobalLogger.debug() << "logger initiated with default config";
465 PingSchedulerList scheduler_list;
466 IoServiceItem io_service;
468 unsigned n_exceptions = 0;
469 unsigned max_exceptions = 0;
473 GetConfigReturnType success_and_config = get_configuration( argc, argv );
474 ConfigurationItem configuration = success_and_config.second;
476 if ( configuration->get_print_version() ) // do this even if parsing of config failed
478 GlobalLogger.debug() << "Printing version info (" << VERSION_STRING << ") and exit" << endl;
479 cout << PROJECT_NAME << " version " << VERSION_STRING << "."
480 << VERSION_REVISION_STRING << " build " << __DATE__ << endl;
484 if ( ! success_and_config.first )
486 GlobalLogger.error() << "Could not read/parse configuration!";
487 GlobalLogger.debug() << "Return 1 immediately" << endl;
490 GlobalLogger.debug() << "Start setup" << endl;
492 int log_level = configuration->get_log_level();
493 I2n::Logger::set_log_level( log_level );
494 GlobalLogger.info() << "Set LogLevel to " << I2n::Logger::get_log_level_string() << endl;
496 set_log_output( configuration );
497 GlobalLogger.notice() << "started";
499 bool daemon_mode = configuration->get_daemon();
502 I2n::Daemon::daemonize();
505 LinkStatusItem status_notifier = get_status_notifier( configuration );
507 IoServiceItem io_service_temp( new boost::asio::io_service() );
508 io_service_temp.swap( io_service );
509 io_service_temp.reset();
512 boost::asio::ip::address name_server_ip =
513 boost::asio::ip::address::from_string(
514 configuration->get_nameserver() );
516 DnsMaster::create_master(
519 configuration->get_resolved_ip_ttl_threshold(),
520 configuration->get_min_time_between_resolves(),
521 configuration->get_max_address_resolution_attempts(),
522 configuration->get_dns_cache_file() );
524 init_pingers( io_service, configuration, status_notifier, &scheduler_list );
526 install_signal_handlers( io_service, log_level );
528 start_pingers( scheduler_list );
530 catch ( const std::exception &ex )
532 GlobalLogger.error() << "Uncaught exception. " << ex.what() << endl;
537 GlobalLogger.error() << "Caught unknown exception!" << endl;
544 GlobalLogger.info() << "starting io_service main loop" << endl;
546 if (max_exceptions > 0)
547 GlobalLogger.warning() << "Limited number of acceptable exceptions,"
548 << " this is a debugging option!";
550 // call boost::asio main event loop, catching exceptions
551 while ( !signal_data.stopped )
557 catch ( const std::exception &ex )
560 GlobalLogger.error() << "Caught exception, will continue. " << ex.what() << endl;
564 GlobalLogger.error() << "Caught unknown exception, will continue!" << endl;
567 if (max_exceptions > 0 && n_exceptions >= max_exceptions)
569 GlobalLogger.info() << "reached max number of exceptions allowed in main loop" << endl;
571 signal_data.stopped = true;
574 else if ( signal_data.stopped )
575 GlobalLogger.info() << "exiting io_service main loop" << endl;
576 // ( io_service->stop() has been called already in signal handler)
578 GlobalLogger.info() << "continuing io_service main loop" << endl;
585 GlobalLogger.info() << "Cleaning up" << endl;
586 stop_pingers( scheduler_list );
587 reset_signal_handlers();
589 catch ( const std::exception &ex )
591 GlobalLogger.error() << "Uncaught exception while cleaning up: " << ex.what() << endl;
595 GlobalLogger.error() << "Caught unknown exception while cleaning up!" << endl;
599 GlobalLogger.notice() << "Pingcheck done " << endl;
600 GlobalLogger.debug() << "In main loop, had " << n_exceptions << " exception[s]" << endl;