continue implementation; first tests with recursion returned IPs but then added cance...
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Fri, 17 Apr 2015 16:52:35 +0000 (18:52 +0200)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Mon, 4 May 2015 14:57:58 +0000 (16:57 +0200)
added option to cancel Recursor if timeout is reached
sometimes re-try resolving more quickly instead of calling handle_unavailable
changed saving of ttls: save original value and creation time by adding friend class HostAddress
cache can do the recursive ip retrieval, better place than ResolverBase

src/dns/dnscache.cpp
src/dns/dnscache.h
src/dns/dnsmaster.cpp
src/dns/dnsmaster.h
src/dns/dnsresolver.cpp
src/dns/dnsresolver.h
src/dns/hostaddress.h
src/dns/ippseudoresolver.h
src/dns/resolverbase.cpp
src/dns/resolverbase.h
src/dns/timetolive.h

index 9e8d1e0..f065223 100644 (file)
@@ -25,6 +25,7 @@
 #include <fstream>
 #include <logfunc.hpp>
 #include <filefunc.hxx>   // I2n::file_exists
+#include <boost/foreach.hpp>
 #include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/asio/placeholders.hpp>
@@ -194,9 +195,8 @@ void DnsCache::update_ttl(const std::string &hostname,
                           const uint32_t new_ttl)
 {
     GlobalLogger.info() << "DnsCache: ensure TTL for IPs for " << hostname
-                        << " is below " << new_ttl;
+                        << " is below " << new_ttl << "s";
     HostAddressVec ips = IpCache[hostname];
-    uint32_t current_ttl;
     TimeToLive current_ttl;
     BOOST_FOREACH( HostAddress &addr, ips )
     {
@@ -208,7 +208,7 @@ void DnsCache::update_ttl(const std::string &hostname,
             HasChanged = true;
         }
     }
-    IpCache[hostname] = ip;
+    IpCache[hostname] = ips;
 }
 
 
@@ -222,11 +222,24 @@ HostAddressVec& DnsCache::get_ips(const std::string &hostname)
 std::string& DnsCache::get_cname(const std::string &hostname)
 {
     GlobalLogger.info() << "DnsCache: request CNAME for " << hostname
-                        << " --> " << CnameCache[hostname];
+                        << " --> \"" << CnameCache[hostname] << "\"";
     return CnameCache[hostname];
 }
 
-
+HostAddressVec& DnsCache::get_ips_recursive(const std::string &hostname)
+{
+    std::string current_host = hostname;
+    HostAddressVec& result = get_ips(current_host);
+    while ( result.empty() )
+    {
+        current_host = get_cname(current_host);
+        if (current_host.empty())
+            break;
+        else
+            result = get_ips(current_host);
+    }
+    return result;
+}
 
 // (created using vim -- the world's best text editor)
 
index d48f6c2..c3879ae 100644 (file)
@@ -49,6 +49,7 @@ public:
     void update_ttl(const std::string &hostname, const uint32_t ttl);
     HostAddressVec& get_ips(const std::string &hostname);
     std::string& get_cname(const std::string &hostname);
+    HostAddressVec& get_ips_recursive(const std::string &hostname);
 
 // variables
 private:
index 5fa5139..0346fc5 100644 (file)
@@ -109,7 +109,7 @@ ResolverItem& DnsMaster::get_resolver_for( const std::string &hostname,
 
 
 ResolverItem& DnsMaster::get_resolver_for(const std::string &hostname,
-                                    const DnsIpProtocol &protocol)
+                                          const DnsIpProtocol &protocol)
 {
     DnsMasterItem master = get_instance();
 
@@ -212,5 +212,16 @@ int DnsMaster::get_max_address_resolution_attempts() const
     return MaxAddressResolutionAttempts;
 }
 
+std::string to_string(const DnsIpProtocol &protocol)
+{
+    switch (protocol)
+    {
+        case DNS_IPv4:  return "IPv4"; break;
+        case DNS_IPv6:  return "IPv6"; break;
+        case DNS_IPALL: return "IPv4/6"; break;
+        default: GlobalLogger.warning() << "Unexpected protocol in to_string!";
+                 return "Unexpected Protocol"; break;
+    }
+}
 // (created using vim -- the world's best text editor)
 
index c47a3d2..128633f 100644 (file)
@@ -59,6 +59,7 @@ const DnsIpProtocol DNS_IPALL = boost::net::dns::type_all;
 typedef std::pair<std::string, DnsIpProtocol> resolver_key_type;
 typedef std::map<resolver_key_type, ResolverItem> resolver_map_type;
 
+std::string to_string(const DnsIpProtocol &protocol);
 
 class DnsMaster : boost::noncopyable
 {
@@ -67,7 +68,7 @@ public:
     ResolverItem& get_resolver_for(const std::string &hostname,
                                    const PingProtocol &ping_protocol);
     ResolverItem& get_resolver_for(const std::string &hostname,
-                                   const DnsIpProtocol &protocol)
+                                   const DnsIpProtocol &protocol);
     ResolverItem get_recursor_for(const std::string &hostname,
                                   const DnsIpProtocol &protocol,
                                   const boost::asio::ip::address &name_server);
index b127c3d..5a9cef2 100644 (file)
@@ -45,7 +45,7 @@ using boost::posix_time::minutes;
 
 namespace Config
 {
-    const int ResolveTimeoutSeconds = 5;
+    const int ResolveTimeoutSeconds = 0;
     const int PauseBeforeRetrySeconds = 10;
     const int StaleDataLongtermMinutes = 15;
     const int DNS_PORT = 53;
@@ -72,6 +72,8 @@ DnsResolver::DnsResolver(IoServiceItem &io_serv,
     , LogPrefix( "DnsResolver" )
     , RandomIdGenerator()
     , RequestId( 0 )
+    , Recursor()
+    , OperationCancelled( false )
 {
     std::stringstream temp;
     temp << "Dns(" << ResolverBase::Hostname << "): ";
@@ -82,12 +84,12 @@ DnsResolver::DnsResolver(IoServiceItem &io_serv,
 DnsResolver::~DnsResolver()
 {
     boost::system::error_code error;
-    Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
-    if ( error )
-        GlobalLogger.warning() << LogPrefix << "Received error " << error
-                               << " when shutting down socket for DNS";
-    // in IcmpPinger always gave an error system:9
-    // (probably EBADF: Bad file descriptor)
+    //Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
+    //if ( error )
+    //    GlobalLogger.warning() << LogPrefix << "Received error " << error
+    //                           << " when shutting down socket for DNS";
+    // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
+    // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
 
     Socket.close(error);
     if ( error )
@@ -115,8 +117,10 @@ void DnsResolver::do_resolve()
         return;
     }
     IsResolving = true;
+    OperationCancelled = false;
 
-    GlobalLogger.info() << LogPrefix << "start resolving";
+    GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
+        << to_string(Protocol) << " using name server " << NameServer;
 
     // just to be sure: cancel timers
     ResolveTimeoutTimer.cancel();
@@ -124,8 +128,7 @@ void DnsResolver::do_resolve()
     StaleDataLongtermTimer.cancel();
 
     // create DNS request
-    boost::net::dns::message dns_message( ResolverBase::Hostname,
-                                          boost::net::dns::type_a); //all ); DEBUG
+    boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
     dns_message.recursive(false);
     dns_message.action(boost::net::dns::message::query);
     dns_message.opcode(boost::net::dns::message::squery);
@@ -167,36 +170,33 @@ void DnsResolver::do_resolve()
         GlobalLogger.warning() << LogPrefix
                                << "Sending of DNS request message failed: "
                                << err.what();
-        handle_unavailable();
+        schedule_retry();
         return;
     }
 
     if ( bytes_sent == 0 )
     {
         GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
-        handle_unavailable();
+        schedule_retry();
         return;
     }
-
-    GlobalLogger.info() << LogPrefix << "resolving under way";
 }
 
 
 void DnsResolver::handle_dns_result(const boost::system::error_code &error,
                                     const std::size_t bytes_transferred)
 {
-    if ( error ==  boost::asio::error::operation_aborted )   // cancelled
-    {
-        GlobalLogger.info() << LogPrefix
-                            << "DNS resolve operation was cancelled";
-        bool was_success = false;
-        finalize_resolve(was_success);
-    }
-    else if (error)
+    if (error)
     {
         GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
-                            << error << " --> treat like unavailable";
-        handle_unavailable();
+                            << error << " --> try again after a little while";
+        schedule_retry();
+        return;
+    }
+    else if ( OperationCancelled )
+    {   // async_resolve was cancelled --> callbacks already called
+        GlobalLogger.info() << LogPrefix
+                            << "Ignoring DNS results since we were cancelled";
         return;
     }
 
@@ -211,116 +211,103 @@ void DnsResolver::handle_dns_result(const boost::system::error_code &error,
 
     // check ID
     if (RequestId != result_message.id())
-        GlobalLogger.warning() << "Received answer for request ID "
+        GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
            << std::showbase << std::hex << result_message.id()
            << " but expected ID " << RequestId;
     else
         GlobalLogger.debug() << LogPrefix << "Result has correct ID "
                                       << std::showbase << std::hex << RequestId;
+    RequestId = 0;
 
     // loop over answers, remembering ips and cnames
     // work with a regular pointer to list of answers since result_message is
     //   owner of data and that exists until end of function
     // Items in answers list are shared_ptr to resource_base_t
-    HostAddressVec ip_list;
-    std::vector<std::string> hosts_for_ips;
+    std::vector<host_addr_pair> result_ips;
     std::vector<string_pair> result_cnames;
     std::vector<string_pair> result_nameservers;
 
-    gather_results(result_message.answers(), &ip_list, &hosts_for_ips,
-                   &result_cnames, &result_nameservers);
+    gather_results(result_message.answers(), &result_ips, &result_cnames,
+                                                          &result_nameservers);
+    // results should have the logical order
+    // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
 
-    // remember cname tree (if there were any)
-    // assume each cname points to next ( source --> destination )
-    std::string source = ResolverBase::Hostname;
-    BOOST_FOREACH( const std::string &cname, result_cnames )
-    {
-        update_cache( source, cname );
-        source = cname;
-    }
+    // remember cname list (if there were any)
+    BOOST_FOREACH( const string_pair &host_and_cname, result_cnames )
+        ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
 
-    // IPs point to last CNAME (or Hostname if no cnames given)
-    if ( !ip_list.empty() )
-    {
-        update_cache( source, ip_list );
-
-        // clean up
-        bool was_success = true;
-        finalize_resolve(was_success);
-    }
+    if ( !result_ips.empty() )
+        handle_ips( result_ips );
     else if ( !result_cnames.empty() )
-    {   // no IPs but a cname --> re-start resolving with that
-        handle_cname(source);
-    }
+        // no IPs but at least one cname --> find the "last" cname and
+        // re-start resolving with that
+        handle_cname(result_cnames);
     else
     {   // no answers --> check for nameservers in authorities section
-        // and corresponding IPs in additional section
         if ( !result_nameservers.empty() )
-            GlobalLogger.warning() << "Received NS records in answers! "
-                                   << "That is quite unexpected..."
-        gather_results(result_message.authorites(), &ip_list, &hosts_for_ips,
+            GlobalLogger.warning() << LogPrefix
+                                   << "Received NS records in answers! "
+                                   << "That is quite unexpected...";
+        gather_results(result_message.authorites(), &result_ips,
                        &result_cnames, &result_nameservers);
-        gather_results(result_message.additionals(), &ip_list, &hosts_for_ips,
+        gather_results(result_message.additionals(), &result_ips,
                        &result_cnames, &result_nameservers);
 
-        int index, index_found=-1;
-        // go through name servers
+        // search for a nameserver for which an IP is given
+        bool have_recursed = false;
         BOOST_FOREACH( const string_pair &nameserver, result_nameservers )
         {
-            index = 0;
             // go through ips and look for match
-            BOOST_FOREACH( const std::string &ip_host, hosts_for_ips )
+            BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips )
             {
-                if (nameserver.second == ip_host)
+                if (nameserver.second == host_and_addr.first)
                 {
-                    index_found = index;
+                    GlobalLogger.info() << LogPrefix << "Ask next nameserver "
+                        << nameserver.second << " with IP "
+                        << host_and_addr.second.get_ip() << " (responsible for "
+                        << nameserver.first << ")";
+                    have_recursed = true;
+                    handle_recurse( host_and_addr.second );
                     break;
                 }
-                ++index;
             }
-
-            if (index_found > -1)
+            if (have_recursed)
                 break;
         }
-        if (index_found > -1)
-        {   // have a name server with ip
-            handle_recurse(ip_list[index_found]);
-
-
-    GlobalLogger.info() << LogPrefix << "Have " << result_ips.size()
-                        << " IPs and " << result_cnames.size() << " CNAMEs";
-
-    // We expect either one single CNAME and no IPs or a list of IPs.
-    // But deal with other cases as well
-    if (result_ips.empty() && result_cnames.empty())
-        handle_unavailable();   // we just got crap, this is a dead end
-
-    bool do_resolve_cnames = !Config::DnsRequestsAreRecursive;
-
-    if (Config::DnsRequestsAreRecursive && !result_cnames.empty()
-                                        && result_ips.empty() )
-    {
-        GlobalLogger.warning() << LogPrefix << "CNAMES appear to be unresolved"
-            << " although DNS requests are recursive! --> try on our own";
-        do_resolve_cnames = true;
-    }
-    else
-        GlobalLogger.info() << LogPrefix << "Ignore CNAMES, assume they were "
-                                         << "resolved";
 
-    if (do_resolve_cnames)
-    {
-        BOOST_FOREACH( const std::string &cname, result_cnames )
-            handle_cname(cname);  // will schedule another DNS call
+        if ( !have_recursed )
+        {   // no nameserver with ip found -- strange
+            if (result_nameservers.empty())
+            {
+                GlobalLogger.error() << LogPrefix << "Result contained neither "
+                    << "IP nor CNAME nor name server --> cannot proceed!";
+                handle_unavailable();
+            }
+            else
+            {
+                // TODO: check in cache for nameserver ips?
+
+                GlobalLogger.warning() << LogPrefix
+                    << "There are " << result_nameservers.size()
+                    << " nameservers given but none with IP "
+                    << "--> need to resolve nameserver -- this sucks!";
+                //handle_recurse_without_ip(result_nameservers[0].second);
+
+                // would have to create a new resolver with previous nameserver
+                // to resolve new nameserver name; save in Recursor
+                // In callback reset Recursor, get ip(s) and continue in
+                // handle_recurse
+                GlobalLogger.warning() << LogPrefix << "Have not implemented "
+                    << "resolution of name server; I sincerely hope this never "
+                    << "happens or can be dealt with more easily another way!";
+                handle_unavailable();
+            }
+        }
     }
-
-    if ( !result_ips.empty() )
-        handle_ips(result_ips);
 }
 
 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *answers,
-                                 HostAddressVec *result_ips,
-                                 std::vector<std::string> *hosts_for_ips,
+                                 std::vector<host_addr_pair> *result_ips,
                                  std::vector<string_pair> *result_cnames,
                                  std::vector<string_pair> *result_nameservers)
                                                                            const
@@ -343,8 +330,7 @@ void DnsResolver::gather_results(const boost::net::dns::rr_list_t *answers,
             boost::asio::ip::address_v4 ip =
                 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
                 ->address();
-            hosts_for_ips->push_back( domain );
-            result_ips->push_back( HostAddress(ip, ttl) );
+            result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
             GlobalLogger.debug() << LogPrefix << "IPv4 " << ip << " with TTL "
                                  << ttl << "s for " << domain;
         }
@@ -359,8 +345,7 @@ void DnsResolver::gather_results(const boost::net::dns::rr_list_t *answers,
             boost::asio::ip::address_v6 ip =
                 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
                 ->address();
-            hosts_for_ips->push_back( domain );
-            result_ips->push_back( HostAddress(ip, ttl) );
+            result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
             GlobalLogger.debug() << LogPrefix << "IPv6 " << ip << " with TTL "
                                  << ttl << "s for " << domain;
         }
@@ -404,17 +389,6 @@ void DnsResolver::gather_results(const boost::net::dns::rr_list_t *answers,
 }
 
 
-void DnsResolver::handle_ips(const HostAddressVec &ips)
-{
-    // save in cache
-    ResolverBase::update_cache( ips );
-
-    // clean up
-    bool was_success = true;
-    finalize_resolve(was_success);
-}
-
-
 void DnsResolver::handle_unavailable()
 {
     // schedule new attempt in quite a while
@@ -431,40 +405,143 @@ void DnsResolver::handle_unavailable()
     finalize_resolve(was_success);
 }
 
-void DnsResolver::handle_cname(const std::string &canonical_name)
+
+void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
 {
-    // get resolver for canonical name
-    ResolverItem resolver = DnsMaster::get_instance()
-                            ->get_resolver_for(canonical_name, Protocol);
-    callback_type callback = boost::bind( &DnsResolver::cname_resolve_callback,
-                                          this, canonical_name, _1, _2 );
-    resolver->async_resolve( callback );
+    // received at least one IP which could be for the queried host name 
+    // or the cname at the "end" of the cname list;
+    // but all IPs should be for the same
+    HostAddressVec addr_list;
+    std::string only_host_for_ips = result_ips[0].first;
+    BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
+    {
+        if ( host_and_addr.first != only_host_for_ips )
+            GlobalLogger.warning() << LogPrefix
+                << "Received IPs for different hosts " << only_host_for_ips
+                << " and " << host_and_addr.first << " in one DNS result! "
+                << "--> ignore second";
+        else
+        {
+            GlobalLogger.notice() << LogPrefix << "Found IP "
+                      << host_and_addr.second.get_ip() << " with TTL "
+                      << host_and_addr.second.get_ttl().get_value() << "s";
+            addr_list.push_back(host_and_addr.second);
+        }
+    }
+    ResolverBase::update_cache( only_host_for_ips, addr_list );
 
-    stop_trying();
+    // clean up
+    bool was_success = true;
+    finalize_resolve(was_success);
+}
+
+
+void DnsResolver::handle_cname(const std::vector<string_pair> &result_cnames)
+{
+    // find the "last" cname in the list
+    // Hostname --> cname1 --> cname2 --> ... --> cnameN
+    // We assume here that this list might not be in order but that all cnames
+    //   form a single list (without a break)
+    std::string last_cname = "";
+    bool could_be_last;
+    BOOST_REVERSE_FOREACH( const string_pair &host_and_cname, result_cnames )
+    {
+        could_be_last = true;
+        BOOST_REVERSE_FOREACH( const string_pair &other, result_cnames )
+        {
+            if (other.first == host_and_cname.second)
+            {   // found cname for current cname
+                could_be_last = false;
+                break;
+            }
+        }
+        if (could_be_last)
+        {
+            last_cname = host_and_cname.second;
+            break;
+        }
+    }
+
+    if (last_cname.empty())
+    {
+        GlobalLogger.error() << LogPrefix
+            << "Could not identify \"last\" CNAME to handle -- "
+            << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
+        GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
+        BOOST_FOREACH( const string_pair &host_and_cname, result_cnames )
+            GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
+                                             << host_and_cname.second;
+        handle_unavailable();
+    }
+    else
+    {   // check cache for IP for this cname
+        bool check_up_to_date = true;
+        HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
+                                                              check_up_to_date);
+        if ( !cached_data.empty() )
+        {
+            bool was_success = true;
+            int recursion_count = 1;  // define cache access as only 1
+            finalize_resolve(was_success, recursion_count);
+        }
+        else
+        {   // get resolver for canonical name
+            // as opposed to the interal Recursor variable used in
+            // handle_recurse, this is a "proper" resolver that is maintained
+            // and cached by DnsMaster --> independent of this Resolver
+            ResolverItem resolver = DnsMaster::get_instance()
+                                    ->get_resolver_for(last_cname, Protocol);
+            callback_type callback = boost::bind(
+                                           &DnsResolver::cname_resolve_callback,
+                                           this, _1, _2 );
+            resolver->async_resolve( callback );
+
+            // treat a CNAME as a partial result: not enough to run callbacks
+            // from finalize_resolve, but enough to stop timers and reset
+            // RetryCount --> name resolution can take longer
+            stop_trying();
+        }
+    }
 }
 
 
-void DnsResolver::cname_resolve_callback(const std::string &canonical_name,
-                                         const bool was_success,
+void DnsResolver::cname_resolve_callback(const bool was_success,
                                          const int recursion_count)
 {
-    if (was_success)
-        // tell cache to return cname's ips if queried for our hostname
-        ResolverBase::update_cache(
-                             ResolverBase::get_cached_results(canonical_name) );
+    if ( OperationCancelled )
+    {   // async_resolve was cancelled --> callbacks already called
+        GlobalLogger.info() << LogPrefix
+                            << "Ignoring CNAME results since we were cancelled";
+        return;
+    }
+    else if (was_success)
+        GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
     else
-        GlobalLogger.info() << LogPrefix << "Cname resolution failed";
+        GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
+        // no use to schedule retry in this case since cname resolver must have
+        // failed several times and we can just re-start the same procedure with
+        // the same information (in recursion can try different name server)
+        // --> no schedule_retry
 
-    // cname counts like one recursion step more...
+    // cname counts like one more recursion step ...
     finalize_resolve(was_success, recursion_count+1);
 }
 
+
 void DnsResolver::handle_recurse(const HostAddress &name_server)
 {
     // get resolver for same hostname but using a different name server
     if (Recursor)
     {
-        GlobalLogger.warning() << "Recursor has not been reset!";
+        if (Recursor->is_resolving())
+        {
+            GlobalLogger.warning() << LogPrefix << "Recursor is resolving! "
+                                   << "Will cancel and reset";
+            Recursor->cancel_resolve();
+        }
+        else
+            GlobalLogger.warning() << LogPrefix
+                                   << "Recursor has not been reset!";
         Recursor.reset();
     }
 
@@ -476,51 +553,82 @@ void DnsResolver::handle_recurse(const HostAddress &name_server)
                                        _1, _2 );
     Recursor->async_resolve( callback );
 
-    stop_trying();
+    // do not cancel timers or reset RetryCount
+    //stop_trying();
 }
 
 
-
 void DnsResolver::recursive_resolve_callback(const uint32_t min_ttl,
                                              const bool was_success,
                                              const int recursion_count)
 {
-    if (was_success)
-        // make sure the saved TTL is not larger than the one we found here
-        ResolverBase::update_cache_ttl(min_ttl);
-    else
-        GlobalLogger.info() << LogPrefix << "Recursive resolution failed";
+    GlobalLogger.debug()
+        << "Recursion back at request with name server " << NameServer;
 
     // do not need recursor any more; next time re-create from different random
-    //    name server
+    //    name server; may have been reset already in cancel_resolve(), so that
+    //    is ok. If not, issue a warning
     if ( !Recursor )
-        GlobalLogger.warning() << "Recursor was reset before callback!";
+    {
+        if ( !OperationCancelled )
+            GlobalLogger.warning() << LogPrefix
+                                   << "Recursor was reset before callback!";
+    }
     else
         Recursor.reset();
 
-    finalize_resolve(was_success, recursion_count+1);
-
+    f ( OperationCancelled )
+    {   // async_resolve was cancelled --> callbacks already called
+        GlobalLogger.info() << LogPrefix
+                        << "Ignoring recursion results since we were cancelled";
+        return;
+    }
+    else if (was_success)
+    {
+        // make sure the saved TTL is not larger than the one we found here
+        ResolverBase::update_cache_ttl(min_ttl);
+        finalize_resolve(was_success, recursion_count+1);
+    }
+    else
+    {
+        GlobalLogger.info() << LogPrefix << "Recursive resolution failed";
+        schedule_retry();
+    }
 }
 
 
 void DnsResolver::finalize_resolve(const bool was_success,
                                    const int recursion_count)
 {
+    // some consistency checks; failure might indicate a situation I had not
+    // anticipated during programming but might not be harmfull yet
+    if ( !IsResolving )
+        GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
+                                            << "not resolving any more!";
+    if ( OperationCancelled )
+        GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
+                                            << " was cancelled!";
+    if ( ResolverBase::CallbackList.empty() )
+        GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
+                                            << "no callbacks!";
+    if ( RequestId != 0 )
+        GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
+                                            << "waiting for DNS reply!";
+
     // stop timers
-    if (recursion_count > 0)
-        stop_trying();
-    // else was called already from handle_cname
+    stop_trying();
 
     // schedule callbacks, clearing callback list
     ResolverBase::schedule_callbacks(was_success, recursion_count);
 
     // finalize
-    GlobalLogger.notice() << LogPrefix << "Done resolving"
+    GlobalLogger.notice() << LogPrefix << "finalized resolve"
                           << " with success = " << was_success
                           << " and recursion_count = " << recursion_count;
     IsResolving = false;
 }
 
+
 void DnsResolver::stop_trying()
 {
     // cancel timers
@@ -533,6 +641,47 @@ void DnsResolver::stop_trying()
     RetryCount = 0;
 }
 
+
+bool DnsResolver::is_resolving()
+{
+    return IsResolving;
+}
+
+
+void DnsResolver::cancel_resolve()
+{
+    if ( !IsResolving )
+    {
+        GlobalLogger.info() << LogPrefix
+               << "Cancel called on non-resolving resolver -- ignore";
+        return;
+    }
+    else if (OperationCancelled)
+    {
+        GlobalLogger.info() << LogPrefix
+               << "Cancel called on cancelled resolver -- ignore";
+        return;
+    }
+
+    if ( Recursor )
+        Recursor->cancel_resolve(); // does not hurt even if it is not resolving
+
+    // set before finalize_resolve so can check in finalize_resolve that ID is
+    //   always 0;  ID is not used any more since handle_dns_result stops if
+    //   OperationCancelled is true
+    RequestId = 0;
+
+    bool was_success = false;
+    int recursion_count = 1;
+    finalize_resolve(was_success, recursion_count);
+
+    // set after finalize_resolve, so can check in finalize_resolve that 
+    // cancel is never true
+    OperationCancelled = true;
+
+}
+
+
 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
 {
     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
@@ -548,20 +697,46 @@ void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
                                << error;
         return;
     }
+    else if ( OperationCancelled )
+    {   // async_resolve was cancelled --> callbacks already called
+        GlobalLogger.info() << LogPrefix
+                        << "Ignoring DNS timeout since we were cancelled";
+        return;
+    }
+    else
+    {
+        GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
+
+        schedule_retry();
+    }
+}
+
 
-    GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
+void DnsResolver::schedule_retry()
+{
+    // clean up a bit
+    if ( Recursor )
+    {
+        Recursor.cancel();
+        Recursor.reset();
+    }
+    ResolveTimeoutTimer.cancel();
+    PauseBeforeRetryTimer.cancel();
 
     // increment timer
     ++RetryCount;
 
     if ( RetryCount > DnsMaster::get_instance()
                       ->get_max_address_resolution_attempts() )
-    {
-        handle_unavailable();
-        RetryCount = 0;
+    {   // too many re-tries
+        GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
+                            << "RetryCount " << RetryCount << " too high";
+        handle_unavailable();   // will call stop_trying i.e. reset RetryCount
     }
     else
     {   // schedule retry
+        GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
+                            << RetryCount << ")";
         PauseBeforeRetryTimer.expires_from_now(
                 seconds(Config::PauseBeforeRetrySeconds));
         PauseBeforeRetryTimer.async_wait(
@@ -574,12 +749,26 @@ void DnsResolver::wait_timer_timeout_handler(
                                          const boost::system::error_code &error)
 {
     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
+    {   // assume that our code cancelled this timer, so callbacks will be
+        // taken care of!
         GlobalLogger.warning() << LogPrefix
-                               << "Resolve timeout timer was cancelled!";
+                               << "Resolve wait timer was cancelled! ";
+    }
     else if (error)
+    {   // not sure what to do here, but callers waiting forever for a callback
+        // is probably the worst thing to happen, so call finalize_resolve
         GlobalLogger.warning() << LogPrefix
-                               << "resolve timeout handler received error "
-                               << error;
+                               << "resolve wait handler received error "
+                               << error << "! Try to finalize resolve";
+        bool was_success = false;
+        finalize_resolve(was_success);
+    }
+    else if ( OperationCancelled )
+    {   // async_resolve was cancelled --> callbacks already called
+        GlobalLogger.info() << LogPrefix
+                          << "Ignoring waiting timeout since we were cancelled";
+        return;
+    }
     else
     {
         GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
@@ -595,7 +784,7 @@ void DnsResolver::wait_timer_timeout_handler(
 HostAddress DnsResolver::get_next_ip()
 {
     // get cached data
-    HostAddressVec cached_data = ResolverBase::get_cached_results();
+    HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
 
     // if no results cached, return default-constructed HostAddress (0.0.0.0)
     if ( cached_data.empty() )
@@ -615,7 +804,7 @@ HostAddress DnsResolver::get_next_ip()
 bool DnsResolver::have_up_to_date_ip()
 {
     // get cached data
-    HostAddressVec cached_data = ResolverBase::get_cached_results();
+    HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
 
     // get threshold
     uint32_t resolved_ip_ttl_threshold = static_cast<uint32_t>(
@@ -635,7 +824,7 @@ bool DnsResolver::have_up_to_date_ip()
 
 int DnsResolver::get_resolved_ip_count()
 {
-    return ResolverBase::get_cached_results().size();
+    return ResolverBase::get_cached_ips_recursively().size();
 }
 
 // (created using vim -- the world's best text editor)
index 7069610..15d2a28 100644 (file)
@@ -38,6 +38,7 @@
 
 
 typedef std::pair<std::string, std::string> string_pair;
+typedef std::pair<std::string, HostAddress> host_addr_pair;
 
 
 class DnsResolver : public ResolverBase
@@ -50,6 +51,10 @@ public:
     friend ResolverItem& DnsMaster::get_resolver_for(
                                                  const std::string &hostname,
                                                  const DnsIpProtocol &protocol);
+    friend ResolverItem DnsMaster::get_recursor_for(
+                                   const std::string &hostname,
+                                   const DnsIpProtocol &protocol,
+                                   const boost::asio::ip::address &name_server);
 private:
     DnsResolver(IoServiceItem &io_serv,
                 const std::string &hostname,
@@ -62,6 +67,8 @@ public:
     HostAddress get_next_ip();
     bool have_up_to_date_ip();
     int get_resolved_ip_count();
+    void cancel_resolve();
+    bool is_resolving();
 
 // implementation of ResolverBase::async_resolve
 protected:
@@ -73,11 +80,10 @@ private:
     void handle_dns_result(const boost::system::error_code &error,
                            const std::size_t bytes_transferred);
     void handle_unavailable();
-    void handle_ips(const HostAddressVec &ips);
-    void handle_cname(const std::string &canonical_name);
+    void handle_ips(const std::vector<host_addr_pair> &result_ips);
+    void handle_cname(const std::vector<string_pair> &result_cnames);
     void handle_recurse(const HostAddress &name_server);
-    void cname_resolve_callback(const std::string &canonical_name,
-                                const bool was_success,
+    void cname_resolve_callback(const bool was_success,
                                 const int recursion_count);
     void recursive_resolve_callback(const uint32_t min_ttl,
                                     const bool was_success,
@@ -86,7 +92,7 @@ private:
     void stop_trying();
     void wait_timer_timeout_handler(const boost::system::error_code &error);
     void gather_results( const boost::net::dns::rr_list_t *answers,
-                         HostAddressVec *result_ips,
+                         std::vector<host_addr_pair> *result_ips,
                          std::vector<string_pair> *result_cnames,
                          std::vector<string_pair> *result_nameservers ) const;
 
@@ -107,6 +113,7 @@ private:
     boost::uuids::random_generator RandomIdGenerator;
     uint16_t RequestId;
     ResolverItem Recursor;
+    bool OperationCancelled;
 };
 
 #endif
index 2722228..9696843 100644 (file)
@@ -66,36 +66,28 @@ private:
     void save(Archive & ar, const unsigned int version) const
     {
         std::string ip = Ip.to_string();
-        std::string now_str = boost::posix_time::to_iso_string(
-                             boost::posix_time::second_clock::universal_time());
-        uint32_t ttl_seconds_now = Ttl.get_updated_value();
-
+        std::string ttl_creation_time = boost::posix_time::to_iso_string(
+                                                                Ttl.TtlSetTime);
         ar & BOOST_SERIALIZATION_NVP(ip);
-        ar & BOOST_SERIALIZATION_NVP(now_str);
-        ar & BOOST_SERIALIZATION_NVP(ttl_seconds_now);
+        ar & BOOST_SERIALIZATION_NVP(Ttl.Ttl);
+        ar & BOOST_SERIALIZATION_NVP(ttl_creation_time);
     }
 
     template<class Archive>
     void load(Archive & ar, const unsigned int version)
     {
         std::string ip;
-        std::string save_time_str;
-        uint32_t ttl_seconds_at_save;
+        uint32_t ttl_seconds;
+        std::string ttl_creation_time;
         ar & BOOST_SERIALIZATION_NVP(ip);
-        ar & BOOST_SERIALIZATION_NVP(save_time_str);
-        ar & BOOST_SERIALIZATION_NVP(ttl_seconds_at_save);
+        ar & BOOST_SERIALIZATION_NVP(ttl_seconds);
+        ar & BOOST_SERIALIZATION_NVP(ttl_creation_time);
 
         // now convert to Ip and Ttl
         Ip = boost::asio::ip::address::from_string(ip);
-        boost::posix_time::ptime save_time =
-                              boost::posix_time::from_iso_string(save_time_str);
-        boost::posix_time::ptime now =
-                              boost::posix_time::second_clock::universal_time();
-        uint32_t elapsed_seconds_since_save = (save_time - now).total_seconds();
-        if (elapsed_seconds_since_save > ttl_seconds_at_save)
-            Ttl = TimeToLive(0);
-        else
-            Ttl = TimeToLive(ttl_seconds_at_save - elapsed_seconds_since_save);
+        Ttl = TimeToLive();
+        Ttl.Ttl = ttl_seconds;
+        Ttl.TtlSetTime = boost::posix_time::from_iso_string(ttl_creation_time);
     }
 
     BOOST_SERIALIZATION_SPLIT_MEMBER()
index c165511..f508749 100644 (file)
@@ -66,6 +66,8 @@ public:
     HostAddress get_next_ip()  { return IpAddress;  }
     bool have_up_to_date_ip()  { return true;       }
     int get_resolved_ip_count(){ return 1;          }
+    bool is_resolving()        { return false;      }
+    void cancel_resolve() {}
 
 // implementation of ResolverBase::async_resolve
 protected:
index 6060729..721fc1e 100644 (file)
@@ -52,6 +52,14 @@ ResolverBase::ResolverBase(const IoServiceItem &io_serv,
     , CallbackList()
 {}
 
+void ResolverBase::update_cache( const std::string &hostname,
+                                 const HostAddressVec &new_results ) const
+{   Cache->update( hostname, new_results );  }
+
+void ResolverBase::update_cache( const std::string &hostname,
+                                 const std::string &cname ) const
+{   Cache->update( hostname, cname );  }
+
 void ResolverBase::update_cache( const HostAddressVec &new_results ) const
 {   Cache->update( Hostname, new_results );  }
 
@@ -61,12 +69,13 @@ void ResolverBase::update_cache( const std::string &cname ) const
 void ResolverBase::update_cache_ttl( const uint32_t ttl ) const
 {   Cache->update_ttl( Hostname, ttl );  }
 
-HostAddressVec& ResolverBase::get_cached_results(const std::string host) const
+HostAddressVec& ResolverBase::get_cached_ips_recursively(const std::string host)
+                                                                           const
 {
     if (host.empty())
-        return Cache->get_data( Hostname );
+        return Cache->get_ips_recursive(Hostname);
     else
-        return Cache->get_data( host );
+        return Cache->get_ips_recursive(host);
 }
 
 void ResolverBase::schedule_callbacks(const bool was_success,
index 2dd435e..a063c6c 100644 (file)
@@ -48,6 +48,8 @@ public:
      *                       const int cname_count)
      */
     void async_resolve(const callback_type &callback);
+    virtual void cancel_resolve() = 0;
+    virtual bool is_resolving() = 0;
 
     virtual HostAddress get_next_ip() = 0;
     virtual bool have_up_to_date_ip() = 0;
@@ -71,9 +73,13 @@ protected:
 
     void update_cache( const HostAddressVec &new_results ) const;
     void update_cache( const std::string &cname ) const;
+    void update_cache( const std::string &hostname,
+                       const HostAddressVec &new_results ) const;
+    void update_cache( const std::string &hostname,
+                       const std::string &cname ) const;
     void update_cache_ttl( const uint32_t ttl ) const;
 
-    HostAddressVec& get_cached_results(const std::string host="") const;
+    HostAddressVec& get_cached_ips_recursively(const std::string host="") const;
 
     void schedule_callbacks(const bool was_success,
                             const int cname_count);
index cfd5168..e56d9eb 100644 (file)
@@ -23,6 +23,10 @@ on this file might be covered by the GNU General Public License.
 #include <stdint.h>
 
 #include <boost/asio.hpp>
+//#include "dns/hostaddress.h"
+
+// forward declaration
+class HostAddress;
 
 //-----------------------------------------------------------------------------
 // TimeToLive
@@ -40,6 +44,14 @@ public:
     uint32_t get_updated_value() const;
 
 private:
+    // required for saving and loading TtlSetTime to original value
+    friend class HostAddress;
+    //template<class Archive>
+    //friend void HostAddress::load(Archive & ar, const unsigned int version);
+    //template<class Archive>
+    //friend void HostAddress::save(Archive & ar, const unsigned int version)
+    //                                                                    const;
+
     /// the numeric time-to-live
     uint32_t Ttl;
     /// the time when the time-to-live was set, so it is possible to know the