created and passed first unit tests for DNS; finished recovery from PingScheduler...
[pingcheck] / src / host / pingscheduler.cpp
CommitLineData
91fcc471
TJ
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on 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 36using namespace std;
2bf8720f
GMF
37using boost::asio::io_service;
38using boost::bind;
101be5ce 39using boost::date_time::time_resolution_traits_adapted64_impl;
2bf8720f
GMF
40using boost::posix_time::microsec_clock;
41using boost::posix_time::ptime;
42using boost::posix_time::seconds;
e58d7507 43using boost::shared_ptr;
301610ca 44using 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 63PingScheduler::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 110PingScheduler::~PingScheduler()
2d591235 111{
ced28dc7
GMF
112}
113
c1d776ba 114void 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 */
132void 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 148void 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 168void 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 */
225void 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
245void 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 264void 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 279void PingScheduler::init_ping_protocol()
23f51766 280{
26b0f687 281 ProtocolIter = Protocols.end();
23f51766
CH
282 get_next_ping_protocol();
283}
284
26b0f687 285void PingScheduler::update_ping_protocol()
23f51766
CH
286{
287 if ( can_change_ping_protocol() )
288 {
289 get_next_ping_protocol();
290 }
291}
292
26b0f687 293void 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 311bool 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 323void 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
333void 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 360void 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
366void 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}