72157ff2872cc8530c11a67dc89f9f5a6c4a6b3a
[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 bool 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_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;
177         break;
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;
185         break;
186     case LogOutput_FILE:
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;
195         break;
196     default:
197         GlobalLogger.error() << "Unknown log output target!" << endl;
198         break;
199     }
200 }
201
202 /**
203  * @brief calculate delay between pingers to evenly distribute them in time
204  *
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).
209  *
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.
212  *
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
216  */
217 DelayMap calc_pinger_delays(const HostList &hosts)
218 {
219     // first step: count number of hosts with same intervals
220     DelayMap delay_shifts;
221     int curr_interval;
222     BOOST_FOREACH( const HostItem &host, hosts )
223     {
224         curr_interval = host->get_interval_in_sec();
225         if (! curr_interval)
226             delay_shifts[curr_interval] = 1.0f;
227         else
228             delay_shifts[curr_interval] += 1.0f;
229     }
230
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;
237
238     return delay_shifts;
239 }
240
241 bool init_pingers(
242         const IoServiceItem io_service,
243         const ConfigurationItem &configuration,
244         const LinkStatusItem &status_notifier,
245         PingSchedulerList *scheduler_list
246 )
247 {
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();
251
252     // remove some hosts at random
253     configuration->randomize_hosts();
254
255     // calculate delays between pingers of same interval
256     DelayMap delay_shifts = calc_pinger_delays(configuration->get_hosts());
257
258     // setup memory for assigned delays
259     DelayMap delays;
260     BOOST_FOREACH( IntervalCountPair interval_and_delay, delay_shifts )
261         delays[interval_and_delay.first] = 0.0f;
262
263     HostList hosts = configuration->get_hosts();
264
265     if (hosts.empty())
266         return false;
267
268     BOOST_FOREACH( const HostItem &host, hosts )
269     {
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();
278
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
284
285         PingSchedulerItem scheduler(
286                 new PingScheduler(
287                         io_service,
288                         network_interface,
289                         destination_address,
290                         destination_port,
291                         protocol_list,
292                         ping_interval_in_sec,
293                         ping_fail_limit,
294                         ping_reply_timeout,
295                         status_notifier,
296                         current_delay,
297                         n_parallel_pings,
298                         parallel_ping_delay
299                 )
300         );
301         scheduler_list->push_back( scheduler );
302     }
303
304     return true;
305 }
306
307 void start_pingers(
308         const PingSchedulerList &scheduler_list
309 )
310 {
311     // start each ping scheduler
312     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
313         scheduler->start_pinging();
314 }
315
316 void stop_pingers(
317         const PingSchedulerList &scheduler_list
318 )
319 {
320     // Stop each ping scheduler
321     GlobalLogger.info() << "Telling all pingers to stop";
322     BOOST_FOREACH( const PingSchedulerItem &scheduler, scheduler_list )
323     {
324         scheduler->stop_pinging();
325     }
326
327     IcmpPacketDistributor::clean_up_all();
328 }
329
330
331 // the one instance of signal_data_struct
332 signal_data_struct signal_data;
333
334
335 /// registered as signal handler; just sets signal_data.signaled_flag
336 void signal_handler_int(int param)
337 {
338     signal_data.signaled_flag_int = 1;
339 }
340 void signal_handler_term(int param)
341 {
342     signal_data.signaled_flag_term = 1;
343 }
344 void signal_handler_usr1(int param)
345 {
346     signal_data.signaled_flag_usr1 = 1;
347 }
348 void signal_handler_usr2(int param)
349 {
350     signal_data.signaled_flag_usr2 = 1;
351 }
352
353
354 /// called regularly from io_service; checks signal_data.signal_flag
355 void signal_checker( const boost::system::error_code &error )
356 {
357     bool want_stop = false;
358
359     if ( error )
360     {   // there was an error in the timer
361         if ( error ==  boost::asio::error::operation_aborted )
362         {
363             GlobalLogger.error() << "Signal check timer was cancelled! Stopping io_service" << endl;
364             want_stop = true;
365         }
366         else
367         {
368             GlobalLogger.error() << "Signal check timer handler received error code " << error
369                 << "! Stopping io_service" << endl;
370             want_stop = true;
371         }
372     }
373     else {
374         if ( signal_data.signaled_flag_int )
375         {
376             signal_data.signaled_flag_int = 0;
377             GlobalLogger.notice() << "Received signal SIGINT --> will stop" << endl;
378             want_stop = true;
379         }
380         else if ( signal_data.signaled_flag_term )
381         {
382             signal_data.signaled_flag_term = 0;
383             GlobalLogger.notice() << "Received signal SIGTERM --> will stop" << endl;
384             want_stop = true;
385         }
386         else if ( signal_data.signaled_flag_usr1 )
387         {
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();
393         }
394         else if ( signal_data.signaled_flag_usr2 )
395         {
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() << ")";
400         }
401     }
402
403     if ( want_stop )
404     {   // interrupt infinite loop in main and asio event loop
405         signal_data.stopped = true;
406         signal_data.io_service->stop();
407     }
408     else
409     {   // re-schedule timer
410         signal_data.check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
411         signal_data.check_timer->async_wait( signal_checker );
412     }
413 }
414
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 )
417 {
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;
423
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)) );
434
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 ) );
437
438     // remember the io_service and the timer
439     signal_data.io_service = io_service;
440     signal_data.check_timer = check_timer;
441
442     // set the timer
443     check_timer->expires_from_now( SIGNAL_CHECK_INTERVAL );
444     check_timer->async_wait( signal_checker );
445     GlobalLogger.debug() << "signal timer set" << endl;
446 }
447
448 /// reset handlers to the ones saved in install_signal_handlers
449 void reset_signal_handlers()
450 {
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);
463
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)) );
469 }
470
471
472 int main( int argc, const char *argv[] )
473 {
474     init_logger();
475     GlobalLogger.debug() << "logger initiated with default config";
476
477     PingSchedulerList scheduler_list;
478     IoServiceItem io_service;
479     int ret_code = 0;
480     unsigned n_exceptions = 0;
481     unsigned max_exceptions = 0;
482
483     try
484     {
485         GetConfigReturnType success_and_config = get_configuration( argc, argv );
486         ConfigurationItem configuration = success_and_config.second;
487
488         if ( configuration->get_print_version() )     // do this even if parsing of config failed
489         {
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__
497                  << endl;
498             return 0;
499         }
500
501         if ( ! success_and_config.first )
502         {
503             GlobalLogger.error() << "Could not read/parse configuration!";
504             GlobalLogger.debug() << "Return 1 immediately" << endl;
505             return 1;
506         }
507         GlobalLogger.debug() << "Start setup" << endl;
508
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;
512
513         set_log_output( configuration );
514         GlobalLogger.notice() << "started pingcheck version "
515                  << VERSION_STRING << "." << VERSION_REVISION_STRING
516                  << " build " << __DATE__
517                  << endl;
518
519         bool daemon_mode = configuration->get_daemon();
520         if ( daemon_mode )
521         {
522             I2n::Daemon::daemonize();
523         }
524
525         LinkStatusItem status_notifier = get_status_notifier( configuration );
526
527         IoServiceItem io_service_temp( new boost::asio::io_service() );
528         io_service_temp.swap( io_service );
529         io_service_temp.reset();
530
531         // create Dns master
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(
537                            io_service,
538                            name_server_ip,
539                            configuration->get_resolved_ip_ttl_threshold(),
540                            configuration->get_min_time_between_resolves(),
541                            configuration->get_max_address_resolution_attempts(),
542                            max_recursion_count,
543                            configuration->get_dns_cache_file() );
544
545         if ( !init_pingers(io_service, configuration, status_notifier,
546                            &scheduler_list) )
547         {
548             GlobalLogger.error() << "Could not initialize pingers or no hosts "
549                 << "given to ping --> exit";
550             return 2;
551         }
552
553         install_signal_handlers( io_service, log_level );
554
555         start_pingers( scheduler_list );
556     }
557     catch ( const std::exception &ex )
558     {
559         GlobalLogger.error() << "Uncaught exception. " << ex.what() << endl;
560         ret_code = 3;
561         ++n_exceptions;
562     }
563     catch (...) {
564         GlobalLogger.error() << "Caught unknown exception!" << endl;
565         ret_code = 4;
566         ++n_exceptions;
567     }
568
569     if ( ret_code == 0 )
570     {
571         GlobalLogger.info() << "starting io_service main loop" << endl;
572
573         if (max_exceptions > 0)
574             GlobalLogger.warning() << "Limited number of acceptable exceptions,"
575                                    << " this is a debugging option!";
576
577         // call boost::asio main event loop, catching exceptions
578         while ( !signal_data.stopped )
579         {
580             try
581             {
582                 io_service->run();
583             }
584             catch ( const std::exception &ex )
585             {
586                 ++n_exceptions;
587                 GlobalLogger.error() << "Caught exception, will continue. " << ex.what() << endl;
588             }
589             catch (...) {
590                 ++n_exceptions;
591                 GlobalLogger.error() << "Caught unknown exception, will continue!" << endl;
592             }
593
594             if (max_exceptions > 0 && n_exceptions >= max_exceptions)
595             {
596                 GlobalLogger.info() << "reached max number of exceptions allowed in main loop" << endl;
597                 io_service->stop();
598                 signal_data.stopped = true;
599                 break;
600             }
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)
604             else
605                 GlobalLogger.info() << "continuing io_service main loop" << endl;
606         }
607     }
608
609     // clean up
610     try
611     {
612         GlobalLogger.info() << "Cleaning up" << endl;
613         stop_pingers( scheduler_list );
614         reset_signal_handlers();
615     }
616     catch ( const std::exception &ex )
617     {
618         GlobalLogger.error() << "Uncaught exception while cleaning up: " << ex.what() << endl;
619         ret_code += 16;
620     }
621     catch (...) {
622         GlobalLogger.error() << "Caught unknown exception while cleaning up!" << endl;
623         ret_code += 32;
624     }
625
626     GlobalLogger.notice() << "Pingcheck done " << endl;
627     GlobalLogger.debug() << "In main loop, had " << n_exceptions << " exception[s]" << endl;
628     return ret_code;
629 }