#include <boost/function.hpp>
#include <boost/net/dns.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
#include <logfunc.hpp>
const int PauseBeforeRetrySeconds = 10;
const int StaleDataLongtermMinutes = 15;
const int DNS_PORT = 53;
- const int UniqueID = 0xaffe;
}
DnsResolver::DnsResolver(IoServiceItem &io_serv,
, Socket( *io_serv, ip::udp::endpoint(ip::udp::v4(), 0))
// just connect to anything, will specify sender/receiver later
, ReceiveBuffer()
+ , RequestBuffer()
, Protocol( protocol )
, NameServer( name_server, Config::DNS_PORT )
, ResolveTimeoutTimer( *io_serv )
, RetryCount( 0 )
, IsResolving( false )
, LogPrefix( "DnsResolver" )
+ , RandomIdGenerator()
+ , RequestId( 0 )
{
std::stringstream temp;
temp << "Dns(" << ResolverBase::Hostname << "): ";
LogPrefix = temp.str();
+
}
DnsResolver::~DnsResolver()
<< "Call to do_resolve ignored since resolving already";
return;
}
+ IsResolving = true;
GlobalLogger.info() << LogPrefix << "start resolving";
// create DNS request
boost::net::dns::message dns_message( ResolverBase::Hostname,
- boost::net::dns::type_all );
+ boost::net::dns::type_a); //all ); DEBUG
dns_message.recursive(false);
dns_message.action(boost::net::dns::message::query);
dns_message.opcode(boost::net::dns::message::squery);
- dns_message.id(Config::UniqueID);
- boost::net::dns_buffer_t request_buffer;
- dns_message.encode(request_buffer);
+
+ // create random ID for message
+ boost::uuids::uuid message_id = RandomIdGenerator();
+ memcpy( &RequestId, message_id.data, sizeof(RequestId) );
+ dns_message.id( RequestId );
+ GlobalLogger.debug() << LogPrefix << "Request has ID "
+ << std::showbase << std::hex << dns_message.id();
// setup receipt of reply
Socket.async_receive_from(
this, boost::asio::placeholders::error) );
// send dns request
+ dns_message.encode(RequestBuffer);
size_t bytes_sent;
try
{
bytes_sent = Socket.send_to(
- boost::asio::buffer(request_buffer.get_array()),
+ boost::asio::buffer(RequestBuffer.get_array()),
NameServer );
}
catch (boost::system::system_error &err)
void DnsResolver::handle_dns_result(const boost::system::error_code &error,
- const std::size_t bytes_transferred)
+ const std::size_t bytes_transferred)
{
if ( error == boost::asio::error::operation_aborted ) // cancelled
{
return;
}
+ GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
+ << bytes_transferred << " bytes transferred)";
+
// next 3(+1) lines copied from boost/net/dns/resolver.hpp:
// clamp the recvBuffer with the number of bytes transferred or decode buffr
ReceiveBuffer.length(bytes_transferred);
boost::net::dns::message result_message;
result_message.decode( ReceiveBuffer );
+ // check ID
+ if (RequestId != result_message.id())
+ GlobalLogger.warning() << "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;
+
+ // 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
- boost::net::dns::rr_list_t *answers = result_message.answers();
- if (answers->size() == 0)
- handle_unavailable();
+ HostAddressVec ip_list;
+ std::vector<std::string> hosts_for_ips;
+ std::vector<string_pair> result_cnames;
+ std::vector<string_pair> result_nameservers;
- // loop over answers, remembering ips and cnames
- HostAddressVec result_ips;
- std::vector<std::string> result_cnames;
+ gather_results(result_message.answers(), &ip_list, &hosts_for_ips,
+ &result_cnames, &result_nameservers);
+
+ // 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;
+ }
+
+ // 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);
+ }
+ else if ( !result_cnames.empty() )
+ { // no IPs but a cname --> re-start resolving with that
+ handle_cname(source);
+ }
+ 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,
+ &result_cnames, &result_nameservers);
+ gather_results(result_message.additionals(), &ip_list, &hosts_for_ips,
+ &result_cnames, &result_nameservers);
+
+ int index, index_found=-1;
+ // go through name servers
+ 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 )
+ {
+ if (nameserver.second == ip_host)
+ {
+ index_found = index;
+ break;
+ }
+ ++index;
+ }
+
+ if (index_found > -1)
+ 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 ( !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<string_pair> *result_cnames,
+ std::vector<string_pair> *result_nameservers)
+ const
+{
using boost::net::dns::resource_base_t;
BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *answers )
{
- GlobalLogger.debug() << LogPrefix << std::showbase << std::hex
- << static_cast<unsigned>(rr_item->rtype()) << ": ";
- uint32_t ttl = rr_item->ttl();
boost::net::dns::type_t rr_type = rr_item->rtype();
+ uint32_t ttl = rr_item->ttl();
+ std::string domain = rr_item->domain();
if (rr_type == boost::net::dns::type_a)
{ // 'A' resource records carry IPv4 addresses
boost::asio::ip::address_v4 ip =
( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
->address();
- result_ips.push_back( HostAddress(ip, ttl) );
+ hosts_for_ips->push_back( domain );
+ result_ips->push_back( HostAddress(ip, ttl) );
+ GlobalLogger.debug() << LogPrefix << "IPv4 " << ip << " with TTL "
+ << ttl << "s for " << domain;
}
else if (rr_type == boost::net::dns::type_a6)
{ // 'AAAA' resource records carry IPv6 addresses
boost::asio::ip::address_v6 ip =
( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
->address();
- result_ips.push_back( HostAddress(ip, ttl) );
+ hosts_for_ips->push_back( domain );
+ result_ips->push_back( HostAddress(ip, ttl) );
+ GlobalLogger.debug() << LogPrefix << "IPv6 " << ip << " with TTL "
+ << ttl << "s for " << domain;
}
else if (rr_type == boost::net::dns::type_cname)
{ // 'CNAME' resource records that carry aliases
std::string cname =
(dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
->canonicalname();
- result_cnames.push_back( cname );
+ result_cnames->push_back( string_pair(domain, cname) );
+ GlobalLogger.debug() << LogPrefix << "CNAME " << cname
+ << " with TTL " << ttl << "s for " << domain;
}
else if (rr_type == boost::net::dns::type_ns)
- GlobalLogger.debug() << LogPrefix << "NS resource";
+ { // NS (nameserver) resource records
+ std::string nameserver =
+ (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
+ ->nameserver();
+ result_nameservers->push_back( string_pair(domain, nameserver) );
+ GlobalLogger.debug() << LogPrefix << "NameServer " << nameserver
+ << " with TTL " << ttl << "s for " << domain;
+ }
else if (rr_type == boost::net::dns::type_soa)
GlobalLogger.debug() << LogPrefix << "SOA resource";
else if (rr_type == boost::net::dns::type_ptr)
else if (rr_type == boost::net::dns::type_axfr)
GlobalLogger.debug() << LogPrefix << "axfr resource";
else
- GlobalLogger.debug() << LogPrefix << "unknown resource type";
+ GlobalLogger.debug() << LogPrefix << "unknown resource type: "
+ << std::showbase << std::hex
+ << static_cast<unsigned>(rr_item->rtype());
}
-
- 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
- else if ( !result_ips.empty() && !result_cnames.empty())
- GlobalLogger.warning() << LogPrefix << "Have CNAMEs AND IPs "
- << "--> deal with both!";
-
- BOOST_FOREACH( const std::string &cname, result_cnames )
- handle_cname(cname); // will schedule another DNS call
-
- if ( !result_ips.empty() )
- handle_ips(result_ips);
}
void DnsResolver::cname_resolve_callback(const std::string &canonical_name,
const bool was_success,
- const int cname_count)
+ const int recursion_count)
{
if (was_success)
// tell cache to return cname's ips if queried for our hostname
else
GlobalLogger.info() << LogPrefix << "Cname resolution failed";
- finalize_resolve(was_success, cname_count+1);
+ // cname counts like one recursion step more...
+ 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!";
+ Recursor.reset();
+ }
+
+ Recursor = DnsMaster::get_instance()->get_recursor_for(
+ Hostname, Protocol, name_server.get_ip());
+ callback_type callback = boost::bind(
+ &DnsResolver::recursive_resolve_callback,
+ this, name_server.get_ttl().get_value(),
+ _1, _2 );
+ Recursor->async_resolve( callback );
+
+ 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";
+
+ // do not need recursor any more; next time re-create from different random
+ // name server
+ if ( !Recursor )
+ GlobalLogger.warning() << "Recursor was reset before callback!";
+ else
+ Recursor.reset();
+
+ finalize_resolve(was_success, recursion_count+1);
+
}
void DnsResolver::finalize_resolve(const bool was_success,
- const int cname_count)
+ const int recursion_count)
{
// stop timers
- if (cname_count > 0)
+ if (recursion_count > 0)
stop_trying();
// else was called already from handle_cname
// schedule callbacks, clearing callback list
- ResolverBase::schedule_callbacks(was_success, cname_count);
+ ResolverBase::schedule_callbacks(was_success, recursion_count);
// finalize
GlobalLogger.notice() << LogPrefix << "Done resolving"
<< " with success = " << was_success
- << " and cname_count = " << cname_count;
+ << " and recursion_count = " << recursion_count;
IsResolving = false;
}