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>
27 #include <logfunc.hpp>
29 #include "boost_assert_handler.h"
30 #include "host/pingerfactory.h"
31 #include "icmp/icmppinger.h"
32 #include "link/linkstatus.h"
35 using boost::asio::io_service;
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;
44 //-----------------------------------------------------------------------------
46 //-----------------------------------------------------------------------------
49 * @brief Parameterized constructor.
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
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,
76 NetworkInterfaceName( network_interface ),
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 ),
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) );
95 std::stringstream temp;
96 temp << "PS(" << DestinationAddress << "): ";
97 LogPrefix = temp.str();
99 // fill circular buffer with protocols
100 BOOST_FOREACH( const PingProtocol &prot, protocol_list )
101 ProtocolRotate.push_back(prot);
103 init_ping_protocol();
109 PingScheduler::~PingScheduler()
113 void PingScheduler::stop_pinging()
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();
120 // now cancel the own timer
121 GlobalLogger.debug() << "scheduler: cancel timer" << endl;
122 NextPingTimer.cancel();
126 * @brief Start into infinite loop of calls to ping
128 * Does not start yet but set NextPingTimer (possibly to 0), so action starts
129 * when io_service is started
131 void PingScheduler::start_pinging()
133 if ( FirstDelay > 0 )
134 GlobalLogger.info() << "Delaying first ping by " << FirstDelay << "s";
136 GlobalLogger.info() << "Schedule ping as soon as possible";
138 (void) NextPingTimer.expires_from_now( seconds( FirstDelay ) );
139 NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
140 boost::asio::placeholders::error ) );
145 * @brief call Ping::ping and schedule a call to ping_done_handler when finished
147 void PingScheduler::ping(const boost::system::error_code &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;
155 GlobalLogger.error() << "Received error " << error
156 << " waiting for ping! Stopping"
161 // ping as soon as dns is ready
167 void PingRotate::try_to_ping()
171 GlobalLogger.info() << "PingRotate: not pinging yet (not requested to)";
174 else if ( Resolver && Resolver->is_resolving() )
176 GlobalLogger.info() << "PingRotate: not pinging yet (DNS not finished)";
179 else if ( !Resolver )
180 // should not happen, but check anyway
181 GlobalLogger.warning() << LogPrefix << "Have no resolver!";
183 GlobalLogger.info() << "PingRotate: start ping";
187 Resolver->get_next_ip().get_ip(),
189 boost::bind(&PingScheduler::ping_done_handler, this, _1)
194 //------------------------------------------------------------------------------
195 // Post Processing of Ping result
196 //------------------------------------------------------------------------------
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
203 void PingScheduler::ping_done_handler( const bool ping_success )
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();
214 // get next protocol, possibly start resolving IPs
215 update_ping_protocol();
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 ) );
223 void PingScheduler::update_ping_interval()
225 // have to ping more often?
226 if ( HostAnalyzer.exceeded_ping_failed_limit() )
228 PingIntervalInSec.speed_up();
230 GlobalLogger.debug() << "- Speeding up ping interval to: " << PingIntervalInSec << "s"
235 PingIntervalInSec.back_to_original();
237 GlobalLogger.debug() << "- Stick to the original ping interval: " << PingIntervalInSec << "s"
242 void PingScheduler::update_ping_elapsed_time()
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;
249 TimeSentLastPing = microsec_clock::universal_time();
253 //------------------------------------------------------------------------------
254 // Ping Protocol Rotation
255 //------------------------------------------------------------------------------
257 void PingRotate::init_ping_protocol()
259 get_next_ping_protocol();
262 void PingRotate::update_ping_protocol()
264 if ( can_change_ping_protocol() )
266 get_next_ping_protocol();
270 void PingRotate::get_next_ping_protocol()
272 PingProtocol ping_protocol = ProtocolRotate.front();
273 ProtocolRotate.pop_front();
274 ProtocolRotate.push_back(ping_protocol);
277 Ping->stop_pinging();
279 Ping = PingerFactory::createPinger(ping_protocol, IoService,
280 NetworkInterfaceName, PingReplyTimeout);
282 update_dns_resolver( ping_protocol );
285 bool PingRotate::can_change_ping_protocol() const
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
292 //------------------------------------------------------------------------------
293 // DNS host name resolution
294 //------------------------------------------------------------------------------
296 void PingRotate::update_dns_resolver( PingProtocol current_protocol )
298 if (Resolver && Resolver->is_resolving())
300 GlobalLogger.warning() << "Resolver still seems to be resolving "
302 Resolver->cancel_resolve();
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,
310 // start resolving if no ips available
311 if ( Resolver->have_up_to_date_ip() )
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!";
320 start_resolving_ping_address();
323 void PingRotate::start_resolving_ping_address() //lint !e1762
325 Resolver->async_resolve( boost::bind(&PingRotate::dns_resolve_callback,
329 void PingRotate::dns_resolve_callback(const bool was_success,
330 const int cname_count)
332 GlobalLogger.info() << "PingRotate: dns resolution finished "
333 << "with success = " << was_success << " "
334 << "and cname_count = " << cname_count;
337 { // host name resolution failed; try again bypassing first CNAME(s)
338 std::string skip_host = Resolver->get_skipper();
340 if (skip_host.empty())
342 GlobalLogger.notice() << LogPrefix << "DNS failed, "
343 << "try anyway with cached data";
344 HostAnalyzer->set_resolved_ip_count(0);
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();
359 HostAnalyzer->set_resolved_ip_count( Resolver->get_resolved_ip_count());