Commit | Line | Data |
---|---|---|
91fcc471 TJ |
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 | */ | |
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 | 36 | using namespace std; |
2bf8720f GMF |
37 | using boost::asio::io_service; |
38 | using boost::bind; | |
101be5ce | 39 | using boost::date_time::time_resolution_traits_adapted64_impl; |
2bf8720f GMF |
40 | using boost::posix_time::microsec_clock; |
41 | using boost::posix_time::ptime; | |
42 | using boost::posix_time::seconds; | |
e58d7507 | 43 | using boost::shared_ptr; |
301610ca | 44 | using 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 |
086e2cc0 GMF |
54 | * @param network_interface The name of the network interface originating 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. | |
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 | 63 | PingScheduler::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 ), |
4cf6fed4 | 87 | HostAnalyzer( destination_address, ping_fail_percentage_limit, link_analyzer ), |
26b0f687 CH |
88 | Resolver(), |
89 | Ping(), | |
90 | WantToPing( false ), | |
91 | LogPrefix(), | |
8d26221d | 92 | ContinueOnOutdatedIps( false ) |
ced28dc7 | 93 | { |
475ad07c GMF |
94 | BOOST_ASSERT( !network_interface.empty() ); |
95 | BOOST_ASSERT( !destination_address.empty() ); | |
23f51766 CH |
96 | BOOST_ASSERT( ( 0 < destination_port ) && |
97 | ( destination_port < numeric_limits<uint16_t>::max() ) ); | |
f71cb7e1 | 98 | BOOST_ASSERT( 0 < ping_interval_in_sec ); |
23f51766 CH |
99 | BOOST_ASSERT( (0 <= ping_fail_percentage_limit) && |
100 | ( ping_fail_percentage_limit <= 100) ); | |
101 | ||
26b0f687 | 102 | update_log_prefix(); |
23f51766 CH |
103 | |
104 | init_ping_protocol(); | |
2d591235 | 105 | } |
ced28dc7 | 106 | |
086e2cc0 GMF |
107 | /** |
108 | * @brief Destructor. | |
109 | */ | |
4bb97b45 | 110 | PingScheduler::~PingScheduler() |
2d591235 | 111 | { |
ced28dc7 GMF |
112 | } |
113 | ||
c1d776ba | 114 | void PingScheduler::stop_pinging() |
ced28dc7 | 115 | { |
f076f8d4 CH |
116 | // stop pinger, which will probably call ping_done_handler --> re-new NextPingTimer |
117 | GlobalLogger.debug() << "scheduler: stop pinging" << endl; | |
5a9bc2d1 | 118 | Ping->stop_pinging(); |
23f51766 | 119 | Resolver->cancel_resolve(); |
f076f8d4 CH |
120 | |
121 | // now cancel the own timer | |
122 | GlobalLogger.debug() << "scheduler: cancel timer" << endl; | |
123 | NextPingTimer.cancel(); | |
c1d776ba CH |
124 | } |
125 | ||
126 | /** | |
23f51766 | 127 | * @brief Start into infinite loop of calls to ping |
cad0b08d CH |
128 | * |
129 | * Does not start yet but set NextPingTimer (possibly to 0), so action starts | |
130 | * when io_service is started | |
c1d776ba CH |
131 | */ |
132 | void PingScheduler::start_pinging() | |
133 | { | |
c1d776ba | 134 | if ( FirstDelay > 0 ) |
59733431 | 135 | GlobalLogger.info() << "Delaying first ping by " << FirstDelay << "s"; |
59733431 | 136 | else |
cad0b08d CH |
137 | GlobalLogger.info() << "Schedule ping as soon as possible"; |
138 | ||
139 | (void) NextPingTimer.expires_from_now( seconds( FirstDelay ) ); | |
23f51766 | 140 | NextPingTimer.async_wait( bind( &PingScheduler::ping, this, |
cad0b08d | 141 | boost::asio::placeholders::error ) ); |
09de3c4b GMF |
142 | } |
143 | ||
4e91c69a | 144 | |
c1d776ba | 145 | /** |
23f51766 | 146 | * @brief call Ping::ping and schedule a call to ping_done_handler when finished |
c1d776ba | 147 | */ |
23f51766 | 148 | void PingScheduler::ping(const boost::system::error_code &error) |
823623d9 | 149 | { |
d26dce11 | 150 | if ( error ) |
f076f8d4 | 151 | { // get here, e.g. by NextPingTimer.cancel in stop_pinging |
d26dce11 | 152 | if ( error == boost::asio::error::operation_aborted ) |
23f51766 CH |
153 | GlobalLogger.error() << "Timer for ping was cancelled! " |
154 | << "Stopping" << endl; | |
d26dce11 CH |
155 | else |
156 | GlobalLogger.error() << "Received error " << error | |
23f51766 CH |
157 | << " waiting for ping! Stopping" |
158 | << endl; | |
d26dce11 CH |
159 | return; |
160 | } | |
161 | ||
23f51766 CH |
162 | // ping as soon as dns is ready |
163 | WantToPing = true; | |
8d26221d | 164 | ping_when_ready(); |
23f51766 CH |
165 | } |
166 | ||
c1d776ba | 167 | |
8d26221d | 168 | void PingScheduler::ping_when_ready() |
23f51766 CH |
169 | { |
170 | if ( !WantToPing ) | |
823623d9 | 171 | { |
26b0f687 | 172 | GlobalLogger.info() << "PingScheduler: not pinging (not requested to)"; |
23f51766 | 173 | return; |
823623d9 | 174 | } |
23f51766 CH |
175 | else if ( Resolver && Resolver->is_resolving() ) |
176 | { | |
26b0f687 | 177 | GlobalLogger.info() << "PingScheduler: not pinging (DNS not finished)"; |
23f51766 CH |
178 | return; |
179 | } | |
180 | else if ( !Resolver ) | |
181 | // should not happen, but check anyway | |
182 | GlobalLogger.warning() << LogPrefix << "Have no resolver!"; | |
09de3c4b | 183 | |
26b0f687 | 184 | GlobalLogger.info() << "PingScheduler: start ping"; |
23f51766 | 185 | WantToPing = false; |
09de3c4b | 186 | |
8d26221d | 187 | // try to get an up-to-date IP |
26b0f687 | 188 | HostAddress ip = Resolver->get_next_ip(); |
8d26221d | 189 | |
26b0f687 | 190 | if ( !ip.is_valid() && ContinueOnOutdatedIps) |
8d26221d | 191 | { // we failed to resolve --> try to use outdated IP |
26b0f687 CH |
192 | GlobalLogger.info() << LogPrefix << "Checking for outdated IPs"; |
193 | bool check_up_to_date = false; | |
194 | ip = Resolver->get_next_ip(check_up_to_date); | |
195 | } | |
196 | if ( ip.is_valid() ) | |
197 | Ping->ping( | |
198 | Resolver->get_next_ip().get_ip(), | |
199 | DestinationPort, | |
200 | boost::bind(&PingScheduler::ping_done_handler, this, _1) | |
201 | ); | |
202 | else | |
203 | { // should not happen | |
204 | GlobalLogger.error() << LogPrefix << "No IP to ping " | |
205 | << "-- this should not have happened!!"; | |
206 | WantToPing = true; | |
8d26221d | 207 | if ( !Resolver->is_resolving() ) |
26b0f687 CH |
208 | start_resolving_ping_address(); |
209 | } | |
8d26221d CH |
210 | |
211 | // next time try with up-to-date IP | |
212 | ContinueOnOutdatedIps = false; | |
3f6ba924 CH |
213 | } |
214 | ||
166fd9e9 | 215 | |
23f51766 CH |
216 | //------------------------------------------------------------------------------ |
217 | // Post Processing of Ping result | |
218 | //------------------------------------------------------------------------------ | |
e58d7507 | 219 | |
c1d776ba CH |
220 | /** |
221 | * @brief called when Ping::ping is done; calls functions to update | |
222 | * statistics, ping interval and elapsed time; | |
23f51766 | 223 | * schedules a call to ping, thereby closing the loop |
c1d776ba CH |
224 | */ |
225 | void PingScheduler::ping_done_handler( const bool ping_success ) | |
502b6af0 | 226 | { |
c1d776ba | 227 | // post-processing |
079d19ab CH |
228 | // You must call these 3 methods exactly in this order |
229 | // TODO Fix this method, once it has a semantic dependency with the | |
c1d776ba | 230 | // update_ping_statistics method, because it depends on the PingAnalyzer |
a341119a | 231 | // statistics to update the exceeded_ping_failed_limit |
c1d776ba | 232 | HostAnalyzer.update_ping_statistics( ping_success ); |
d8a91bd6 | 233 | update_ping_interval(); |
c1d776ba CH |
234 | update_ping_elapsed_time(); |
235 | ||
23f51766 CH |
236 | // get next protocol, possibly start resolving IPs |
237 | update_ping_protocol(); | |
238 | ||
c1d776ba CH |
239 | // schedule next ping |
240 | (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) ); | |
23f51766 | 241 | NextPingTimer.async_wait( bind( &PingScheduler::ping, this, |
d26dce11 | 242 | boost::asio::placeholders::error ) ); |
d8a91bd6 GMF |
243 | } |
244 | ||
245 | void PingScheduler::update_ping_interval() | |
246 | { | |
c1d776ba | 247 | // have to ping more often? |
fb469ffa | 248 | if ( HostAnalyzer.exceeded_ping_failed_limit() ) |
d8a91bd6 GMF |
249 | { |
250 | PingIntervalInSec.speed_up(); | |
251 | ||
99ee8244 | 252 | GlobalLogger.debug() << "- Speeding up ping interval to: " << PingIntervalInSec << "s" |
e58d7507 | 253 | << endl; |
d8a91bd6 GMF |
254 | } |
255 | else | |
256 | { | |
257 | PingIntervalInSec.back_to_original(); | |
258 | ||
99ee8244 | 259 | GlobalLogger.debug() << "- Stick to the original ping interval: " << PingIntervalInSec << "s" |
e58d7507 | 260 | << endl; |
d8a91bd6 | 261 | } |
ced28dc7 GMF |
262 | } |
263 | ||
502b6af0 | 264 | void PingScheduler::update_ping_elapsed_time() |
ced28dc7 | 265 | { |
e5706968 | 266 | ptime now = microsec_clock::universal_time(); |
101be5ce GMF |
267 | time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec = |
268 | (now - TimeSentLastPing).total_seconds(); | |
99ee8244 | 269 | GlobalLogger.debug() << "- Time elapsed since last ping: " << elapsed_time_in_sec << "s" << endl; |
e5706968 GMF |
270 | |
271 | TimeSentLastPing = microsec_clock::universal_time(); | |
e39cc3da | 272 | } |
c1d776ba | 273 | |
23f51766 CH |
274 | |
275 | //------------------------------------------------------------------------------ | |
276 | // Ping Protocol Rotation | |
277 | //------------------------------------------------------------------------------ | |
278 | ||
26b0f687 | 279 | void PingScheduler::init_ping_protocol() |
23f51766 | 280 | { |
26b0f687 | 281 | ProtocolIter = Protocols.end(); |
23f51766 CH |
282 | get_next_ping_protocol(); |
283 | } | |
284 | ||
26b0f687 | 285 | void PingScheduler::update_ping_protocol() |
23f51766 CH |
286 | { |
287 | if ( can_change_ping_protocol() ) | |
288 | { | |
289 | get_next_ping_protocol(); | |
290 | } | |
291 | } | |
292 | ||
26b0f687 | 293 | void PingScheduler::get_next_ping_protocol() |
23f51766 | 294 | { |
26b0f687 CH |
295 | ++ProtocolIter; |
296 | if (ProtocolIter == Protocols.end()) | |
297 | ProtocolIter = Protocols.begin(); | |
298 | PingProtocol ping_protocol = *ProtocolIter; | |
299 | // --> ProtocolIter still points to currently used protocol which is | |
300 | // required in dns_resolve_callback | |
23f51766 CH |
301 | |
302 | if (Ping) | |
303 | Ping->stop_pinging(); | |
304 | ||
305 | Ping = PingerFactory::createPinger(ping_protocol, IoService, | |
306 | NetworkInterfaceName, PingReplyTimeout); | |
307 | ||
308 | update_dns_resolver( ping_protocol ); | |
309 | } | |
310 | ||
26b0f687 | 311 | bool PingScheduler::can_change_ping_protocol() const |
23f51766 CH |
312 | { |
313 | // TODO can_change_ping_protocol() and get_next_ping_protocol() may be implemented in a Algorithm | |
314 | // class that can be exchanged in this class to provide an algorithm neutral class | |
315 | return true; | |
316 | } | |
317 | ||
318 | //------------------------------------------------------------------------------ | |
319 | // DNS host name resolution | |
320 | //------------------------------------------------------------------------------ | |
26b0f687 CH |
321 | |
322 | // show "!" after host name if running on outdated IPs | |
8d26221d | 323 | void PingScheduler::update_log_prefix() |
26b0f687 CH |
324 | { |
325 | std::stringstream temp; | |
326 | temp << "PS(" << DestinationAddress; | |
327 | if (ContinueOnOutdatedIps) | |
328 | temp << "!"; | |
329 | temp << "): "; | |
330 | LogPrefix = temp.str(); | |
331 | } | |
332 | ||
333 | void PingScheduler::update_dns_resolver( PingProtocol current_protocol ) | |
23f51766 CH |
334 | { |
335 | if (Resolver && Resolver->is_resolving()) | |
336 | { | |
337 | GlobalLogger.warning() << "Resolver still seems to be resolving " | |
338 | << "--> cancel!"; | |
339 | Resolver->cancel_resolve(); | |
340 | } | |
341 | ||
342 | // DNS master caches created resolvers and resolved IPs, so this will | |
343 | // probably just return an existing resolver with already resolved IPs for | |
344 | // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important) | |
345 | Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress, | |
346 | current_protocol); | |
347 | // start resolving if no ips available | |
348 | if ( Resolver->have_up_to_date_ip() ) | |
349 | { | |
350 | if (!Resolver->is_resolving()) | |
26b0f687 | 351 | GlobalLogger.warning() << "PingScheduler: have up to date IPs but " |
23f51766 CH |
352 | << "resolver seems to be resolving all the same... " |
353 | << "Start pinging anyway!"; | |
8d26221d | 354 | ping_when_ready(); |
23f51766 CH |
355 | } |
356 | else | |
357 | start_resolving_ping_address(); | |
358 | } | |
359 | ||
26b0f687 | 360 | void PingScheduler::start_resolving_ping_address() |
23f51766 | 361 | { |
26b0f687 | 362 | Resolver->async_resolve( boost::bind(&PingScheduler::dns_resolve_callback, |
23f51766 CH |
363 | this, _1, _2) ); |
364 | } | |
365 | ||
26b0f687 CH |
366 | void PingScheduler::dns_resolve_callback(const bool was_success, |
367 | const int cname_count) | |
23f51766 | 368 | { |
26b0f687 | 369 | GlobalLogger.info() << "PingScheduler: dns resolution finished " |
23f51766 CH |
370 | << "with success = " << was_success << " " |
371 | << "and cname_count = " << cname_count; | |
372 | ||
26b0f687 CH |
373 | // TODO this is too simple, but need to think more about how to update here! |
374 | // (may have to switch back some time to resolver for original host or so | |
8d26221d | 375 | ContinueOnOutdatedIps = !was_success; |
26b0f687 CH |
376 | update_log_prefix(); |
377 | ||
378 | if ( was_success ) | |
379 | { | |
380 | HostAnalyzer.set_resolved_ip_count( Resolver->get_resolved_ip_count()); | |
8d26221d | 381 | ping_when_ready(); |
26b0f687 CH |
382 | } |
383 | else | |
384 | { // host name resolution failed; try again bypassing first outdated CNAME | |
8d26221d | 385 | // or using cached IP |
26b0f687 CH |
386 | |
387 | std::string skip_host = Resolver->get_skip_cname(); | |
23f51766 CH |
388 | |
389 | if (skip_host.empty()) | |
8d26221d | 390 | { // continue with IP |
23f51766 CH |
391 | GlobalLogger.notice() << LogPrefix << "DNS failed, " |
392 | << "try anyway with cached data"; | |
26b0f687 | 393 | HostAnalyzer.set_resolved_ip_count(0); |
8d26221d | 394 | ping_when_ready(); |
23f51766 CH |
395 | } |
396 | else | |
8d26221d | 397 | { // have CNAME to continue |
23f51766 CH |
398 | GlobalLogger.notice() << LogPrefix << "DNS failed, " |
399 | << "try again skipping a CNAME and resolving " | |
400 | << skip_host << " directly"; | |
401 | Resolver = DnsMaster::get_instance() | |
26b0f687 | 402 | ->get_resolver_for(skip_host, *ProtocolIter); |
23f51766 | 403 | start_resolving_ping_address(); |
8d26221d CH |
404 | |
405 | // (the original resolver is still alive and cached by DnsMaster and | |
406 | // counting down time to re-try on its own until cancel_resolve) | |
23f51766 CH |
407 | } |
408 | } | |
23f51766 | 409 | } |