46d0f589b9a78eb2de868d972e41aeb8858826f3
[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     switch (log_output)
167     {
168     case LogOutput_SYSLOG:
169         GlobalLogger.info() << "Setting log output target to syslog" << endl;
170         I2n::Logger::enable_syslog(true);
171         I2n::Logger::enable_stderr_log(false);
172         I2n::Logger::enable_log_file(false);
173         GlobalLogger.info() << "Set log output target to syslog" << endl;
174         break;
175     case LogOutput_TERMINAL:
176         GlobalLogger.info() << "Setting log output target to terminal" << endl;
177         I2n::Logger::enable_syslog(false);
178         I2n::Logger::enable_stderr_log(true);
179         I2n::Logger::enable_log_file(false);
180         GlobalLogger.info() << "Set log output target to terminal" << endl;
181         GlobalLogger.info() << "(check syslog for earlier messages)" << endl;
182         break;
183     default:
184         GlobalLogger.error() << "Unknown log output target!" << endl;
185         break;
186     }
187 }
188
189 /**
190  * @brief calculate delay between pingers to evenly distribute them in time
191  *
192  * If there are many pingers with same interval, will get bursts of pings
193  * and none in-between. This function calculates delays for large numbers
194  * of hosts with same ping intervals, to distribute them as evenly as
195  * possible, right from the start (might diverge over time, anyway).
196  *
197  * Will not do much good for pingers with many different intervals, but
198  *   then is not required anyway and does no(t much) harm.
199  *
200  * Called by init_pingers with
201  * @param hosts list of hosts as obtained from configuration
202  * @returns a map from ping interval to delay between pingers of that interval
203  */
204 DelayMap calc_pinger_delays(const HostList &hosts)
205 {
206     // first step: count number of hosts with same intervals
207     DelayMap delay_shifts;
208     int curr_interval;
209     BOOST_FOREACH( const HostItem &host, hosts )
210     {
211         curr_interval = host->get_interval_in_sec();
212         if (! curr_interval)
213             delay_shifts[curr_interval] = 1.0f;
214         else
215             delay_shifts[curr_interval] += 1.0f;
216     }
217
218     // second step: divide intervals by counts, round to int
219     // --> for 18 pingers with a 30s interval, get 30s/18 = 1.66667
220     BOOST_FOREACH( IntervalCountPair interval_and_count, delay_shifts )
221         delay_shifts[interval_and_count.first] =
222                 boost::numeric_cast<float>(interval_and_count.first) /
223                 interval_and_count.second;
224
225     return delay_shifts;
226 }
227
228 void init_pingers(
229         const IoServiceItem io_service,
230         const ConfigurationItem &configuration,
231         const LinkStatusItem &status_notifier,
232         PingSchedulerList *scheduler_list
233 )
234 {
235     string default_network_interface = configuration->get_source_network_interface();
236     int ping_fail_limit = configuration->get_ping_fail_limit();
237     int ping_reply_timeout = configuration->get_ping_reply_timeout();
238     int resolved_ip_ttl_threshold = configuration->get_resolved_ip_ttl_threshold();
239
240     // remove some hosts at random
241     configuration->randomize_hosts();
242
243     // calculate delays between pingers of same interval
244     DelayMap delay_shifts = calc_pinger_delays(configuration->get_hosts());
245
246     // setup memory for assigned delays
247     DelayMap delays;
248     BOOST_FOREACH( IntervalCountPair interval_and_delay, delay_shifts )
249         delays[interval_and_delay.first] = 0.0f;
250
251     HostList hosts = configuration->get_hosts();
252     BOOST_FOREACH( const HostItem &host, hosts )
253     {
254         string destination_address = host->get_address();
255         uint16_t destination_port = host->get_port();
256         string host_network_interface = host->get_source_network_interface();
257         string network_interface = ( host_network_interface == "default" ) ?
258                 default_network_interface :
259                 host_network_interface;
260         PingProtocolList protocol_list = host->get_ping_protocol_list();
261         int ping_interval_in_sec = host->get_interval_in_sec();
262
263         // get delay for this scheduler and update assigned delays
264         int current_delay = boost::math::iround(delays[ping_interval_in_sec]);
265         delays[ping_interval_in_sec] += delay_shifts[ping_interval_in_sec];
266
267         PingSchedulerItem scheduler(
268                 new PingScheduler(
269                         io_service,
270                         network_interface,
271                         destination_address,
272                         destination_port,
273                         protocol_list,
274                         ping_interval_in_sec,
275                         ping_fail_limit,
276                         ping_reply_timeout,
277                         resolved_ip_ttl_threshold,
278                         status_notifier,
279                         current_delay
280                 )
281         );
282         scheduler_list->push_back( scheduler );
283     }
284 }
285
286 void start_pingers(
287         const PingSchedulerList &scheduler_list
288 )
289 {
290     // start each ping scheduler
291     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
292         scheduler->start_pinging();
293 }
294
295 void stop_pingers(
296         const PingSchedulerList &scheduler_list
297 )
298 {
299     // Stop each ping scheduler
300     GlobalLogger.info() << "Telling all pingers to stop";
301     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
302     {
303         scheduler->stop_pinging();
304     }
305
306     IcmpPacketDistributor::clean_up_all();
307 }
308
309
310 // the one instance of signal_data_struct
311 signal_data_struct signal_data;
312
313
314 /// registered as signal handler; just sets signal_data.signaled_flag
315 void signal_handler_int(int param)
316 {
317     signal_data.signaled_flag_int = 1;
318 }
319 void signal_handler_term(int param)
320 {
321     signal_data.signaled_flag_term = 1;
322 }
323 void signal_handler_usr1(int param)
324 {
325     signal_data.signaled_flag_usr1 = 1;
326 }
327 void signal_handler_usr2(int param)
328 {
329     signal_data.signaled_flag_usr2 = 1;
330 }
331
332
333 /// called regularly from io_service; checks signal_data.signal_flag
334 void signal_checker( const boost::system::error_code &error )
335 {
336     bool want_stop = false;
337
338     if ( error )
339     {   // there was an error in the timer
340         if ( error ==  boost::asio::error::operation_aborted )
341         {
342             GlobalLogger.error() << "Signal check timer was cancelled! Stopping io_service" << endl;
343             want_stop = true;
344         }
345         else
346         {
347             GlobalLogger.error() << "Signal check timer handler received error code " << error
348                 << "! Stopping io_service" << endl;
349             want_stop = true;
350         }
351     }
352     else {
353         if ( signal_data.signaled_flag_int )
354         {
355             signal_data.signaled_flag_int = 0;
356             GlobalLogger.notice() << "Received signal SIGINT --> will stop" << endl;
357             want_stop = true;
358         }
359         else if ( signal_data.signaled_flag_term )
360         {
361             signal_data.signaled_flag_term = 0;
362             GlobalLogger.notice() << "Received signal SIGTERM --> will stop" << endl;
363             want_stop = true;
364         }
365         else if ( signal_data.signaled_flag_usr1 )
366         {
367             signal_data.signaled_flag_usr1 = 0;
368             int new_log_level = I2n::Logger::get_log_level()+1;
369             I2n::Logger::set_log_level( new_log_level );
370             GlobalLogger.info() << "Received SIGUSR1 -- increased log level to "
371                 << I2n::Logger::get_log_level_string();
372         }
373         else if ( signal_data.signaled_flag_usr2 )
374         {
375             signal_data.signaled_flag_usr2 = 0;
376             I2n::Logger::set_log_level( signal_data.config_log_level );
377             GlobalLogger.info() << "Received SIGUSR2 -- reset log level to normal ("
378                 << I2n::Logger::get_log_level_string() << ")";
379         }
380     }
381
382     if ( want_stop )
383     {   // interrupt infinite loop in main and asio event loop
384         signal_data.stopped = true;
385         signal_data.io_service->stop();
386     }
387     else
388     {   // re-schedule timer
389         signal_data.check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
390         signal_data.check_timer->async_wait( signal_checker );
391     }
392 }
393
394 /// register own signal handlers; see reset_signal_handlers for undo
395 void install_signal_handlers( const IoServiceItem io_service, const int config_log_level )
396 {
397     signal_data.signaled_flag_int = 0;
398     signal_data.signaled_flag_term = 0;
399     signal_data.signaled_flag_usr1 = 0;
400     signal_data.signaled_flag_usr2 = 0;
401     signal_data.config_log_level = config_log_level;
402
403     // install own signal handlers
404     signal_data.old_handler_int  = signal(SIGINT,  signal_handler_int);
405     signal_data.old_handler_term = signal(SIGTERM, signal_handler_term);
406     signal_data.old_handler_usr1 = signal(SIGUSR1, signal_handler_usr1);
407     signal_data.old_handler_usr2 = signal(SIGUSR2, signal_handler_usr2);
408     if (  signal_data.old_handler_int  == SIG_ERR ||
409           signal_data.old_handler_term == SIG_ERR ||
410           signal_data.old_handler_usr1 == SIG_ERR ||
411           signal_data.old_handler_usr2 == SIG_ERR )
412         throw runtime_error( string("Failed to install signal handler: ") + string(strerror(errno)) );
413
414     // create a timer and a shared pointer to it, so it does not get out of scope
415     TimerItem check_timer( new boost::asio::deadline_timer( *io_service ) );
416
417     // remember the io_service and the timer
418     signal_data.io_service = io_service;
419     signal_data.check_timer = check_timer;
420
421     // set the timer
422     check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
423     check_timer->async_wait( signal_checker );
424     GlobalLogger.debug() << "signal timer set" << endl;
425 }
426
427 /// reset handlers to the ones saved in install_signal_handlers
428 void reset_signal_handlers()
429 {
430     void (*old_handler_int)(int) = 0;
431     void (*old_handler_term)(int) = 0;
432     void (*old_handler_usr1)(int) = 0;
433     void (*old_handler_usr2)(int) = 0;
434     if (signal_data.old_handler_int != 0 )
435         old_handler_int  = signal(SIGINT , signal_data.old_handler_int);
436     if (signal_data.old_handler_term != 0 )
437         old_handler_term = signal(SIGTERM, signal_data.old_handler_term);
438     if (signal_data.old_handler_usr1 != 0 )
439         old_handler_usr1 = signal(SIGUSR1, signal_data.old_handler_usr1);
440     if (signal_data.old_handler_usr2 != 0 )
441         old_handler_usr2 = signal(SIGUSR2, signal_data.old_handler_usr2);
442
443     if (  old_handler_int  == SIG_ERR ||
444           old_handler_term == SIG_ERR ||
445           old_handler_usr1 == SIG_ERR ||
446           old_handler_usr2 == SIG_ERR )
447         throw runtime_error( string("Failed to reset signal handler: ") + string(strerror(errno)) );
448 }
449
450
451 int main( int argc, const char *argv[] )
452 {
453     init_logger();
454     GlobalLogger.debug() << "logger initiated with default config";
455
456     PingSchedulerList scheduler_list;
457     IoServiceItem io_service;
458     int ret_code = 0;
459     unsigned n_exceptions = 0;
460     unsigned max_exceptions = 1;
461
462     try
463     {
464         GetConfigReturnType success_and_config = get_configuration( argc, argv );
465         ConfigurationItem configuration = success_and_config.second;
466
467         if ( configuration->get_print_version() )     // do this even if parsing of config failed
468         {
469             GlobalLogger.debug() << "Printing version info (" << VERSION_STRING << ") and exit" << endl;
470             cout << PROJECT_NAME << " version " << VERSION_STRING << "."
471                  << VERSION_REVISION_STRING << " build " << __DATE__ << endl;
472             return 0;
473         }
474
475         if ( ! success_and_config.first )
476         {
477             GlobalLogger.error() << "Could not read/parse configuration!";
478             GlobalLogger.debug() << "Return 1 immediately" << endl;
479             return 1;
480         }
481         GlobalLogger.debug() << "Start setup" << endl;
482
483         int log_level = configuration->get_log_level();
484         I2n::Logger::set_log_level( log_level );
485         GlobalLogger.info() << "Set LogLevel to " << I2n::Logger::get_log_level_string() << endl;
486
487         set_log_output( configuration );
488         GlobalLogger.notice() << "started";
489
490         bool daemon_mode = configuration->get_daemon();
491         if ( daemon_mode )
492         {
493             I2n::Daemon::daemonize();
494         }
495
496         LinkStatusItem status_notifier = get_status_notifier( configuration );
497
498         IoServiceItem io_service_temp( new boost::asio::io_service() );
499         io_service_temp.swap( io_service );
500         io_service_temp.reset();
501
502         // create Dns master
503         boost::asio::ip::address name_server_ip =
504                                     boost::asio::ip::address::from_string(
505                                             configuration->get_nameserver() );
506
507         DnsMaster::create_master(
508                            io_service,
509                            name_server_ip,
510                            configuration->get_resolved_ip_ttl_threshold(),
511                            configuration->get_max_address_resolution_attempts(),
512                            configuration->get_dns_cache_file() );
513
514         init_pingers( io_service, configuration, status_notifier, &scheduler_list );
515
516         install_signal_handlers( io_service, log_level );
517
518         start_pingers( scheduler_list );
519     }
520     catch ( const std::exception &ex )
521     {
522         GlobalLogger.error() << "Uncaught exception. " << ex.what() << endl;
523         ret_code = 2;
524         ++n_exceptions;
525     }
526     catch (...) {
527         GlobalLogger.error() << "Caught unknown exception!" << endl;
528         ret_code = 3;
529         ++n_exceptions;
530     }
531
532     if ( ret_code == 0 )
533     {
534         GlobalLogger.info() << "starting io_service main loop" << endl;
535
536         if (max_exceptions > 0)
537             GlobalLogger.warning() << "Limited number of acceptable exceptions,"
538                                    << " this is a debugging option!";
539
540         // call boost::asio main event loop, catching exceptions
541         while ( !signal_data.stopped )
542         {
543             try
544             {
545                 io_service->run();
546             }
547             catch ( const std::exception &ex )
548             {
549                 ++n_exceptions;
550                 GlobalLogger.error() << "Caught exception, will continue. " << ex.what() << endl;
551             }
552             catch (...) {
553                 ++n_exceptions;
554                 GlobalLogger.error() << "Caught unknown exception, will continue!" << endl;
555             }
556
557             if (max_exceptions > 0 && n_exceptions >= max_exceptions)
558             {
559                 GlobalLogger.info() << "reached max number of exceptions allowed in main loop" << endl;
560                 io_service->stop();
561                 signal_data.stopped = true;
562                 break;
563             }
564             else if ( signal_data.stopped )
565                 GlobalLogger.info() << "exiting io_service main loop" << endl;
566                 // ( io_service->stop() has been called already in signal handler)
567             else
568                 GlobalLogger.info() << "continuing io_service main loop" << endl;
569         }
570     }
571
572     // clean up
573     try
574     {
575         GlobalLogger.info() << "Cleaning up" << endl;
576         stop_pingers( scheduler_list );
577         reset_signal_handlers();
578     }
579     catch ( const std::exception &ex )
580     {
581         GlobalLogger.error() << "Uncaught exception while cleaning up: " << ex.what() << endl;
582         ret_code += 4;
583     }
584     catch (...) {
585         GlobalLogger.error() << "Caught unknown exception while cleaning up!" << endl;
586         ret_code += 8;
587     }
588
589     GlobalLogger.notice() << "Pingcheck done " << endl;
590     GlobalLogger.debug() << "In main loop, had " << n_exceptions << " exception[s]" << endl;
591     return ret_code;
592 }