namespace Config
{
- const int ResolveTimeoutSeconds = 5;
+ const int ResolveTimeoutSeconds = 0;
const int PauseBeforeRetrySeconds = 10;
const int StaleDataLongtermMinutes = 15;
const int DNS_PORT = 53;
, LogPrefix( "DnsResolver" )
, RandomIdGenerator()
, RequestId( 0 )
+ , Recursor()
+ , OperationCancelled( false )
{
std::stringstream temp;
temp << "Dns(" << ResolverBase::Hostname << "): ";
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 )
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();
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);
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;
}
// 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
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;
}
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;
}
}
-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
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();
}
_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
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
<< 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(
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";
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() )
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>(
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)