From e18c1337ff87b02406dbefdc63153c32a3703437 Mon Sep 17 00:00:00 2001 From: Christian Herdtweck Date: Fri, 17 Apr 2015 18:52:35 +0200 Subject: [PATCH] continue implementation; first tests with recursion returned IPs but then added cancel code 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 | 23 ++- src/dns/dnscache.h | 1 + src/dns/dnsmaster.cpp | 13 +- src/dns/dnsmaster.h | 3 +- src/dns/dnsresolver.cpp | 497 ++++++++++++++++++++++++++++++-------------- src/dns/dnsresolver.h | 17 +- src/dns/hostaddress.h | 30 +-- src/dns/ippseudoresolver.h | 2 + src/dns/resolverbase.cpp | 15 +- src/dns/resolverbase.h | 8 +- src/dns/timetolive.h | 12 + 11 files changed, 432 insertions(+), 189 deletions(-) diff --git a/src/dns/dnscache.cpp b/src/dns/dnscache.cpp index 9e8d1e0..f065223 100644 --- a/src/dns/dnscache.cpp +++ b/src/dns/dnscache.cpp @@ -25,6 +25,7 @@ #include #include #include // I2n::file_exists +#include #include #include #include @@ -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) diff --git a/src/dns/dnscache.h b/src/dns/dnscache.h index d48f6c2..c3879ae 100644 --- a/src/dns/dnscache.h +++ b/src/dns/dnscache.h @@ -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: diff --git a/src/dns/dnsmaster.cpp b/src/dns/dnsmaster.cpp index 5fa5139..0346fc5 100644 --- a/src/dns/dnsmaster.cpp +++ b/src/dns/dnsmaster.cpp @@ -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) diff --git a/src/dns/dnsmaster.h b/src/dns/dnsmaster.h index c47a3d2..128633f 100644 --- a/src/dns/dnsmaster.h +++ b/src/dns/dnsmaster.h @@ -59,6 +59,7 @@ const DnsIpProtocol DNS_IPALL = boost::net::dns::type_all; typedef std::pair resolver_key_type; typedef std::map 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); diff --git a/src/dns/dnsresolver.cpp b/src/dns/dnsresolver.cpp index b127c3d..5a9cef2 100644 --- a/src/dns/dnsresolver.cpp +++ b/src/dns/dnsresolver.cpp @@ -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 hosts_for_ips; + std::vector result_ips; std::vector result_cnames; std::vector 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 *hosts_for_ips, + std::vector *result_ips, std::vector *result_cnames, std::vector *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 (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 (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 &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 &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( @@ -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) diff --git a/src/dns/dnsresolver.h b/src/dns/dnsresolver.h index 7069610..15d2a28 100644 --- a/src/dns/dnsresolver.h +++ b/src/dns/dnsresolver.h @@ -38,6 +38,7 @@ typedef std::pair string_pair; +typedef std::pair 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 &result_ips); + void handle_cname(const std::vector &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 *result_ips, std::vector *result_cnames, std::vector *result_nameservers ) const; @@ -107,6 +113,7 @@ private: boost::uuids::random_generator RandomIdGenerator; uint16_t RequestId; ResolverItem Recursor; + bool OperationCancelled; }; #endif diff --git a/src/dns/hostaddress.h b/src/dns/hostaddress.h index 2722228..9696843 100644 --- a/src/dns/hostaddress.h +++ b/src/dns/hostaddress.h @@ -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 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() diff --git a/src/dns/ippseudoresolver.h b/src/dns/ippseudoresolver.h index c165511..f508749 100644 --- a/src/dns/ippseudoresolver.h +++ b/src/dns/ippseudoresolver.h @@ -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: diff --git a/src/dns/resolverbase.cpp b/src/dns/resolverbase.cpp index 6060729..721fc1e 100644 --- a/src/dns/resolverbase.cpp +++ b/src/dns/resolverbase.cpp @@ -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, diff --git a/src/dns/resolverbase.h b/src/dns/resolverbase.h index 2dd435e..a063c6c 100644 --- a/src/dns/resolverbase.h +++ b/src/dns/resolverbase.h @@ -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); diff --git a/src/dns/timetolive.h b/src/dns/timetolive.h index cfd5168..e56d9eb 100644 --- a/src/dns/timetolive.h +++ b/src/dns/timetolive.h @@ -23,6 +23,10 @@ on this file might be covered by the GNU General Public License. #include #include +//#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 + //friend void HostAddress::load(Archive & ar, const unsigned int version); + //template + //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 -- 1.7.1