simplified dns (no self-made recursion); merge PingScheduler and PingRotate; make...
[pingcheck] / src / host / pingscheduler.cpp
index ed82e8a..2b476f4 100644 (file)
@@ -72,32 +72,35 @@ PingScheduler::PingScheduler(
         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();
 }
 
 /**
@@ -112,6 +115,7 @@ void PingScheduler::stop_pinging()
     // 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;
@@ -119,93 +123,82 @@ void PingScheduler::stop_pinging()
 }
 
 /**
- * @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 )
 {
@@ -218,9 +211,12 @@ 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 ) );
 }
 
@@ -253,3 +249,114 @@ void PingScheduler::update_ping_elapsed_time()
     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();
+    }
+}