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(), | |
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; | |
164 | try_to_ping(); | |
165 | } | |
166 | ||
c1d776ba | 167 | |
26b0f687 | 168 | void PingScheduler::try_to_ping() |
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 | |
26b0f687 CH |
187 | HostAddress ip = Resolver->get_next_ip(); |
188 | if ( !ip.is_valid() && ContinueOnOutdatedIps) | |
189 | { | |
190 | GlobalLogger.info() << LogPrefix << "Checking for outdated IPs"; | |
191 | bool check_up_to_date = false; | |
192 | ip = Resolver->get_next_ip(check_up_to_date); | |
193 | } | |
194 | if ( ip.is_valid() ) | |
195 | Ping->ping( | |
196 | Resolver->get_next_ip().get_ip(), | |
197 | DestinationPort, | |
198 | boost::bind(&PingScheduler::ping_done_handler, this, _1) | |
199 | ); | |
200 | else | |
201 | { // should not happen | |
202 | GlobalLogger.error() << LogPrefix << "No IP to ping " | |
203 | << "-- this should not have happened!!"; | |
204 | WantToPing = true; | |
205 | if ( !Resolver.is_resolving() ) | |
206 | start_resolving_ping_address(); | |
207 | } | |
3f6ba924 CH |
208 | } |
209 | ||
166fd9e9 | 210 | |
23f51766 CH |
211 | //------------------------------------------------------------------------------ |
212 | // Post Processing of Ping result | |
213 | //------------------------------------------------------------------------------ | |
e58d7507 | 214 | |
c1d776ba CH |
215 | /** |
216 | * @brief called when Ping::ping is done; calls functions to update | |
217 | * statistics, ping interval and elapsed time; | |
23f51766 | 218 | * schedules a call to ping, thereby closing the loop |
c1d776ba CH |
219 | */ |
220 | void PingScheduler::ping_done_handler( const bool ping_success ) | |
502b6af0 | 221 | { |
c1d776ba | 222 | // post-processing |
079d19ab CH |
223 | // You must call these 3 methods exactly in this order |
224 | // TODO Fix this method, once it has a semantic dependency with the | |
c1d776ba | 225 | // update_ping_statistics method, because it depends on the PingAnalyzer |
a341119a | 226 | // statistics to update the exceeded_ping_failed_limit |
c1d776ba | 227 | HostAnalyzer.update_ping_statistics( ping_success ); |
d8a91bd6 | 228 | update_ping_interval(); |
c1d776ba CH |
229 | update_ping_elapsed_time(); |
230 | ||
23f51766 CH |
231 | // get next protocol, possibly start resolving IPs |
232 | update_ping_protocol(); | |
233 | ||
c1d776ba CH |
234 | // schedule next ping |
235 | (void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) ); | |
23f51766 | 236 | NextPingTimer.async_wait( bind( &PingScheduler::ping, this, |
d26dce11 | 237 | boost::asio::placeholders::error ) ); |
d8a91bd6 GMF |
238 | } |
239 | ||
240 | void PingScheduler::update_ping_interval() | |
241 | { | |
c1d776ba | 242 | // have to ping more often? |
fb469ffa | 243 | if ( HostAnalyzer.exceeded_ping_failed_limit() ) |
d8a91bd6 GMF |
244 | { |
245 | PingIntervalInSec.speed_up(); | |
246 | ||
99ee8244 | 247 | GlobalLogger.debug() << "- Speeding up ping interval to: " << PingIntervalInSec << "s" |
e58d7507 | 248 | << endl; |
d8a91bd6 GMF |
249 | } |
250 | else | |
251 | { | |
252 | PingIntervalInSec.back_to_original(); | |
253 | ||
99ee8244 | 254 | GlobalLogger.debug() << "- Stick to the original ping interval: " << PingIntervalInSec << "s" |
e58d7507 | 255 | << endl; |
d8a91bd6 | 256 | } |
ced28dc7 GMF |
257 | } |
258 | ||
502b6af0 | 259 | void PingScheduler::update_ping_elapsed_time() |
ced28dc7 | 260 | { |
e5706968 | 261 | ptime now = microsec_clock::universal_time(); |
101be5ce GMF |
262 | time_resolution_traits_adapted64_impl::int_type elapsed_time_in_sec = |
263 | (now - TimeSentLastPing).total_seconds(); | |
99ee8244 | 264 | GlobalLogger.debug() << "- Time elapsed since last ping: " << elapsed_time_in_sec << "s" << endl; |
e5706968 GMF |
265 | |
266 | TimeSentLastPing = microsec_clock::universal_time(); | |
e39cc3da | 267 | } |
c1d776ba | 268 | |
23f51766 CH |
269 | |
270 | //------------------------------------------------------------------------------ | |
271 | // Ping Protocol Rotation | |
272 | //------------------------------------------------------------------------------ | |
273 | ||
26b0f687 | 274 | void PingScheduler::init_ping_protocol() |
23f51766 | 275 | { |
26b0f687 | 276 | ProtocolIter = Protocols.end(); |
23f51766 CH |
277 | get_next_ping_protocol(); |
278 | } | |
279 | ||
26b0f687 | 280 | void PingScheduler::update_ping_protocol() |
23f51766 CH |
281 | { |
282 | if ( can_change_ping_protocol() ) | |
283 | { | |
284 | get_next_ping_protocol(); | |
285 | } | |
286 | } | |
287 | ||
26b0f687 | 288 | void PingScheduler::get_next_ping_protocol() |
23f51766 | 289 | { |
26b0f687 CH |
290 | ++ProtocolIter; |
291 | if (ProtocolIter == Protocols.end()) | |
292 | ProtocolIter = Protocols.begin(); | |
293 | PingProtocol ping_protocol = *ProtocolIter; | |
294 | // --> ProtocolIter still points to currently used protocol which is | |
295 | // required in dns_resolve_callback | |
23f51766 CH |
296 | |
297 | if (Ping) | |
298 | Ping->stop_pinging(); | |
299 | ||
300 | Ping = PingerFactory::createPinger(ping_protocol, IoService, | |
301 | NetworkInterfaceName, PingReplyTimeout); | |
302 | ||
303 | update_dns_resolver( ping_protocol ); | |
304 | } | |
305 | ||
26b0f687 | 306 | bool PingScheduler::can_change_ping_protocol() const |
23f51766 CH |
307 | { |
308 | // TODO can_change_ping_protocol() and get_next_ping_protocol() may be implemented in a Algorithm | |
309 | // class that can be exchanged in this class to provide an algorithm neutral class | |
310 | return true; | |
311 | } | |
312 | ||
313 | //------------------------------------------------------------------------------ | |
314 | // DNS host name resolution | |
315 | //------------------------------------------------------------------------------ | |
26b0f687 CH |
316 | |
317 | // show "!" after host name if running on outdated IPs | |
318 | void update_log_prefix() | |
319 | { | |
320 | std::stringstream temp; | |
321 | temp << "PS(" << DestinationAddress; | |
322 | if (ContinueOnOutdatedIps) | |
323 | temp << "!"; | |
324 | temp << "): "; | |
325 | LogPrefix = temp.str(); | |
326 | } | |
327 | ||
328 | void PingScheduler::update_dns_resolver( PingProtocol current_protocol ) | |
23f51766 CH |
329 | { |
330 | if (Resolver && Resolver->is_resolving()) | |
331 | { | |
332 | GlobalLogger.warning() << "Resolver still seems to be resolving " | |
333 | << "--> cancel!"; | |
334 | Resolver->cancel_resolve(); | |
335 | } | |
336 | ||
337 | // DNS master caches created resolvers and resolved IPs, so this will | |
338 | // probably just return an existing resolver with already resolved IPs for | |
339 | // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important) | |
340 | Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress, | |
341 | current_protocol); | |
342 | // start resolving if no ips available | |
343 | if ( Resolver->have_up_to_date_ip() ) | |
344 | { | |
345 | if (!Resolver->is_resolving()) | |
26b0f687 | 346 | GlobalLogger.warning() << "PingScheduler: have up to date IPs but " |
23f51766 CH |
347 | << "resolver seems to be resolving all the same... " |
348 | << "Start pinging anyway!"; | |
349 | try_to_ping(); | |
350 | } | |
351 | else | |
352 | start_resolving_ping_address(); | |
353 | } | |
354 | ||
26b0f687 | 355 | void PingScheduler::start_resolving_ping_address() |
23f51766 | 356 | { |
26b0f687 | 357 | Resolver->async_resolve( boost::bind(&PingScheduler::dns_resolve_callback, |
23f51766 CH |
358 | this, _1, _2) ); |
359 | } | |
360 | ||
26b0f687 CH |
361 | void PingScheduler::dns_resolve_callback(const bool was_success, |
362 | const int cname_count) | |
23f51766 | 363 | { |
26b0f687 | 364 | GlobalLogger.info() << "PingScheduler: dns resolution finished " |
23f51766 CH |
365 | << "with success = " << was_success << " " |
366 | << "and cname_count = " << cname_count; | |
367 | ||
26b0f687 CH |
368 | // TODO this is too simple, but need to think more about how to update here! |
369 | // (may have to switch back some time to resolver for original host or so | |
370 | ContinueOnOutdatedIps = true; | |
371 | update_log_prefix(); | |
372 | ||
373 | if ( was_success ) | |
374 | { | |
375 | HostAnalyzer.set_resolved_ip_count( Resolver->get_resolved_ip_count()); | |
376 | try_to_ping(); | |
377 | } | |
378 | else | |
379 | { // host name resolution failed; try again bypassing first outdated CNAME | |
380 | ||
381 | std::string skip_host = Resolver->get_skip_cname(); | |
23f51766 CH |
382 | |
383 | if (skip_host.empty()) | |
384 | { | |
385 | GlobalLogger.notice() << LogPrefix << "DNS failed, " | |
386 | << "try anyway with cached data"; | |
26b0f687 | 387 | HostAnalyzer.set_resolved_ip_count(0); |
23f51766 CH |
388 | try_to_ping(); |
389 | } | |
390 | else | |
391 | { | |
392 | GlobalLogger.notice() << LogPrefix << "DNS failed, " | |
393 | << "try again skipping a CNAME and resolving " | |
394 | << skip_host << " directly"; | |
395 | Resolver = DnsMaster::get_instance() | |
26b0f687 CH |
396 | ->get_resolver_for(skip_host, *ProtocolIter); |
397 | // the original resolver is still alive and cached by DnsMaster | |
398 | // and counting down the time to re-try on its own | |
23f51766 CH |
399 | start_resolving_ping_address(); |
400 | } | |
401 | } | |
23f51766 | 402 | } |