fixed 2 bugs and made clearer that Long-term timer in DnsResolver is not affected...
[pingcheck] / src / host / pingscheduler.cpp
CommitLineData
91fcc471
TJ
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
8f66f529 20#include "host/pingscheduler.h"
9c55ecd3 21
0d46491b 22#include <iostream>
1309d0e4 23#include <limits>
0d46491b 24
9c55ecd3 25#include <boost/bind.hpp>
26b0f687 26#include <boost/foreach.hpp>
0d46491b 27
301610ca
GMF
28#include <logfunc.hpp>
29
780b0bca 30#include "boost_assert_handler.h"
086e2cc0 31#include "host/pingerfactory.h"
26b0f687 32#include "dns/dnsmaster.h"
51cbc790 33#include "icmp/icmppinger.h"
72e54d1c 34#include "link/linkstatus.h"
ced28dc7 35
a7c2eb51 36using namespace std;
2bf8720f
GMF
37using boost::asio::io_service;
38using boost::bind;
101be5ce 39using boost::date_time::time_resolution_traits_adapted64_impl;
2bf8720f
GMF
40using boost::posix_time::microsec_clock;
41using boost::posix_time::ptime;
42using boost::posix_time::seconds;
e58d7507 43using boost::shared_ptr;
301610ca 44using I2n::Logger::GlobalLogger;
a7c2eb51 45
4c2a5ab5 46//-----------------------------------------------------------------------------
4bb97b45 47// PingScheduler
4c2a5ab5
GMF
48//-----------------------------------------------------------------------------
49
086e2cc0
GMF
50/**
51 * @brief Parameterized constructor.
52 *
ab2cb1ef 53 * @param io_serv The one @c io_serv object that controls async processing
c1abff61 54 * @param network_interface The name of the network interface sending the pings.
086e2cc0
GMF
55 * @param destination_address The remote address to ping.
56 * @param destination_port The remote port to ping.
57 * @param ping_protocol_list A list of protocols to use.
58 * @param ping_interval_in_sec Amount of time between each ping.
59 * @param ping_fail_percentage_limit Maximum amount of pings that can fail.
086e2cc0 60 * @param link_analyzer The object to monitor the link status.
365036be 61 * @param first_delay Delay in seconds from start_pinging to first ping attempt
086e2cc0 62 */
4bb97b45 63PingScheduler::PingScheduler(
ab2cb1ef 64 const IoServiceItem io_serv,
2bf8720f
GMF
65 const string &network_interface,
66 const string &destination_address,
238da857 67 const uint16_t destination_port,
fe6a2f80 68 const PingProtocolList &ping_protocol_list,
c15a722d 69 const long ping_interval_in_sec,
a341119a 70 const int ping_fail_percentage_limit,
079d19ab 71 const int ping_reply_timeout,
59733431
CH
72 LinkStatusItem link_analyzer,
73 const int first_delay
c5e4bfa1 74
e39cc3da 75) :
23f51766
CH
76 IoService( io_serv ),
77 NetworkInterfaceName( network_interface ),
23f51766
CH
78 DestinationAddress( destination_address ),
79 DestinationPort( destination_port ),
26b0f687
CH
80 Protocols( ping_protocol_list ),
81 ProtocolIter(),
82 PingIntervalInSec( ping_interval_in_sec ),
83 FirstDelay( first_delay ),
ab2cb1ef 84 NextPingTimer( *io_serv ),
e39cc3da 85 TimeSentLastPing( microsec_clock::universal_time() ),
26b0f687 86 PingReplyTimeout( ping_reply_timeout ),
c1abff61
CH
87 HostAnalyzer( destination_address, ping_fail_percentage_limit,
88 link_analyzer ),
26b0f687
CH
89 Resolver(),
90 Ping(),
91 WantToPing( false ),
92 LogPrefix(),
8d26221d 93 ContinueOnOutdatedIps( false )
ced28dc7 94{
475ad07c
GMF
95 BOOST_ASSERT( !network_interface.empty() );
96 BOOST_ASSERT( !destination_address.empty() );
23f51766
CH
97 BOOST_ASSERT( ( 0 < destination_port ) &&
98 ( destination_port < numeric_limits<uint16_t>::max() ) );
f71cb7e1 99 BOOST_ASSERT( 0 < ping_interval_in_sec );
23f51766
CH
100 BOOST_ASSERT( (0 <= ping_fail_percentage_limit) &&
101 ( ping_fail_percentage_limit <= 100) );
102
26b0f687 103 update_log_prefix();
23f51766
CH
104
105 init_ping_protocol();
2d591235 106}
ced28dc7 107
086e2cc0
GMF
108/**
109 * @brief Destructor.
110 */
4bb97b45 111PingScheduler::~PingScheduler()
2d591235 112{
ced28dc7
GMF
113}
114
c1d776ba 115void PingScheduler::stop_pinging()
ced28dc7 116{
c1abff61
CH
117 // stop pinger and resolver
118 GlobalLogger.debug() << LogPrefix << "scheduler: stop pinging";
5a9bc2d1 119 Ping->stop_pinging();
72be9e7d 120 cancel_resolve(true);
f076f8d4 121
c1abff61
CH
122 // now cancel the own timer in case that pinger cancelation called callback
123 GlobalLogger.debug() << LogPrefix << "scheduler: cancel timer";
f076f8d4 124 NextPingTimer.cancel();
c1d776ba
CH
125}
126
127/**
23f51766 128 * @brief Start into infinite loop of calls to ping
cad0b08d
CH
129 *
130 * Does not start yet but set NextPingTimer (possibly to 0), so action starts
131 * when io_service is started
c1d776ba
CH
132 */
133void PingScheduler::start_pinging()
134{
c1d776ba 135 if ( FirstDelay > 0 )
c1abff61
CH
136 GlobalLogger.info() << LogPrefix << "Delaying first ping by "
137 << FirstDelay << "s";
59733431 138 else
c1abff61 139 GlobalLogger.info() << LogPrefix << "Schedule ping as soon as possible";
cad0b08d
CH
140
141 (void) NextPingTimer.expires_from_now( seconds( FirstDelay ) );
23f51766 142 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
cad0b08d 143 boost::asio::placeholders::error ) );
09de3c4b
GMF
144}
145
4e91c69a 146
c1d776ba 147/**
23f51766 148 * @brief call Ping::ping and schedule a call to ping_done_handler when finished
c1d776ba 149 */
23f51766 150void PingScheduler::ping(const boost::system::error_code &error)
823623d9 151{
d26dce11 152 if ( error )
f076f8d4 153 { // get here, e.g. by NextPingTimer.cancel in stop_pinging
d26dce11 154 if ( error == boost::asio::error::operation_aborted )
c1abff61
CH
155 GlobalLogger.error() << LogPrefix << "Timer for ping was cancelled!"
156 << " --> Stopping";
d26dce11 157 else
c1abff61
CH
158 GlobalLogger.error() << LogPrefix << "Received error " << error
159 << " waiting for ping! Stopping";
d26dce11
CH
160 return;
161 }
162
23f51766
CH
163 // ping as soon as dns is ready
164 WantToPing = true;
8d26221d 165 ping_when_ready();
23f51766
CH
166}
167
c1d776ba 168
8d26221d 169void PingScheduler::ping_when_ready()
23f51766
CH
170{
171 if ( !WantToPing )
823623d9 172 {
c1abff61 173 GlobalLogger.info() << LogPrefix << "not pinging (not requested to)";
23f51766 174 return;
823623d9 175 }
23f51766
CH
176 else if ( Resolver && Resolver->is_resolving() )
177 {
c1abff61 178 GlobalLogger.info() << LogPrefix << "not pinging (DNS not finished)";
23f51766
CH
179 return;
180 }
181 else if ( !Resolver )
182 // should not happen, but check anyway
183 GlobalLogger.warning() << LogPrefix << "Have no resolver!";
09de3c4b 184
c1abff61 185 GlobalLogger.info() << LogPrefix << "start ping";
23f51766 186 WantToPing = false;
09de3c4b 187
8d26221d 188 // try to get an up-to-date IP
26b0f687 189 HostAddress ip = Resolver->get_next_ip();
8d26221d 190
26b0f687 191 if ( !ip.is_valid() && ContinueOnOutdatedIps)
8d26221d 192 { // we failed to resolve --> try to use outdated IP
26b0f687
CH
193 GlobalLogger.info() << LogPrefix << "Checking for outdated IPs";
194 bool check_up_to_date = false;
195 ip = Resolver->get_next_ip(check_up_to_date);
196 }
197 if ( ip.is_valid() )
72be9e7d
CH
198 Ping->ping( ip.get_ip(),
199 DestinationPort,
200 boost::bind(&PingScheduler::ping_done_handler, this, _1) );
26b0f687
CH
201 else
202 { // should not happen
203 GlobalLogger.error() << LogPrefix << "No IP to ping "
204 << "-- this should not have happened!!";
205 WantToPing = true;
8d26221d 206 if ( !Resolver->is_resolving() )
26b0f687
CH
207 start_resolving_ping_address();
208 }
3f6ba924
CH
209}
210
166fd9e9 211
23f51766
CH
212//------------------------------------------------------------------------------
213// Post Processing of Ping result
214//------------------------------------------------------------------------------
e58d7507 215
c1d776ba
CH
216/**
217 * @brief called when Ping::ping is done; calls functions to update
218 * statistics, ping interval and elapsed time;
23f51766 219 * schedules a call to ping, thereby closing the loop
c1d776ba
CH
220 */
221void PingScheduler::ping_done_handler( const bool ping_success )
502b6af0 222{
c1d776ba 223 // post-processing
079d19ab
CH
224 // You must call these 3 methods exactly in this order
225 // TODO Fix this method, once it has a semantic dependency with the
c1d776ba 226 // update_ping_statistics method, because it depends on the PingAnalyzer
a341119a 227 // statistics to update the exceeded_ping_failed_limit
c1d776ba 228 HostAnalyzer.update_ping_statistics( ping_success );
d8a91bd6 229 update_ping_interval();
c1d776ba
CH
230 update_ping_elapsed_time();
231
72be9e7d
CH
232 if (ping_success)
233 { // reset ContinueOnOutdatedIps
234 ContinueOnOutdatedIps = false;
235 update_log_prefix();
236 }
237
23f51766
CH
238 // get next protocol, possibly start resolving IPs
239 update_ping_protocol();
240
c1d776ba
CH
241 // schedule next ping
242 (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) );
23f51766 243 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
d26dce11 244 boost::asio::placeholders::error ) );
d8a91bd6
GMF
245}
246
247void PingScheduler::update_ping_interval()
248{
c1d776ba 249 // have to ping more often?
fb469ffa 250 if ( HostAnalyzer.exceeded_ping_failed_limit() )
d8a91bd6
GMF
251 {
252 PingIntervalInSec.speed_up();
253
c1abff61
CH
254 GlobalLogger.debug() << LogPrefix << "- Speeding up ping interval to: "
255 << PingIntervalInSec << "s";
d8a91bd6
GMF
256 }
257 else
258 {
259 PingIntervalInSec.back_to_original();
260
c1abff61
CH
261 GlobalLogger.debug() << LogPrefix << "- Stick to the original ping "
262 << "interval: " << PingIntervalInSec << "s";
d8a91bd6 263 }
ced28dc7
GMF
264}
265
502b6af0 266void PingScheduler::update_ping_elapsed_time()
ced28dc7 267{
e5706968 268 ptime now = microsec_clock::universal_time();
101be5ce
GMF
269 time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec =
270 (now - TimeSentLastPing).total_seconds();
c1abff61
CH
271 GlobalLogger.debug() << LogPrefix << "- Time elapsed since last ping: "
272 << elapsed_time_in_sec << "s";
e5706968
GMF
273
274 TimeSentLastPing = microsec_clock::universal_time();
e39cc3da 275}
c1d776ba 276
23f51766
CH
277
278//------------------------------------------------------------------------------
279// Ping Protocol Rotation
280//------------------------------------------------------------------------------
281
26b0f687 282void PingScheduler::init_ping_protocol()
23f51766 283{
26b0f687 284 ProtocolIter = Protocols.end();
23f51766
CH
285 get_next_ping_protocol();
286}
287
26b0f687 288void PingScheduler::update_ping_protocol()
23f51766
CH
289{
290 if ( can_change_ping_protocol() )
291 {
292 get_next_ping_protocol();
293 }
294}
295
26b0f687 296void PingScheduler::get_next_ping_protocol()
23f51766 297{
26b0f687
CH
298 ++ProtocolIter;
299 if (ProtocolIter == Protocols.end())
300 ProtocolIter = Protocols.begin();
301 PingProtocol ping_protocol = *ProtocolIter;
72be9e7d 302 // --> ProtocolIter still points to currently used protocol which is
26b0f687 303 // required in dns_resolve_callback
23f51766
CH
304
305 if (Ping)
306 Ping->stop_pinging();
307
308 Ping = PingerFactory::createPinger(ping_protocol, IoService,
309 NetworkInterfaceName, PingReplyTimeout);
310
311 update_dns_resolver( ping_protocol );
72be9e7d 312
23f51766
CH
313}
314
26b0f687 315bool PingScheduler::can_change_ping_protocol() const
23f51766 316{
c1abff61
CH
317 // TODO can_change_ping_protocol() and get_next_ping_protocol() may be
318 // implemented in a Algorithm class that can be exchanged in this class to
319 // provide an algorithm neutral class
23f51766
CH
320 return true;
321}
322
323//------------------------------------------------------------------------------
324// DNS host name resolution
325//------------------------------------------------------------------------------
26b0f687
CH
326
327// show "!" after host name if running on outdated IPs
8d26221d 328void PingScheduler::update_log_prefix()
26b0f687
CH
329{
330 std::stringstream temp;
72be9e7d 331 temp << "Sched(" << DestinationAddress;
26b0f687
CH
332 if (ContinueOnOutdatedIps)
333 temp << "!";
334 temp << "): ";
335 LogPrefix = temp.str();
336}
337
338void PingScheduler::update_dns_resolver( PingProtocol current_protocol )
23f51766
CH
339{
340 if (Resolver && Resolver->is_resolving())
72be9e7d
CH
341 cancel_resolve(false);
342
343 if (ContinueOnOutdatedIps)
23f51766 344 {
72be9e7d
CH
345 ContinueOnOutdatedIps = false;
346 update_log_prefix();
23f51766
CH
347 }
348
349 // DNS master caches created resolvers and resolved IPs, so this will
350 // probably just return an existing resolver with already resolved IPs for
351 // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
352 Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
353 current_protocol);
354 // start resolving if no ips available
355 if ( Resolver->have_up_to_date_ip() )
356 {
72be9e7d 357 if (Resolver->is_resolving())
c1abff61 358 GlobalLogger.warning() << LogPrefix << "have up to date IPs but "
23f51766
CH
359 << "resolver seems to be resolving all the same... "
360 << "Start pinging anyway!";
8d26221d 361 ping_when_ready();
23f51766
CH
362 }
363 else
364 start_resolving_ping_address();
365}
366
26b0f687 367void PingScheduler::start_resolving_ping_address()
23f51766 368{
26b0f687 369 Resolver->async_resolve( boost::bind(&PingScheduler::dns_resolve_callback,
23f51766
CH
370 this, _1, _2) );
371}
372
26b0f687
CH
373void PingScheduler::dns_resolve_callback(const bool was_success,
374 const int cname_count)
23f51766 375{
c1abff61 376 GlobalLogger.info() << LogPrefix << "dns resolution finished "
23f51766
CH
377 << "with success = " << was_success << " "
378 << "and cname_count = " << cname_count;
379
26b0f687
CH
380 if ( was_success )
381 {
382 HostAnalyzer.set_resolved_ip_count( Resolver->get_resolved_ip_count());
8d26221d 383 ping_when_ready();
26b0f687
CH
384 }
385 else
386 { // host name resolution failed; try again bypassing first outdated CNAME
8d26221d 387 // or using cached IP
72be9e7d
CH
388 ContinueOnOutdatedIps = true;
389 update_log_prefix();
26b0f687
CH
390
391 std::string skip_host = Resolver->get_skip_cname();
23f51766
CH
392
393 if (skip_host.empty())
8d26221d 394 { // continue with IP
23f51766
CH
395 GlobalLogger.notice() << LogPrefix << "DNS failed, "
396 << "try anyway with cached data";
26b0f687 397 HostAnalyzer.set_resolved_ip_count(0);
8d26221d 398 ping_when_ready();
23f51766
CH
399 }
400 else
8d26221d 401 { // have CNAME to continue
23f51766
CH
402 GlobalLogger.notice() << LogPrefix << "DNS failed, "
403 << "try again skipping a CNAME and resolving "
404 << skip_host << " directly";
72be9e7d
CH
405
406 cancel_resolve(false);
407
408 // now create new resolver
23f51766 409 Resolver = DnsMaster::get_instance()
26b0f687 410 ->get_resolver_for(skip_host, *ProtocolIter);
23f51766
CH
411 start_resolving_ping_address();
412 }
413 }
23f51766 414}
72be9e7d
CH
415
416/**
417 * cancel resolver if force_cancel or if it is not resolving DestinationAddress
418 *
419 * Resolvers have a life on their own: they are cached by DnsMaster so never go
420 * out of scope and even after calling callbacks, there might still be a
421 * longterm timer active to re-try resolving.
422 * We want to cancel that long-term timer only if the Resolver is not for our
423 * real, original DestinationAddress but a CNAME, which can happen when trying
424 * to skip cnames and working on out-dated IPs
425 */
426void PingScheduler::cancel_resolve(const bool force_cancel)
427{
428 if (force_cancel)
429 {
430 GlobalLogger.info() << "Cancelling resolver (forced)";
431 Resolver->cancel_resolve();
432 }
433 else if ( Resolver->get_hostname() == DestinationAddress )
434 GlobalLogger.info() << LogPrefix
435 << "Leave original resolver active in background";
436 else
437 {
438 GlobalLogger.info() << LogPrefix << "Cancel resolver for "
439 << Resolver->get_hostname() << " since is not the original "
440 << DestinationAddress;
441 Resolver->cancel_resolve();
442 }
443}
444