2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 #include "host/pingscheduler.h"
25 #include <boost/bind.hpp>
26 #include <boost/foreach.hpp>
28 #include <logfunc.hpp>
30 #include "boost_assert_handler.h"
31 #include "host/pingerfactory.h"
32 #include "dns/dnsmaster.h"
33 #include "icmp/icmppinger.h"
34 #include "link/linkstatus.h"
37 using boost::asio::io_service;
39 using boost::date_time::time_resolution_traits_adapted64_impl;
40 using boost::posix_time::microsec_clock;
41 using boost::posix_time::ptime;
42 using boost::posix_time::seconds;
43 using boost::shared_ptr;
44 using I2n::Logger::GlobalLogger;
46 //-----------------------------------------------------------------------------
48 //-----------------------------------------------------------------------------
51 * @brief Parameterized constructor.
53 * @param io_serv The one @c io_serv object that controls async processing
54 * @param network_interface The name of the network interface sending the pings.
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.
60 * @param link_analyzer The object to monitor the link status.
61 * @param first_delay Delay in seconds from start_pinging to first ping attempt
63 PingScheduler::PingScheduler(
64 const IoServiceItem io_serv,
65 const string &network_interface,
66 const string &destination_address,
67 const uint16_t destination_port,
68 const PingProtocolList &ping_protocol_list,
69 const long ping_interval_in_sec,
70 const int ping_fail_percentage_limit,
71 const int ping_reply_timeout,
72 LinkStatusItem link_analyzer,
77 NetworkInterfaceName( network_interface ),
78 DestinationAddress( destination_address ),
79 DestinationPort( destination_port ),
80 Protocols( ping_protocol_list ),
82 PingIntervalInSec( ping_interval_in_sec ),
83 FirstDelay( first_delay ),
84 NextPingTimer( *io_serv ),
85 TimeSentLastPing( microsec_clock::universal_time() ),
86 PingReplyTimeout( ping_reply_timeout ),
87 HostAnalyzer( destination_address, ping_fail_percentage_limit,
93 ContinueOnOutdatedIps( false )
95 BOOST_ASSERT( !network_interface.empty() );
96 BOOST_ASSERT( !destination_address.empty() );
97 BOOST_ASSERT( ( 0 < destination_port ) &&
98 ( destination_port < numeric_limits<uint16_t>::max() ) );
99 BOOST_ASSERT( 0 < ping_interval_in_sec );
100 BOOST_ASSERT( (0 <= ping_fail_percentage_limit) &&
101 ( ping_fail_percentage_limit <= 100) );
105 init_ping_protocol();
111 PingScheduler::~PingScheduler()
115 void PingScheduler::stop_pinging()
117 // stop pinger and resolver
118 GlobalLogger.debug() << LogPrefix << "scheduler: stop pinging";
119 Ping->stop_pinging();
120 cancel_resolve(true);
122 // now cancel the own timer in case that pinger cancelation called callback
123 GlobalLogger.debug() << LogPrefix << "scheduler: cancel timer";
124 NextPingTimer.cancel();
128 * @brief Start into infinite loop of calls to ping
130 * Does not start yet but set NextPingTimer (possibly to 0), so action starts
131 * when io_service is started
133 void PingScheduler::start_pinging()
135 if ( FirstDelay > 0 )
136 GlobalLogger.info() << LogPrefix << "Delaying first ping by "
137 << FirstDelay << "s";
139 GlobalLogger.info() << LogPrefix << "Schedule ping as soon as possible";
141 (void) NextPingTimer.expires_from_now( seconds( FirstDelay ) );
142 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
143 boost::asio::placeholders::error ) );
148 * @brief call Ping::ping and schedule a call to ping_done_handler when finished
150 void PingScheduler::ping(const boost::system::error_code &error)
153 { // get here, e.g. by NextPingTimer.cancel in stop_pinging
154 if ( error == boost::asio::error::operation_aborted )
155 GlobalLogger.error() << LogPrefix << "Timer for ping was cancelled!"
158 GlobalLogger.error() << LogPrefix << "Received error " << error
159 << " waiting for ping! Stopping";
163 // ping as soon as dns is ready
169 void PingScheduler::ping_when_ready()
173 GlobalLogger.info() << LogPrefix << "waiting for ping request "
174 << "(should take no more than than " << PingIntervalInSec << "s)";
177 else if ( Resolver && Resolver->is_resolving() )
179 GlobalLogger.info() << LogPrefix << "waiting for DNS to finish";
182 else if ( !Resolver )
183 // should not happen, but check anyway
184 GlobalLogger.warning() << LogPrefix << "Have no resolver!";
186 GlobalLogger.info() << LogPrefix << "start ping";
189 // try to get an up-to-date IP (ContinueOnOutdatedIps may only be set
190 // because a CNAME was out of date -- IPs may still be current)
191 HostAddress ip = Resolver->get_next_ip();
193 if ( !ip.is_valid() )
194 { // this can happen in 2 cases: if ContinueOnOutdatedIps==true
195 // or when ip went out of date between resolve and now
196 // --> try to use outdated IP
197 GlobalLogger.info() << LogPrefix << "Checking for outdated IPs";
198 bool check_up_to_date = false;
199 ip = Resolver->get_next_ip(check_up_to_date);
201 if ( !ip.is_valid() )
202 { // Do not even have an outdated IP!
203 // This happens if have no cached IPs and resolve failed
204 GlobalLogger.info() << LogPrefix << "Not even outdated IP to ping "
205 << "-- treat like a failed ping.";
207 // skip the ping and directly call ping_done_handler
208 HostAnalyzer.set_resolved_ip_count(1); // must have been 0 --> failed
209 // ping would create failed assumption (nPings > nIPs)
210 ping_done_handler(false);
211 HostAnalyzer.set_resolved_ip_count(0); // set back
215 uint32_t ttl = ip.get_ttl().get_updated_value();
218 expiry = "out of date!";
221 boost::posix_time::ptime now =
222 boost::posix_time::second_clock::local_time();
223 expiry = boost::posix_time::to_simple_string(now + seconds(ttl));
226 GlobalLogger.info() << LogPrefix << "pinging IP " << ip.get_ip()
227 << " with TTL " << ttl << "s (" << expiry << ")";
228 Ping->ping( ip.get_ip(),
230 boost::bind(&PingScheduler::ping_done_handler, this, _1) );
235 //------------------------------------------------------------------------------
236 // Post Processing of Ping result
237 //------------------------------------------------------------------------------
240 * @brief called when Ping::ping is done; calls functions to update
241 * statistics, ping interval and elapsed time;
242 * schedules a call to ping, thereby closing the loop
244 void PingScheduler::ping_done_handler( const bool ping_success )
246 GlobalLogger.info() << LogPrefix << "Ping done with success = "
250 // You must call these 3 methods exactly in this order
251 // TODO Fix this method, once it has a semantic dependency with the
252 // update_ping_statistics method, because it depends on the PingAnalyzer
253 // statistics to update the exceeded_ping_failed_limit
254 HostAnalyzer.update_ping_statistics( ping_success );
255 update_ping_interval();
256 update_ping_elapsed_time();
259 { // reset ContinueOnOutdatedIps
260 ContinueOnOutdatedIps = false;
264 // get next protocol, possibly start resolving IPs
265 update_ping_protocol();
267 // schedule next ping
268 (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) );
269 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
270 boost::asio::placeholders::error ) );
273 void PingScheduler::update_ping_interval()
275 // have to ping more often?
276 if ( HostAnalyzer.exceeded_ping_failed_limit() )
278 PingIntervalInSec.speed_up();
280 GlobalLogger.debug() << LogPrefix << "- Speeding up ping interval to: "
281 << PingIntervalInSec << "s";
285 PingIntervalInSec.back_to_original();
287 GlobalLogger.debug() << LogPrefix << "- Stick to the original ping "
288 << "interval: " << PingIntervalInSec << "s";
292 void PingScheduler::update_ping_elapsed_time()
294 ptime now = microsec_clock::universal_time();
295 time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec =
296 (now - TimeSentLastPing).total_seconds();
297 GlobalLogger.debug() << LogPrefix << "- Time elapsed since last ping: "
298 << elapsed_time_in_sec << "s";
300 TimeSentLastPing = microsec_clock::universal_time();
304 //------------------------------------------------------------------------------
305 // Ping Protocol Rotation
306 //------------------------------------------------------------------------------
308 void PingScheduler::init_ping_protocol()
310 ProtocolIter = Protocols.end();
311 get_next_ping_protocol();
314 void PingScheduler::update_ping_protocol()
316 if ( can_change_ping_protocol() )
318 get_next_ping_protocol();
322 void PingScheduler::get_next_ping_protocol()
326 Ping->stop_pinging();
330 GlobalLogger.debug() << LogPrefix
331 << "------------------------------------------------------------------";
333 if (ProtocolIter == Protocols.end())
334 ProtocolIter = Protocols.begin();
335 PingProtocol ping_protocol = *ProtocolIter;
336 // --> ProtocolIter still points to currently used protocol which is
337 // required in dns_resolve_callback
339 Ping = PingerFactory::createPinger(ping_protocol, IoService,
340 NetworkInterfaceName, PingReplyTimeout);
342 update_dns_resolver( ping_protocol );
346 bool PingScheduler::can_change_ping_protocol() const
348 // TODO can_change_ping_protocol() and get_next_ping_protocol() may be
349 // implemented in a Algorithm class that can be exchanged in this class to
350 // provide an algorithm neutral class
354 //------------------------------------------------------------------------------
355 // DNS host name resolution
356 //------------------------------------------------------------------------------
358 // show "!" after host name if running on outdated IPs
359 void PingScheduler::update_log_prefix()
361 std::stringstream temp;
362 temp << "Sched(" << DestinationAddress;
363 if (ContinueOnOutdatedIps)
366 LogPrefix = temp.str();
369 void PingScheduler::update_dns_resolver( PingProtocol current_protocol )
371 if (Resolver && Resolver->is_resolving())
372 cancel_resolve(false);
374 if (ContinueOnOutdatedIps)
376 ContinueOnOutdatedIps = false;
380 // DNS master caches created resolvers and resolved IPs, so this will
381 // probably just return an existing resolver with already resolved IPs for
382 // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
383 Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
386 // get number of up-to-date IPs
387 // TODO should check here, if they will be up to date in PingIntervalInSec
388 bool check_up_to_date = true;
389 int ip_count = Resolver->get_resolved_ip_count(check_up_to_date);
392 GlobalLogger.info() << LogPrefix << "Set resolved_ip_count to "
393 << ip_count << " (IPs may be outdated=" << !check_up_to_date << ")";
394 HostAnalyzer.set_resolved_ip_count( ip_count );
396 if (Resolver->is_resolving())
397 GlobalLogger.warning() << LogPrefix << "have up to date IPs but "
398 << "resolver seems to be resolving all the same... "
399 << "Start pinging anyway!";
404 GlobalLogger.info() << LogPrefix
405 << "No up-to-date IPs --> start resolve";
406 start_resolving_ping_address();
407 // set resolved_ip_count will be called in resolve callback
411 void PingScheduler::start_resolving_ping_address()
413 Resolver->async_resolve( boost::bind(&PingScheduler::dns_resolve_callback,
417 void PingScheduler::dns_resolve_callback(const bool was_success,
418 const int recursion_count)
420 GlobalLogger.info() << LogPrefix << "dns resolution finished "
421 << "with success = " << was_success << " "
422 << "after " << recursion_count << " recursions";
426 // trust that a successfull DNS resolve means we have an IP with TTL>0
427 int ip_count = Resolver->get_resolved_ip_count(!ContinueOnOutdatedIps);
429 { // this will create trouble in HostAnalyzer
430 GlobalLogger.warning() << LogPrefix
431 << "Should not have reached this case: resolve was "
432 << "successfull but still have no IPs (up-to-date="
433 << !ContinueOnOutdatedIps << ")!";
434 if (DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() > 0)
435 GlobalLogger.warning() << LogPrefix << "This probably happened "
436 << "because you specified a TTL threshold > 0 but resolving"
437 << " had no effect on TTLs since external cache is only "
438 << "updated when TTL=0 is reached.";
442 GlobalLogger.info() << LogPrefix << "Set resolved_ip_count to "
443 << ip_count << " (IPs may be outdated="
444 << ContinueOnOutdatedIps << ") --> could ping now";
445 HostAnalyzer.set_resolved_ip_count( ip_count );
450 { // host name resolution failed; try again bypassing first outdated CNAME
451 // or using cached IP
452 std::string skip_host = Resolver->get_skip_cname();
454 if (skip_host.empty())
455 { // try to continue with cached IPs
456 int ip_count = Resolver->get_resolved_ip_count(false);
459 GlobalLogger.notice() << LogPrefix << "DNS failed "
460 << "and have no cached IPs either --> cannot ping";
461 // ping_when_ready will deal with this case
464 ContinueOnOutdatedIps = true;
467 GlobalLogger.notice() << LogPrefix << "DNS failed, "
468 << "try anyway with cached data";
471 GlobalLogger.info() << LogPrefix << "Set resolved_ip_count to "
472 << ip_count << " (IPs may be outdated=" << true << ")";
473 HostAnalyzer.set_resolved_ip_count( ip_count );
478 { // have CNAME to continue
479 ContinueOnOutdatedIps = true;
481 GlobalLogger.notice() << LogPrefix << "DNS failed, "
482 << "try again skipping a CNAME and resolving "
483 << skip_host << " directly";
485 cancel_resolve(false);
487 // now create new resolver
488 Resolver = DnsMaster::get_instance()
489 ->get_resolver_for(skip_host, *ProtocolIter);
490 start_resolving_ping_address();
496 * cancel resolver if force_cancel or if it is not resolving DestinationAddress
498 * Resolvers have a life on their own: they are cached by DnsMaster so never go
499 * out of scope and even after calling callbacks, there might still be a
500 * longterm timer active to re-try resolving.
501 * We want to cancel that long-term timer only if the Resolver is not for our
502 * real, original DestinationAddress but a CNAME, which can happen when trying
503 * to skip cnames and working on out-dated IPs
505 void PingScheduler::cancel_resolve(const bool force_cancel)
509 GlobalLogger.info() << "Cancelling resolver (forced)";
510 Resolver->cancel_resolve();
512 else if ( Resolver->get_hostname() == DestinationAddress )
513 GlobalLogger.info() << LogPrefix
514 << "Leave original resolver active in background";
517 GlobalLogger.info() << LogPrefix << "Cancel resolver for "
518 << Resolver->get_hostname() << " since is not the original "
519 << DestinationAddress;
520 Resolver->cancel_resolve();