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 Resolver->cancel_resolve();
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 << "not pinging (not requested to)";
176 else if ( Resolver && Resolver->is_resolving() )
178 GlobalLogger.info() << LogPrefix << "not pinging (DNS not finished)";
181 else if ( !Resolver )
182 // should not happen, but check anyway
183 GlobalLogger.warning() << LogPrefix << "Have no resolver!";
185 GlobalLogger.info() << LogPrefix << "start ping";
188 // try to get an up-to-date IP
189 HostAddress ip = Resolver->get_next_ip();
191 if ( !ip.is_valid() && ContinueOnOutdatedIps)
192 { // we failed to resolve --> try to use outdated IP
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);
199 Resolver->get_next_ip().get_ip(),
201 boost::bind(&PingScheduler::ping_done_handler, this, _1)
204 { // should not happen
205 GlobalLogger.error() << LogPrefix << "No IP to ping "
206 << "-- this should not have happened!!";
208 if ( !Resolver->is_resolving() )
209 start_resolving_ping_address();
212 // next time try with up-to-date IP
213 ContinueOnOutdatedIps = false;
217 //------------------------------------------------------------------------------
218 // Post Processing of Ping result
219 //------------------------------------------------------------------------------
222 * @brief called when Ping::ping is done; calls functions to update
223 * statistics, ping interval and elapsed time;
224 * schedules a call to ping, thereby closing the loop
226 void PingScheduler::ping_done_handler( const bool ping_success )
229 // You must call these 3 methods exactly in this order
230 // TODO Fix this method, once it has a semantic dependency with the
231 // update_ping_statistics method, because it depends on the PingAnalyzer
232 // statistics to update the exceeded_ping_failed_limit
233 HostAnalyzer.update_ping_statistics( ping_success );
234 update_ping_interval();
235 update_ping_elapsed_time();
237 // get next protocol, possibly start resolving IPs
238 update_ping_protocol();
240 // schedule next ping
241 (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) );
242 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
243 boost::asio::placeholders::error ) );
246 void PingScheduler::update_ping_interval()
248 // have to ping more often?
249 if ( HostAnalyzer.exceeded_ping_failed_limit() )
251 PingIntervalInSec.speed_up();
253 GlobalLogger.debug() << LogPrefix << "- Speeding up ping interval to: "
254 << PingIntervalInSec << "s";
258 PingIntervalInSec.back_to_original();
260 GlobalLogger.debug() << LogPrefix << "- Stick to the original ping "
261 << "interval: " << PingIntervalInSec << "s";
265 void PingScheduler::update_ping_elapsed_time()
267 ptime now = microsec_clock::universal_time();
268 time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec =
269 (now - TimeSentLastPing).total_seconds();
270 GlobalLogger.debug() << LogPrefix << "- Time elapsed since last ping: "
271 << elapsed_time_in_sec << "s";
273 TimeSentLastPing = microsec_clock::universal_time();
277 //------------------------------------------------------------------------------
278 // Ping Protocol Rotation
279 //------------------------------------------------------------------------------
281 void PingScheduler::init_ping_protocol()
283 ProtocolIter = Protocols.end();
284 get_next_ping_protocol();
287 void PingScheduler::update_ping_protocol()
289 if ( can_change_ping_protocol() )
291 get_next_ping_protocol();
295 void PingScheduler::get_next_ping_protocol()
298 if (ProtocolIter == Protocols.end())
299 ProtocolIter = Protocols.begin();
300 PingProtocol ping_protocol = *ProtocolIter;
301 // --> ProtocolIter still points to currently used protocol which is
302 // required in dns_resolve_callback
305 Ping->stop_pinging();
307 Ping = PingerFactory::createPinger(ping_protocol, IoService,
308 NetworkInterfaceName, PingReplyTimeout);
310 update_dns_resolver( ping_protocol );
313 bool PingScheduler::can_change_ping_protocol() const
315 // TODO can_change_ping_protocol() and get_next_ping_protocol() may be
316 // implemented in a Algorithm class that can be exchanged in this class to
317 // provide an algorithm neutral class
321 //------------------------------------------------------------------------------
322 // DNS host name resolution
323 //------------------------------------------------------------------------------
325 // show "!" after host name if running on outdated IPs
326 void PingScheduler::update_log_prefix()
328 std::stringstream temp;
329 temp << "PS(" << DestinationAddress;
330 if (ContinueOnOutdatedIps)
333 LogPrefix = temp.str();
336 void PingScheduler::update_dns_resolver( PingProtocol current_protocol )
338 if (Resolver && Resolver->is_resolving())
340 GlobalLogger.warning() << LogPrefix
341 << "Resolver still seems to be resolving --> cancel!";
342 Resolver->cancel_resolve();
345 // DNS master caches created resolvers and resolved IPs, so this will
346 // probably just return an existing resolver with already resolved IPs for
347 // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
348 Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
350 // start resolving if no ips available
351 if ( Resolver->have_up_to_date_ip() )
353 if (!Resolver->is_resolving())
354 GlobalLogger.warning() << LogPrefix << "have up to date IPs but "
355 << "resolver seems to be resolving all the same... "
356 << "Start pinging anyway!";
360 start_resolving_ping_address();
363 void PingScheduler::start_resolving_ping_address()
365 Resolver->async_resolve( boost::bind(&PingScheduler::dns_resolve_callback,
369 void PingScheduler::dns_resolve_callback(const bool was_success,
370 const int cname_count)
372 GlobalLogger.info() << LogPrefix << "dns resolution finished "
373 << "with success = " << was_success << " "
374 << "and cname_count = " << cname_count;
376 // TODO this is too simple, but need to think more about how to update here!
377 // (may have to switch back some time to resolver for original host or so
378 ContinueOnOutdatedIps = !was_success;
383 HostAnalyzer.set_resolved_ip_count( Resolver->get_resolved_ip_count());
387 { // host name resolution failed; try again bypassing first outdated CNAME
388 // or using cached IP
390 std::string skip_host = Resolver->get_skip_cname();
392 if (skip_host.empty())
393 { // continue with IP
394 GlobalLogger.notice() << LogPrefix << "DNS failed, "
395 << "try anyway with cached data";
396 HostAnalyzer.set_resolved_ip_count(0);
400 { // have CNAME to continue
401 GlobalLogger.notice() << LogPrefix << "DNS failed, "
402 << "try again skipping a CNAME and resolving "
403 << skip_host << " directly";
404 Resolver = DnsMaster::get_instance()
405 ->get_resolver_for(skip_host, *ProtocolIter);
406 start_resolving_ping_address();
408 // (the original resolver is still alive and cached by DnsMaster and
409 // counting down time to re-try on its own until cancel_resolve)