simplified dns (no self-made recursion); merge PingScheduler and PingRotate; make...
[pingcheck] / src / host / pingscheduler.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 "host/pingscheduler.h"
21
22 #include <iostream>
23 #include <limits>
24
25 #include <boost/bind.hpp>
26
27 #include <logfunc.hpp>
28
29 #include "boost_assert_handler.h"
30 #include "host/pingerfactory.h"
31 #include "icmp/icmppinger.h"
32 #include "link/linkstatus.h"
33
34 using namespace std;
35 using boost::asio::io_service;
36 using boost::bind;
37 using boost::date_time::time_resolution_traits_adapted64_impl;
38 using boost::posix_time::microsec_clock;
39 using boost::posix_time::ptime;
40 using boost::posix_time::seconds;
41 using boost::shared_ptr;
42 using I2n::Logger::GlobalLogger;
43
44 //-----------------------------------------------------------------------------
45 // PingScheduler
46 //-----------------------------------------------------------------------------
47
48 /**
49  * @brief Parameterized constructor.
50  *
51  * @param io_serv The one @c io_serv object that controls async processing
52  * @param network_interface The name of the network interface originating the pings.
53  * @param destination_address The remote address to ping.
54  * @param destination_port The remote port to ping.
55  * @param ping_protocol_list A list of protocols to use.
56  * @param ping_interval_in_sec Amount of time between each ping.
57  * @param ping_fail_percentage_limit Maximum amount of pings that can fail.
58  * @param link_analyzer The object to monitor the link status.
59  * @param first_delay Delay in seconds from start_pinging to first ping attempt
60  */
61 PingScheduler::PingScheduler(
62         const IoServiceItem io_serv,
63         const string &network_interface,
64         const string &destination_address,
65         const uint16_t destination_port,
66         const PingProtocolList &ping_protocol_list,
67         const long ping_interval_in_sec,
68         const int ping_fail_percentage_limit,
69         const int ping_reply_timeout,
70         const int resolved_ip_ttl_threshold,
71         LinkStatusItem link_analyzer,
72         const int first_delay
73
74 ) :
75     IoService( io_serv ),
76     NetworkInterfaceName( network_interface ),
77     Resolver(),
78     DestinationAddress( destination_address ),
79     DestinationPort( destination_port ),
80     NextPingTimer( *io_serv ),
81     TimeSentLastPing( microsec_clock::universal_time() ),
82     PingIntervalInSec( ping_interval_in_sec ),
83     HostAnalyzer( destination_address, ping_fail_percentage_limit, link_analyzer ),
84     FirstDelay( first_delay ),
85     Ping()
86 {
87     BOOST_ASSERT( !network_interface.empty() );
88     BOOST_ASSERT( !destination_address.empty() );
89     BOOST_ASSERT( ( 0 < destination_port ) &&
90                   ( destination_port < numeric_limits<uint16_t>::max() ) );
91     BOOST_ASSERT( 0 < ping_interval_in_sec );
92     BOOST_ASSERT( (0 <= ping_fail_percentage_limit) &&
93                   ( ping_fail_percentage_limit <= 100) );
94
95     std::stringstream temp;
96     temp << "PS(" << DestinationAddress << "): ";
97     LogPrefix = temp.str();
98
99     // fill circular buffer with protocols
100     BOOST_FOREACH( const PingProtocol &prot, protocol_list )
101         ProtocolRotate.push_back(prot);
102
103     init_ping_protocol();
104 }
105
106 /**
107  * @brief Destructor.
108  */
109 PingScheduler::~PingScheduler()
110 {
111 }
112
113 void PingScheduler::stop_pinging()
114 {
115     // stop pinger, which will probably call ping_done_handler --> re-new NextPingTimer
116     GlobalLogger.debug() << "scheduler: stop pinging" << endl;
117     Ping->stop_pinging();
118     Resolver->cancel_resolve();
119
120     // now cancel the own timer
121     GlobalLogger.debug() << "scheduler: cancel timer" << endl;
122     NextPingTimer.cancel();
123 }
124
125 /**
126  * @brief Start into infinite loop of calls to ping
127  *
128  * Does not start yet but set NextPingTimer (possibly to 0), so action starts
129  *   when io_service is started
130  */
131 void PingScheduler::start_pinging()
132 {
133     if ( FirstDelay > 0 )
134         GlobalLogger.info() << "Delaying first ping by " << FirstDelay << "s";
135     else
136         GlobalLogger.info() << "Schedule ping as soon as possible";
137
138     (void) NextPingTimer.expires_from_now( seconds( FirstDelay ) );
139     NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
140                                           boost::asio::placeholders::error ) );
141 }
142
143
144 /**
145  * @brief call Ping::ping and schedule a call to ping_done_handler when finished
146  */
147 void PingScheduler::ping(const boost::system::error_code &error)
148 {
149     if ( error )
150     {   // get here, e.g. by NextPingTimer.cancel in stop_pinging
151         if ( error ==  boost::asio::error::operation_aborted )
152             GlobalLogger.error() << "Timer for ping was cancelled! "
153                                  << "Stopping" << endl;
154         else
155             GlobalLogger.error() << "Received error " << error
156                                  << " waiting for ping! Stopping"
157                                  << endl;
158         return;
159     }
160
161     // ping as soon as dns is ready
162     WantToPing = true;
163     try_to_ping();
164 }
165
166
167 void PingRotate::try_to_ping()
168 {
169     if ( !WantToPing )
170     {
171         GlobalLogger.info() << "PingRotate: not pinging yet (not requested to)";
172         return;
173     }
174     else if ( Resolver && Resolver->is_resolving() )
175     {
176         GlobalLogger.info() << "PingRotate: not pinging yet (DNS not finished)";
177         return;
178     }
179     else if ( !Resolver )
180         // should not happen, but check anyway
181         GlobalLogger.warning() << LogPrefix << "Have no resolver!";
182
183     GlobalLogger.info() << "PingRotate: start ping";
184     WantToPing = false;
185
186     Ping->ping(
187             Resolver->get_next_ip().get_ip(),
188             DestinationPort,
189             boost::bind(&PingScheduler::ping_done_handler, this, _1)
190     );
191 }
192
193
194 //------------------------------------------------------------------------------
195 // Post Processing of Ping result
196 //------------------------------------------------------------------------------
197
198 /**
199  * @brief called when Ping::ping is done; calls functions to update
200  *   statistics, ping interval and elapsed time;
201  *   schedules a call to ping, thereby closing the loop
202  */
203 void PingScheduler::ping_done_handler( const bool ping_success )
204 {
205     // post-processing
206     // You must call these 3 methods exactly in this order
207     // TODO Fix this method, once it has a semantic dependency with the
208     // update_ping_statistics method, because it depends on the PingAnalyzer
209     // statistics to update the exceeded_ping_failed_limit
210     HostAnalyzer.update_ping_statistics( ping_success );
211     update_ping_interval();
212     update_ping_elapsed_time();
213
214     // get next protocol, possibly start resolving IPs
215     update_ping_protocol();
216
217     // schedule next ping
218     (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) );
219     NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
220                                          boost::asio::placeholders::error ) );
221 }
222
223 void PingScheduler::update_ping_interval()
224 {
225     // have to ping more often?
226     if ( HostAnalyzer.exceeded_ping_failed_limit() )
227     {
228         PingIntervalInSec.speed_up();
229
230         GlobalLogger.debug() << "- Speeding up ping interval to: " << PingIntervalInSec << "s"
231                 << endl;
232     }
233     else
234     {
235         PingIntervalInSec.back_to_original();
236
237         GlobalLogger.debug() << "- Stick to the original ping interval: " << PingIntervalInSec << "s"
238                 << endl;
239     }
240 }
241
242 void PingScheduler::update_ping_elapsed_time()
243 {
244     ptime now = microsec_clock::universal_time();
245     time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec =
246             (now - TimeSentLastPing).total_seconds();
247     GlobalLogger.debug() << "- Time elapsed since last ping: " << elapsed_time_in_sec << "s" << endl;
248
249     TimeSentLastPing = microsec_clock::universal_time();
250 }
251
252
253 //------------------------------------------------------------------------------
254 // Ping Protocol Rotation
255 //------------------------------------------------------------------------------
256
257 void PingRotate::init_ping_protocol()
258 {
259     get_next_ping_protocol();
260 }
261
262 void PingRotate::update_ping_protocol()
263 {
264     if ( can_change_ping_protocol() )
265     {
266         get_next_ping_protocol();
267     }
268 }
269
270 void PingRotate::get_next_ping_protocol()
271 {
272     PingProtocol ping_protocol = ProtocolRotate.front();
273     ProtocolRotate.pop_front();
274     ProtocolRotate.push_back(ping_protocol);
275
276     if (Ping)
277         Ping->stop_pinging();
278
279     Ping = PingerFactory::createPinger(ping_protocol, IoService,
280                                        NetworkInterfaceName, PingReplyTimeout);
281
282     update_dns_resolver( ping_protocol );
283 }
284
285 bool PingRotate::can_change_ping_protocol() const
286 {
287     // TODO can_change_ping_protocol() and get_next_ping_protocol() may be implemented in a Algorithm
288     // class that can be exchanged in this class to provide an algorithm neutral class
289     return true;
290 }
291
292 //------------------------------------------------------------------------------
293 // DNS host name resolution
294 //------------------------------------------------------------------------------
295 //
296 void PingRotate::update_dns_resolver( PingProtocol current_protocol )
297 {
298     if (Resolver && Resolver->is_resolving())
299     {
300         GlobalLogger.warning() << "Resolver still seems to be resolving "
301                                << "--> cancel!";
302         Resolver->cancel_resolve();
303     }
304
305     // DNS master caches created resolvers and resolved IPs, so this will
306     // probably just return an existing resolver with already resolved IPs for
307     // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
308     Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
309                                                            current_protocol);
310     // start resolving if no ips available
311     if ( Resolver->have_up_to_date_ip() )
312     {
313         if (!Resolver->is_resolving())
314             GlobalLogger.warning() << "PingRotate: have up to date IPs but "
315                 << "resolver seems to be resolving all the same... "
316                 << "Start pinging anyway!";
317         try_to_ping();
318     }
319     else
320         start_resolving_ping_address();
321 }
322
323 void PingRotate::start_resolving_ping_address()                                                         //lint !e1762
324 {
325     Resolver->async_resolve( boost::bind(&PingRotate::dns_resolve_callback,
326                                           this, _1, _2) );
327 }
328
329 void PingRotate::dns_resolve_callback(const bool was_success,
330                                       const int cname_count)
331 {
332     GlobalLogger.info() << "PingRotate: dns resolution finished "
333                         << "with success = " << was_success << " "
334                         << "and cname_count = " << cname_count;
335
336     if ( !was_success )
337     {   // host name resolution failed; try again bypassing first CNAME(s)
338         std::string skip_host = Resolver->get_skipper();
339
340         if (skip_host.empty())
341         {
342             GlobalLogger.notice() << LogPrefix << "DNS failed, "
343                 << "try anyway with cached data";
344             HostAnalyzer->set_resolved_ip_count(0);
345             try_to_ping();
346         }
347         else
348         {
349             GlobalLogger.notice() << LogPrefix << "DNS failed, "
350                 << "try again skipping a CNAME and resolving "
351                 << skip_host << " directly";
352             Resolver = DnsMaster::get_instance()
353                                ->get_resolver_for(skip_host, PingRotate.back());
354             start_resolving_ping_address();
355         }
356     }
357     else
358     {
359         HostAnalyzer->set_resolved_ip_count( Resolver->get_resolved_ip_count());
360         try_to_ping();
361     }
362 }