changed how dns deals with cnames and recursion: remember cnames and implement recurs...
[pingcheck] / src / dns / dnsresolver.cpp
index c215b77..b127c3d 100644 (file)
@@ -34,6 +34,8 @@
 #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>
 
@@ -47,7 +49,6 @@ namespace Config
     const int PauseBeforeRetrySeconds = 10;
     const int StaleDataLongtermMinutes = 15;
     const int DNS_PORT = 53;
-    const int UniqueID = 0xaffe;
 }
 
 DnsResolver::DnsResolver(IoServiceItem &io_serv,
@@ -59,6 +60,7 @@ 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 )
@@ -68,10 +70,13 @@ DnsResolver::DnsResolver(IoServiceItem &io_serv,
     , RetryCount( 0 )
     , IsResolving( false )
     , LogPrefix( "DnsResolver" )
+    , RandomIdGenerator()
+    , RequestId( 0 )
 {
     std::stringstream temp;
     temp << "Dns(" << ResolverBase::Hostname << "): ";
     LogPrefix = temp.str();
+
 }
 
 DnsResolver::~DnsResolver()
@@ -109,6 +114,7 @@ void DnsResolver::do_resolve()
             << "Call to do_resolve ignored since resolving already";
         return;
     }
+    IsResolving = true;
 
     GlobalLogger.info() << LogPrefix << "start resolving";
 
@@ -119,13 +125,17 @@ void DnsResolver::do_resolve()
 
     // 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(
@@ -144,11 +154,12 @@ void DnsResolver::do_resolve()
                                       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)
@@ -172,7 +183,7 @@ void DnsResolver::do_resolve()
 
 
 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
     {
@@ -189,29 +200,137 @@ void DnsResolver::handle_dns_result(const boost::system::error_code &error,
         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
@@ -224,7 +343,10 @@ void DnsResolver::handle_dns_result(const boost::system::error_code &error,
             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
@@ -237,17 +359,29 @@ void DnsResolver::handle_dns_result(const boost::system::error_code &error,
             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)
@@ -263,25 +397,10 @@ void DnsResolver::handle_dns_result(const boost::system::error_code &error,
         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);
 }
 
 
@@ -327,7 +446,7 @@ void DnsResolver::handle_cname(const std::string &canonical_name)
 
 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
@@ -336,25 +455,69 @@ void DnsResolver::cname_resolve_callback(const std::string &canonical_name,
     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;
 }