merged PingRotate into PingScheduler; fixed save/load of cache to/from file; started...
[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(),
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;
164 try_to_ping();
165}
166
c1d776ba 167
26b0f687 168void 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 */
220void 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
240void 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 259void 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 274void PingScheduler::init_ping_protocol()
23f51766 275{
26b0f687 276 ProtocolIter = Protocols.end();
23f51766
CH
277 get_next_ping_protocol();
278}
279
26b0f687 280void PingScheduler::update_ping_protocol()
23f51766
CH
281{
282 if ( can_change_ping_protocol() )
283 {
284 get_next_ping_protocol();
285 }
286}
287
26b0f687 288void 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 306bool 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
318void 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
328void 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 355void 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
361void 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}