const int first_delay
) :
+ IoService( io_serv ),
+ NetworkInterfaceName( network_interface ),
+ Resolver(),
+ DestinationAddress( destination_address ),
+ DestinationPort( destination_port ),
NextPingTimer( *io_serv ),
- NextAddressTimer( *io_serv ),
TimeSentLastPing( microsec_clock::universal_time() ),
PingIntervalInSec( ping_interval_in_sec ),
- AddressResolveIntervalInSec( ping_interval_in_sec ),
HostAnalyzer( destination_address, ping_fail_percentage_limit, link_analyzer ),
FirstDelay( first_delay ),
- AddressResolutionAttempts( 0 ),
- EverHadAnyIP( false ),
Ping()
{
BOOST_ASSERT( !network_interface.empty() );
BOOST_ASSERT( !destination_address.empty() );
- BOOST_ASSERT( 0 < destination_port );
+ BOOST_ASSERT( ( 0 < destination_port ) &&
+ ( destination_port < numeric_limits<uint16_t>::max() ) );
BOOST_ASSERT( 0 < ping_interval_in_sec );
- BOOST_ASSERT( (0 <= ping_fail_percentage_limit) && (ping_fail_percentage_limit <= 100) );
-
- Ping = PingerFactory::createPinger(
- ping_protocol_list,
- io_serv,
- network_interface,
- destination_address,
- destination_port,
- resolved_ip_ttl_threshold,
- ping_reply_timeout
- );
+ BOOST_ASSERT( (0 <= ping_fail_percentage_limit) &&
+ ( ping_fail_percentage_limit <= 100) );
+
+ std::stringstream temp;
+ temp << "PS(" << DestinationAddress << "): ";
+ LogPrefix = temp.str();
+
+ // fill circular buffer with protocols
+ BOOST_FOREACH( const PingProtocol &prot, protocol_list )
+ ProtocolRotate.push_back(prot);
+
+ init_ping_protocol();
}
/**
// stop pinger, which will probably call ping_done_handler --> re-new NextPingTimer
GlobalLogger.debug() << "scheduler: stop pinging" << endl;
Ping->stop_pinging();
+ Resolver->cancel_resolve();
// now cancel the own timer
GlobalLogger.debug() << "scheduler: cancel timer" << endl;
}
/**
- * @brief Start into infinite loop of calls to resolve_and_ping
+ * @brief Start into infinite loop of calls to ping
*
* Does not start yet but set NextPingTimer (possibly to 0), so action starts
* when io_service is started
*/
void PingScheduler::start_pinging()
{
- // assume that even at re-start there is no IP known
- EverHadAnyIP = false;
-
if ( FirstDelay > 0 )
GlobalLogger.info() << "Delaying first ping by " << FirstDelay << "s";
else
GlobalLogger.info() << "Schedule ping as soon as possible";
(void) NextPingTimer.expires_from_now( seconds( FirstDelay ) );
- NextPingTimer.async_wait( bind( &PingScheduler::resolve_and_ping, this,
+ NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
boost::asio::placeholders::error ) );
}
/**
- * @brief Check whether need to resolve host names via DNS and then
- * call ping;
- * If name resolution fails too often, reports this to HostAnalyzer
+ * @brief call Ping::ping and schedule a call to ping_done_handler when finished
*/
-void PingScheduler::resolve_and_ping(const boost::system::error_code &error)
+void PingScheduler::ping(const boost::system::error_code &error)
{
if ( error )
{ // get here, e.g. by NextPingTimer.cancel in stop_pinging
if ( error == boost::asio::error::operation_aborted )
- GlobalLogger.error() << "Timer for resolve_and_ping was cancelled! Stopping" << endl;
+ GlobalLogger.error() << "Timer for ping was cancelled! "
+ << "Stopping" << endl;
else
GlobalLogger.error() << "Received error " << error
- << " waiting for resolve_and_ping! Stopping" << endl;
+ << " waiting for ping! Stopping"
+ << endl;
return;
}
- bool ips_up_to_date = EverHadAnyIP && Ping->have_up_to_date_ip();
+ // ping as soon as dns is ready
+ WantToPing = true;
+ try_to_ping();
+}
+
- // determine if address resolution is required
- if ( !ips_up_to_date )
+void PingRotate::try_to_ping()
+{
+ if ( !WantToPing )
{
- Ping->start_resolving_ping_address(); // try to resolve
- if ( ips_up_to_date )
- {
- EverHadAnyIP = true;
- HostAnalyzer.set_resolved_ip_count( Ping->get_resolved_ip_count() );
- AddressResolutionAttempts = 0;
- }
+ GlobalLogger.info() << "PingRotate: not pinging yet (not requested to)";
+ return;
}
- // (may still have no or only outdated IPs)
+ else if ( Resolver && Resolver->is_resolving() )
+ {
+ GlobalLogger.info() << "PingRotate: not pinging yet (DNS not finished)";
+ return;
+ }
+ else if ( !Resolver )
+ // should not happen, but check anyway
+ GlobalLogger.warning() << LogPrefix << "Have no resolver!";
+ GlobalLogger.info() << "PingRotate: start ping";
+ WantToPing = false;
- if ( EverHadAnyIP )
- { // have IPs --> can ping (even if IPs are out of date)
- if ( !ips_up_to_date )
- GlobalLogger.info() << "Using outdated IPs" << endl;
- ping();
- }
- else
- { // no IPs --> try again later, possibly report offline before
- AddressResolutionAttempts++;
- if (AddressResolutionAttempts >= 10) //MaxAddressResolutionAttempts)
- {
- GlobalLogger.notice() << "No IPs after " << AddressResolutionAttempts << " DNS queries!";
- HostAnalyzer.report_dns_resolution_failure();
- }
- (void) NextAddressTimer.expires_from_now( seconds( AddressResolveIntervalInSec ) );
- NextAddressTimer.async_wait( bind( &PingScheduler::resolve_and_ping, this,
- boost::asio::placeholders::error ) );
- }
+ Ping->ping(
+ Resolver->get_next_ip().get_ip(),
+ DestinationPort,
+ boost::bind(&PingScheduler::ping_done_handler, this, _1)
+ );
}
-/**
- * @brief call Ping::ping and schedule a call to ping_done_handler when finished
- */
-void PingScheduler::ping()
-{
- Ping->ping( boost::bind(&PingScheduler::ping_done_handler, this, _1) );
-}
+//------------------------------------------------------------------------------
+// Post Processing of Ping result
+//------------------------------------------------------------------------------
/**
* @brief called when Ping::ping is done; calls functions to update
* statistics, ping interval and elapsed time;
- * schedules a call to resolve_and_ping, thereby closing the loop
+ * schedules a call to ping, thereby closing the loop
*/
void PingScheduler::ping_done_handler( const bool ping_success )
{
update_ping_interval();
update_ping_elapsed_time();
+ // get next protocol, possibly start resolving IPs
+ update_ping_protocol();
+
// schedule next ping
(void) NextPingTimer.expires_from_now( seconds( PingIntervalInSec ) );
- NextPingTimer.async_wait( bind( &PingScheduler::resolve_and_ping, this,
+ NextPingTimer.async_wait( bind( &PingScheduler::ping, this,
boost::asio::placeholders::error ) );
}
TimeSentLastPing = microsec_clock::universal_time();
}
+
+//------------------------------------------------------------------------------
+// Ping Protocol Rotation
+//------------------------------------------------------------------------------
+
+void PingRotate::init_ping_protocol()
+{
+ get_next_ping_protocol();
+}
+
+void PingRotate::update_ping_protocol()
+{
+ if ( can_change_ping_protocol() )
+ {
+ get_next_ping_protocol();
+ }
+}
+
+void PingRotate::get_next_ping_protocol()
+{
+ PingProtocol ping_protocol = ProtocolRotate.front();
+ ProtocolRotate.pop_front();
+ ProtocolRotate.push_back(ping_protocol);
+
+ if (Ping)
+ Ping->stop_pinging();
+
+ Ping = PingerFactory::createPinger(ping_protocol, IoService,
+ NetworkInterfaceName, PingReplyTimeout);
+
+ update_dns_resolver( ping_protocol );
+}
+
+bool PingRotate::can_change_ping_protocol() const
+{
+ // TODO can_change_ping_protocol() and get_next_ping_protocol() may be implemented in a Algorithm
+ // class that can be exchanged in this class to provide an algorithm neutral class
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// DNS host name resolution
+//------------------------------------------------------------------------------
+//
+void PingRotate::update_dns_resolver( PingProtocol current_protocol )
+{
+ if (Resolver && Resolver->is_resolving())
+ {
+ GlobalLogger.warning() << "Resolver still seems to be resolving "
+ << "--> cancel!";
+ Resolver->cancel_resolve();
+ }
+
+ // DNS master caches created resolvers and resolved IPs, so this will
+ // probably just return an existing resolver with already resolved IPs for
+ // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
+ Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
+ current_protocol);
+ // start resolving if no ips available
+ if ( Resolver->have_up_to_date_ip() )
+ {
+ if (!Resolver->is_resolving())
+ GlobalLogger.warning() << "PingRotate: have up to date IPs but "
+ << "resolver seems to be resolving all the same... "
+ << "Start pinging anyway!";
+ try_to_ping();
+ }
+ else
+ start_resolving_ping_address();
+}
+
+void PingRotate::start_resolving_ping_address() //lint !e1762
+{
+ Resolver->async_resolve( boost::bind(&PingRotate::dns_resolve_callback,
+ this, _1, _2) );
+}
+
+void PingRotate::dns_resolve_callback(const bool was_success,
+ const int cname_count)
+{
+ GlobalLogger.info() << "PingRotate: dns resolution finished "
+ << "with success = " << was_success << " "
+ << "and cname_count = " << cname_count;
+
+ if ( !was_success )
+ { // host name resolution failed; try again bypassing first CNAME(s)
+ std::string skip_host = Resolver->get_skipper();
+
+ if (skip_host.empty())
+ {
+ GlobalLogger.notice() << LogPrefix << "DNS failed, "
+ << "try anyway with cached data";
+ HostAnalyzer->set_resolved_ip_count(0);
+ try_to_ping();
+ }
+ else
+ {
+ GlobalLogger.notice() << LogPrefix << "DNS failed, "
+ << "try again skipping a CNAME and resolving "
+ << skip_host << " directly";
+ Resolver = DnsMaster::get_instance()
+ ->get_resolver_for(skip_host, PingRotate.back());
+ start_resolving_ping_address();
+ }
+ }
+ else
+ {
+ HostAnalyzer->set_resolved_ip_count( Resolver->get_resolved_ip_count());
+ try_to_ping();
+ }
+}