d0803346fe11d69ecd3dfbf8b0dc6b508d9375a2
[pingcheck] / src / main.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
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.
13
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.
16
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.
19 */
20 #include <signal.h>
21 #include <stdint.h>
22
23 #include <vector>
24 #include <iostream>
25
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>
32
33 #include <daemonfunc.hpp>
34 #include <logfunc.hpp>
35
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"
45
46
47 using namespace std;
48 using boost::shared_ptr;
49 using boost::posix_time::time_duration;
50 using I2n::Logger::GlobalLogger;
51
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;
56
57 const boost::posix_time::time_duration SIGNAL_CHECK_INTERVAL = boost::posix_time::seconds(1);
58
59 //-----------------------------------------------------------------------------
60 // Declarations
61 //-----------------------------------------------------------------------------
62
63 typedef std::pair<bool, ConfigurationItem> GetConfigReturnType;
64 GetConfigReturnType get_configuration(int, const char**);
65 LinkStatusItem get_status_notifier(const ConfigurationItem&);
66 void init_logger();
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&);
73
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();
81
82 // data required for signal handling (SIGINT, SIGTERM, ... )
83 struct signal_data_struct
84 {
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);
94     bool stopped;
95     TimerItem check_timer;
96     int config_log_level;
97
98     signal_data_struct():
99         signaled_flag_int( 0 ),
100         signaled_flag_term( 0 ),
101         signaled_flag_usr1( 0 ),
102         signaled_flag_usr2( 0 ),
103         io_service(),
104         old_handler_int( 0 ),
105         old_handler_term( 0 ),
106         old_handler_usr1( 0 ),
107         old_handler_usr2( 0 ),
108         stopped( false ),
109         check_timer(),
110         config_log_level( I2n::Logger::LogLevel::Notice )
111     { }
112
113 };
114 //-----------------------------------------------------------------------------
115 // Definitions
116 //-----------------------------------------------------------------------------
117
118 GetConfigReturnType get_configuration(
119         int argc,
120         const char *argv[]
121 )
122 {
123     ConfigurationReader config_reader;
124     bool parsed_success = config_reader.parse( argc, argv );
125     Configuration config_obj = config_reader.get_configuration();
126
127     ConfigurationItem configuration( new Configuration( config_obj ) );
128     GetConfigReturnType return_val( parsed_success, configuration );
129     return return_val;
130 }
131
132 LinkStatusItem get_status_notifier(
133         const ConfigurationItem &configuration
134 )
135 {
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(
141             new LinkStatus(
142                     hosts_down_limit,
143                     link_up_interval_in_min,
144                     link_down_interval_in_min,
145                     status_notifier_cmd
146             )
147     );
148
149     return link_analyzer;
150 }
151
152 void init_logger()
153 {
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 );
159 }
160
161 void set_log_output(
162         const ConfigurationItem &configuration
163 )
164 {
165     LogOutput log_output = configuration->get_log_output();
166     string log_file_name = configuration->get_log_file();
167     switch (log_output)
168     {
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;
175         break;
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;
183         break;
184     case LogOutput_FILE:
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;
193         break;
194     default:
195         GlobalLogger.error() << "Unknown log output target!" << endl;
196         break;
197     }
198 }
199
200 /**
201  * @brief calculate delay between pingers to evenly distribute them in time
202  *
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).
207  *
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.
210  *
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
214  */
215 DelayMap calc_pinger_delays(const HostList &hosts)
216 {
217     // first step: count number of hosts with same intervals
218     DelayMap delay_shifts;
219     int curr_interval;
220     BOOST_FOREACH( const HostItem &host, hosts )
221     {
222         curr_interval = host->get_interval_in_sec();
223         if (! curr_interval)
224             delay_shifts[curr_interval] = 1.0f;
225         else
226             delay_shifts[curr_interval] += 1.0f;
227     }
228
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;
235
236     return delay_shifts;
237 }
238
239 void init_pingers(
240         const IoServiceItem io_service,
241         const ConfigurationItem &configuration,
242         const LinkStatusItem &status_notifier,
243         PingSchedulerList *scheduler_list
244 )
245 {
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();
249
250     // remove some hosts at random
251     configuration->randomize_hosts();
252
253     // calculate delays between pingers of same interval
254     DelayMap delay_shifts = calc_pinger_delays(configuration->get_hosts());
255
256     // setup memory for assigned delays
257     DelayMap delays;
258     BOOST_FOREACH( IntervalCountPair interval_and_delay, delay_shifts )
259         delays[interval_and_delay.first] = 0.0f;
260
261     HostList hosts = configuration->get_hosts();
262     BOOST_FOREACH( const HostItem &host, hosts )
263     {
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();
272
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];
276
277         PingSchedulerItem scheduler(
278                 new PingScheduler(
279                         io_service,
280                         network_interface,
281                         destination_address,
282                         destination_port,
283                         protocol_list,
284                         ping_interval_in_sec,
285                         ping_fail_limit,
286                         ping_reply_timeout,
287                         status_notifier,
288                         current_delay
289                 )
290         );
291         scheduler_list->push_back( scheduler );
292     }
293 }
294
295 void start_pingers(
296         const PingSchedulerList &scheduler_list
297 )
298 {
299     // start each ping scheduler
300     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
301         scheduler->start_pinging();
302 }
303
304 void stop_pingers(
305         const PingSchedulerList &scheduler_list
306 )
307 {
308     // Stop each ping scheduler
309     GlobalLogger.info() << "Telling all pingers to stop";
310     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
311     {
312         scheduler->stop_pinging();
313     }
314
315     IcmpPacketDistributor::clean_up_all();
316 }
317
318
319 // the one instance of signal_data_struct
320 signal_data_struct signal_data;
321
322
323 /// registered as signal handler; just sets signal_data.signaled_flag
324 void signal_handler_int(int param)
325 {
326     signal_data.signaled_flag_int = 1;
327 }
328 void signal_handler_term(int param)
329 {
330     signal_data.signaled_flag_term = 1;
331 }
332 void signal_handler_usr1(int param)
333 {
334     signal_data.signaled_flag_usr1 = 1;
335 }
336 void signal_handler_usr2(int param)
337 {
338     signal_data.signaled_flag_usr2 = 1;
339 }
340
341
342 /// called regularly from io_service; checks signal_data.signal_flag
343 void signal_checker( const boost::system::error_code &error )
344 {
345     bool want_stop = false;
346
347     if ( error )
348     {   // there was an error in the timer
349         if ( error ==  boost::asio::error::operation_aborted )
350         {
351             GlobalLogger.error() << "Signal check timer was cancelled! Stopping io_service" << endl;
352             want_stop = true;
353         }
354         else
355         {
356             GlobalLogger.error() << "Signal check timer handler received error code " << error
357                 << "! Stopping io_service" << endl;
358             want_stop = true;
359         }
360     }
361     else {
362         if ( signal_data.signaled_flag_int )
363         {
364             signal_data.signaled_flag_int = 0;
365             GlobalLogger.notice() << "Received signal SIGINT --> will stop" << endl;
366             want_stop = true;
367         }
368         else if ( signal_data.signaled_flag_term )
369         {
370             signal_data.signaled_flag_term = 0;
371             GlobalLogger.notice() << "Received signal SIGTERM --> will stop" << endl;
372             want_stop = true;
373         }
374         else if ( signal_data.signaled_flag_usr1 )
375         {
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();
381         }
382         else if ( signal_data.signaled_flag_usr2 )
383         {
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() << ")";
388         }
389     }
390
391     if ( want_stop )
392     {   // interrupt infinite loop in main and asio event loop
393         signal_data.stopped = true;
394         signal_data.io_service->stop();
395     }
396     else
397     {   // re-schedule timer
398         signal_data.check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
399         signal_data.check_timer->async_wait( signal_checker );
400     }
401 }
402
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 )
405 {
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;
411
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)) );
422
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 ) );
425
426     // remember the io_service and the timer
427     signal_data.io_service = io_service;
428     signal_data.check_timer = check_timer;
429
430     // set the timer
431     check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
432     check_timer->async_wait( signal_checker );
433     GlobalLogger.debug() << "signal timer set" << endl;
434 }
435
436 /// reset handlers to the ones saved in install_signal_handlers
437 void reset_signal_handlers()
438 {
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);
451
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)) );
457 }
458
459
460 int main( int argc, const char *argv[] )
461 {
462     init_logger();
463     GlobalLogger.debug() << "logger initiated with default config";
464
465     PingSchedulerList scheduler_list;
466     IoServiceItem io_service;
467     int ret_code = 0;
468     unsigned n_exceptions = 0;
469     unsigned max_exceptions = 0;
470
471     try
472     {
473         GetConfigReturnType success_and_config = get_configuration( argc, argv );
474         ConfigurationItem configuration = success_and_config.second;
475
476         if ( configuration->get_print_version() )     // do this even if parsing of config failed
477         {
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;
481             return 0;
482         }
483
484         if ( ! success_and_config.first )
485         {
486             GlobalLogger.error() << "Could not read/parse configuration!";
487             GlobalLogger.debug() << "Return 1 immediately" << endl;
488             return 1;
489         }
490         GlobalLogger.debug() << "Start setup" << endl;
491
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;
495
496         set_log_output( configuration );
497         GlobalLogger.notice() << "started";
498
499         bool daemon_mode = configuration->get_daemon();
500         if ( daemon_mode )
501         {
502             I2n::Daemon::daemonize();
503         }
504
505         LinkStatusItem status_notifier = get_status_notifier( configuration );
506
507         IoServiceItem io_service_temp( new boost::asio::io_service() );
508         io_service_temp.swap( io_service );
509         io_service_temp.reset();
510
511         // create Dns master
512         boost::asio::ip::address name_server_ip =
513                                     boost::asio::ip::address::from_string(
514                                             configuration->get_nameserver() );
515
516         int max_recursion_count = 10;
517         DnsMaster::create_master(
518                            io_service,
519                            name_server_ip,
520                            configuration->get_resolved_ip_ttl_threshold(),
521                            configuration->get_min_time_between_resolves(),
522                            configuration->get_max_address_resolution_attempts(),
523                            max_recursion_count,
524                            configuration->get_dns_cache_file() );
525
526         init_pingers( io_service, configuration, status_notifier, &scheduler_list );
527
528         install_signal_handlers( io_service, log_level );
529
530         start_pingers( scheduler_list );
531     }
532     catch ( const std::exception &ex )
533     {
534         GlobalLogger.error() << "Uncaught exception. " << ex.what() << endl;
535         ret_code = 2;
536         ++n_exceptions;
537     }
538     catch (...) {
539         GlobalLogger.error() << "Caught unknown exception!" << endl;
540         ret_code = 3;
541         ++n_exceptions;
542     }
543
544     if ( ret_code == 0 )
545     {
546         GlobalLogger.info() << "starting io_service main loop" << endl;
547
548         if (max_exceptions > 0)
549             GlobalLogger.warning() << "Limited number of acceptable exceptions,"
550                                    << " this is a debugging option!";
551
552         // call boost::asio main event loop, catching exceptions
553         while ( !signal_data.stopped )
554         {
555             try
556             {
557                 io_service->run();
558             }
559             catch ( const std::exception &ex )
560             {
561                 ++n_exceptions;
562                 GlobalLogger.error() << "Caught exception, will continue. " << ex.what() << endl;
563             }
564             catch (...) {
565                 ++n_exceptions;
566                 GlobalLogger.error() << "Caught unknown exception, will continue!" << endl;
567             }
568
569             if (max_exceptions > 0 && n_exceptions >= max_exceptions)
570             {
571                 GlobalLogger.info() << "reached max number of exceptions allowed in main loop" << endl;
572                 io_service->stop();
573                 signal_data.stopped = true;
574                 break;
575             }
576             else if ( signal_data.stopped )
577                 GlobalLogger.info() << "exiting io_service main loop" << endl;
578                 // ( io_service->stop() has been called already in signal handler)
579             else
580                 GlobalLogger.info() << "continuing io_service main loop" << endl;
581         }
582     }
583
584     // clean up
585     try
586     {
587         GlobalLogger.info() << "Cleaning up" << endl;
588         stop_pingers( scheduler_list );
589         reset_signal_handlers();
590     }
591     catch ( const std::exception &ex )
592     {
593         GlobalLogger.error() << "Uncaught exception while cleaning up: " << ex.what() << endl;
594         ret_code += 4;
595     }
596     catch (...) {
597         GlobalLogger.error() << "Caught unknown exception while cleaning up!" << endl;
598         ret_code += 8;
599     }
600
601     GlobalLogger.notice() << "Pingcheck done " << endl;
602     GlobalLogger.debug() << "In main loop, had " << n_exceptions << " exception[s]" << endl;
603     return ret_code;
604 }