139219ce428bcba25d889ad0e51ba109fce0f6f8
[pingcheck] / src / dns / dnsresolver.cpp
1 /*
2  The software in this package is distributed under the GNU General
3  Public License version 2 (with a special exception described below).
4
5  A copy of GNU General Public License (GPL) is included in this distribution,
6  in the file COPYING.GPL.
7
8  As a special exception, if other files instantiate templates or use macros
9  or inline functions from this file, or you compile this file and link it
10  with other works to produce a work based on this file, this file
11  does not by itself cause the resulting work to be covered
12  by the GNU General Public License.
13
14  However the source code for this file must still be made available
15  in accordance with section (3) of the GNU General Public License.
16
17  This exception does not invalidate any other reasons why a work based
18  on this file might be covered by the GNU General Public License.
19
20  Christian Herdtweck, Intra2net AG 2015
21
22  with code copied from boost::net::dns::resolve.hpp
23  by Andreas Haberstroh (andreas at ibusy dot com)
24     from https://github.com/softwareace/Boost.DNS
25  */
26
27 #include "dns/dnsresolver.h"
28
29 #include <stdint.h>
30 #include <sstream>
31
32 #include <boost/foreach.hpp>
33 #include <boost/bind.hpp>
34 #include <boost/function.hpp>
35 #include <boost/net/dns.hpp>
36 #include <boost/date_time/posix_time/posix_time.hpp>
37 #include <boost/uuid/uuid.hpp>
38 #include <boost/uuid/uuid_io.hpp>
39
40 #include <logfunc.hpp>
41
42 using I2n::Logger::GlobalLogger;
43 using boost::posix_time::seconds;
44 using boost::posix_time::minutes;
45
46 namespace Config
47 {
48     const int ResolveTimeoutSeconds = 3;
49     const int PauseBeforeRetrySeconds = 10;
50     const int StaleDataLongtermMinutes = 15;
51     const int DNS_PORT = 53;
52 }
53
54 DnsResolver::DnsResolver(IoServiceItem &io_serv,
55                          const std::string &hostname,
56                          const DnsIpProtocol &protocol,
57                          const DnsCacheItem cache,
58                          const boost::asio::ip::address &name_server)
59     : ResolverBase( io_serv, hostname, cache )
60     , Socket( *io_serv, ip::udp::endpoint(ip::udp::v4(), 0))
61         // just connect to anything, will specify sender/receiver later
62     , ReceiveBuffer()
63     , RequestBuffer()
64     , Protocol( protocol )
65     , NameServer( name_server, Config::DNS_PORT )
66     , ResolveTimeoutTimer( *io_serv )
67     , PauseBeforeRetryTimer( *io_serv )
68     , StaleDataLongtermTimer( *io_serv )
69     , NextIpIndex( 0 )
70     , RetryCount( 0 )
71     , IsResolving( false )
72     , LogPrefix( "DnsResolver" )
73     , RandomIdGenerator()
74     , RequestId( 0 )
75     , Recursor()
76     , OperationCancelled( false )
77 {
78     std::stringstream temp;
79     temp << "Dns(" << ResolverBase::Hostname << "): ";
80     LogPrefix = temp.str();
81
82 }
83
84 DnsResolver::~DnsResolver()
85 {
86     boost::system::error_code error;
87     //Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
88     //if ( error )
89     //    GlobalLogger.warning() << LogPrefix << "Received error " << error
90     //                           << " when shutting down socket for DNS";
91     // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
92     // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
93
94     Socket.close(error);
95     if ( error )
96         GlobalLogger.warning() << LogPrefix << "Received error " << error
97                                << " when closing socket for DNS";
98 }
99
100
101
102 //==============================================================================
103 // ASYNC RESOLVE
104 //==============================================================================
105
106 /**
107  * copied here code from boost::net::dns::resolve.hpp, since want async
108  * operation and that is used only internally, there
109  */
110 void DnsResolver::do_resolve()
111 {
112     // check if resolving already
113     if (IsResolving)
114     {
115         GlobalLogger.info() << LogPrefix
116             << "Call to do_resolve ignored since resolving already";
117         return;
118     }
119     IsResolving = true;
120     OperationCancelled = false;
121
122     GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
123         << to_string(Protocol) << " using name server " << NameServer;
124
125     // just to be sure: cancel timers
126     ResolveTimeoutTimer.cancel();
127     PauseBeforeRetryTimer.cancel();
128     StaleDataLongtermTimer.cancel();
129
130     // create DNS request
131     boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
132     dns_message.recursive(false);
133     dns_message.action(boost::net::dns::message::query);
134     dns_message.opcode(boost::net::dns::message::squery);
135
136     // create random ID for message
137     boost::uuids::uuid message_id = RandomIdGenerator();
138     memcpy( &RequestId, message_id.data, sizeof(RequestId) );
139     dns_message.id( RequestId );
140     GlobalLogger.debug() << LogPrefix << "Request has ID "
141                                << std::showbase << std::hex << dns_message.id();
142
143     // setup receipt of reply
144     Socket.async_receive_from(
145             boost::asio::buffer(ReceiveBuffer.get_array()),
146             NameServer,
147             boost::bind( &DnsResolver::handle_dns_result, this,
148                          boost::asio::placeholders::error,
149                          boost::asio::placeholders::bytes_transferred)
150     );
151
152     // schedule timeout
153     (void) ResolveTimeoutTimer.expires_from_now(
154                                         seconds(Config::ResolveTimeoutSeconds));
155     ResolveTimeoutTimer.async_wait( boost::bind(
156                                       &DnsResolver::handle_resolve_timeout,
157                                       this, boost::asio::placeholders::error) );
158
159     // send dns request
160     dns_message.encode(RequestBuffer);
161     size_t bytes_sent;
162     try
163     {
164         bytes_sent = Socket.send_to(
165                                 boost::asio::buffer(RequestBuffer.get_array()),
166                                 NameServer );
167     }
168     catch (boost::system::system_error &err)
169     {
170         GlobalLogger.warning() << LogPrefix
171                                << "Sending of DNS request message failed: "
172                                << err.what();
173         schedule_retry();
174         return;
175     }
176
177     if ( bytes_sent == 0 )
178     {
179         GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
180         schedule_retry();
181         return;
182     }
183 }
184
185
186 void DnsResolver::handle_dns_result(const boost::system::error_code &error,
187                                     const std::size_t bytes_transferred)
188 {
189     if (error)
190     {
191         GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
192                             << error << " --> try again after a little while";
193         schedule_retry();
194         return;
195     }
196     else if ( OperationCancelled )
197     {   // async_resolve was cancelled --> callbacks already called
198         GlobalLogger.info() << LogPrefix
199                             << "Ignoring DNS results since we were cancelled";
200         return;
201     }
202
203     GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
204                          << bytes_transferred << " bytes transferred)";
205
206     // next 3(+1) lines copied from boost/net/dns/resolver.hpp:
207     // clamp the recvBuffer with the number of bytes transferred or decode buffr
208     ReceiveBuffer.length(bytes_transferred);
209     boost::net::dns::message result_message;
210     result_message.decode( ReceiveBuffer );
211
212     // check ID
213     if (RequestId != result_message.id())
214         GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
215            << std::showbase << std::hex << result_message.id()
216            << " but expected ID " << RequestId;
217     else
218         GlobalLogger.debug() << LogPrefix << "Result has correct ID "
219                                       << std::showbase << std::hex << RequestId;
220     RequestId = 0;
221
222     // loop over answers, remembering ips and cnames
223     // work with a regular pointer to list of answers since result_message is
224     //   owner of data and that exists until end of function
225     // Items in answers list are shared_ptr to resource_base_t
226     std::vector<host_addr_pair> result_ips;
227     std::vector<src_cname_pair> result_cnames;
228     std::vector<string_pair> result_name_servers;
229
230     GlobalLogger.debug() << "Checking ANSWERS section of dns reply";
231     gather_results(result_message.answers(), &result_ips, &result_cnames,
232                                                           &result_name_servers);
233     // results should have the logical order
234     // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
235
236     // remember cname list (if there were any)
237     BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
238     {
239         GlobalLogger.debug() << LogPrefix << "Remember CNAME "
240             << host_and_cname.first << " --> " << host_and_cname.second.first;
241         ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
242     }
243
244     if ( !result_ips.empty() )
245         handle_ips( result_ips );
246     else if ( !result_cnames.empty() )
247         // no IPs but at least one cname --> find the "last" cname and
248         // re-start resolving with that
249         handle_cname(result_cnames);
250     else
251     {   // no answers --> check for name_servers in authorities section
252         if ( !result_name_servers.empty() )
253             GlobalLogger.warning() << LogPrefix
254                                    << "Received NS records in answers! "
255                                    << "That is quite unexpected...";
256         GlobalLogger.debug() << "Checking AUTHORITIES section of dns reply";
257         gather_results(result_message.authorites(), &result_ips,
258                        &result_cnames, &result_name_servers);
259         GlobalLogger.debug() << "Checking ADDITIONALS section of dns reply";
260         gather_results(result_message.additionals(), &result_ips,
261                        &result_cnames, &result_name_servers);
262
263         // search for a name_server for which an IP is given
264         bool have_recursed = false;
265         BOOST_FOREACH( const string_pair &name_server, result_name_servers )
266         {
267             if (name_server.second == Hostname)
268             {   // in case we try to resolve IP for name server, do not use same
269                 GlobalLogger.debug() << LogPrefix
270                     << "Name server found is same as hostname --> skip";
271                 continue;
272             }
273
274             // go through ips and look for match
275             BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips )
276             {
277                 if (name_server.second == host_and_addr.first)
278                 {
279                     GlobalLogger.info() << LogPrefix << "Ask next name_server "
280                         << name_server.second << " with IP "
281                         << host_and_addr.second.get_ip() << " (responsible for "
282                         << name_server.first << ")";
283                     have_recursed = true;
284                     handle_recurse( host_and_addr.second );
285                     break;
286                 }
287             }
288             if (have_recursed)
289                 break;
290         }
291
292         if ( !have_recursed )
293         {   // no name_server with ip found -- strange
294             if (result_name_servers.empty())
295             {
296                 GlobalLogger.error() << LogPrefix << "Result contained neither "
297                     << "IP nor CNAME nor name server --> cannot proceed!";
298                 handle_unavailable();
299             }
300             else
301             {
302                 GlobalLogger.warning() << LogPrefix
303                     << "There are " << result_name_servers.size()
304                     << " name_servers given but none with IP!";
305                 handle_recurse_without_ip(result_name_servers[0].second);
306             }
307         }
308     }
309 }
310
311 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *answers,
312                                  std::vector<host_addr_pair> *result_ips,
313                                  std::vector<src_cname_pair> *result_cnames,
314                                  std::vector<string_pair> *result_name_servers)
315                                                                            const
316 {
317     using boost::net::dns::resource_base_t;
318     boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
319     BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *answers )
320     {
321         boost::net::dns::type_t rr_type = rr_item->rtype();
322         uint32_t ttl = rr_item->ttl();
323         std::string domain = rr_item->domain();
324         std::string expiry =
325                         boost::posix_time::to_simple_string(now + seconds(ttl));
326
327         if (rr_type == boost::net::dns::type_a)
328         {    // 'A' resource records carry IPv4 addresses
329             if (Protocol == DNS_IPv6)
330             {
331                 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
332                         << "because resolver was configured to only use IPv6.";
333                 continue;
334             }
335             boost::asio::ip::address_v4 ip =
336                 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
337                 ->address();
338             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
339             GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
340                                  << " with TTL " << ttl << "s (until "
341                                  << expiry << ")";
342         }
343         else if (rr_type == boost::net::dns::type_a6)
344         {   // 'AAAA' resource records carry IPv6 addresses
345             if (Protocol == DNS_IPv4)
346             {
347                 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
348                         << "because resolver was configured to only use IPv4.";
349                 continue;
350             }
351             boost::asio::ip::address_v6 ip =
352                 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
353                 ->address();
354             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
355             GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
356                                  << " with TTL " << ttl << "s (until "
357                                  << expiry << ")";
358         }
359         else if (rr_type == boost::net::dns::type_cname)
360         {   // 'CNAME' resource records that carry aliases
361             std::string cname =
362                 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
363                 ->canonicalname();
364             result_cnames->push_back( src_cname_pair(domain,
365                                                      Cname(cname, ttl)) );
366             GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
367                                  << cname << " with TTL " << ttl << "s (until "
368                                  << expiry << ")";
369         }
370         else if (rr_type == boost::net::dns::type_ns)
371         {   // NS (name_server) resource records
372             std::string name_server =
373                 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
374                 ->nameserver();
375             result_name_servers->push_back( string_pair(domain, name_server) );
376             GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
377                                  << " for " << domain << " with TTL " << ttl
378                                  << "s (until " << expiry << ")";
379         }
380         else if (rr_type == boost::net::dns::type_soa)
381             GlobalLogger.debug() << LogPrefix << "SOA resource";
382         else if (rr_type == boost::net::dns::type_ptr)
383             GlobalLogger.debug() << LogPrefix << "ptr resource";
384         else if (rr_type == boost::net::dns::type_hinfo)
385             GlobalLogger.debug() << LogPrefix << "hinfo resource";
386         else if (rr_type == boost::net::dns::type_mx)
387             GlobalLogger.debug() << LogPrefix << "mx resource";
388         else if (rr_type == boost::net::dns::type_txt)
389             GlobalLogger.debug() << LogPrefix << "txt resource";
390         else if (rr_type == boost::net::dns::type_srv)
391             GlobalLogger.debug() << LogPrefix << "srv resource";
392         else if (rr_type == boost::net::dns::type_axfr)
393             GlobalLogger.debug() << LogPrefix << "axfr resource";
394         else
395             GlobalLogger.debug() << LogPrefix << "unknown resource type: "
396                                  << std::showbase << std::hex
397                                  << static_cast<unsigned>(rr_item->rtype());
398     }
399 }
400
401
402 void DnsResolver::handle_unavailable()
403 {
404     // schedule new attempt in quite a while
405     StaleDataLongtermTimer.expires_from_now(
406                                      minutes(Config::StaleDataLongtermMinutes));
407     StaleDataLongtermTimer.async_wait(
408             boost::bind( &DnsResolver::wait_timer_timeout_handler,
409                          this, boost::asio::placeholders::error
410             )
411     );
412
413     // for now, admit failure
414     bool was_success = false;
415     finalize_resolve(was_success);
416 }
417
418
419 void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
420 {
421     // received at least one IP which could be for the queried host name 
422     // or the cname at the "end" of the cname list;
423     // but all IPs should be for the same
424     HostAddressVec addr_list;
425     std::string only_host_for_ips = result_ips[0].first;
426     BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
427     {
428         if ( host_and_addr.first != only_host_for_ips )
429             GlobalLogger.warning() << LogPrefix
430                 << "Received IPs for different hosts " << only_host_for_ips
431                 << " and " << host_and_addr.first << " in one DNS result! "
432                 << "--> ignore second";
433         else
434         {
435             GlobalLogger.notice() << LogPrefix << "Found IP "
436                       << host_and_addr.second.get_ip() << " with TTL "
437                       << host_and_addr.second.get_ttl().get_value() << "s";
438             addr_list.push_back(host_and_addr.second);
439         }
440     }
441     ResolverBase::update_cache( only_host_for_ips, addr_list );
442
443     // clean up
444     bool was_success = true;
445     finalize_resolve(was_success);
446 }
447
448
449 void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
450 {
451     // find the "last" cname in the list
452     // Hostname --> cname1 --> cname2 --> ... --> cnameN
453     // We assume here that this list might not be in order but that all cnames
454     //   form a single list (form one connected list and not several isolated)
455
456     // host_and_cname.second is a Cname, which is a pair (destination, ttl)
457     std::string last_cname = "";
458     bool could_be_last;
459     BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
460     {
461         could_be_last = true;
462         BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
463         {
464             if (other.first == host_and_cname.second.first)
465             {   // found cname for current cname
466                 could_be_last = false;
467                 break;
468             }
469         }
470         if (could_be_last)
471         {
472             last_cname = host_and_cname.second.first;
473             break;
474         }
475     }
476
477     if (last_cname.empty())
478     {
479         GlobalLogger.error() << LogPrefix
480             << "Could not identify \"last\" CNAME to handle -- "
481             << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
482         GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
483         BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
484             GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
485                                              << host_and_cname.second.first;
486         handle_unavailable();
487     }
488     else
489     {   // check cache for IP for this cname
490         bool check_up_to_date = true;
491         HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
492                                                               check_up_to_date);
493         if ( !cached_data.empty() )
494         {
495             bool was_success = true;
496             int recursion_count = 1;  // define cache access as only 1
497             finalize_resolve(was_success, recursion_count);
498         }
499         else
500         {   // get resolver for canonical name
501             // as opposed to the interal Recursor variable used in
502             // handle_recurse, this is a "proper" resolver that is maintained
503             // and cached by DnsMaster --> independent of this Resolver
504             ResolverItem resolver = DnsMaster::get_instance()
505                                     ->get_resolver_for(last_cname, Protocol);
506             callback_type callback = boost::bind(
507                                            &DnsResolver::cname_resolve_callback,
508                                            this, _1, _2 );
509             resolver->async_resolve( callback );
510
511             // treat a CNAME as a partial result: not enough to run callbacks
512             // from finalize_resolve, but enough to stop timers and reset
513             // RetryCount --> name resolution can take longer
514             stop_trying();
515         }
516     }
517 }
518
519
520 void DnsResolver::cname_resolve_callback(const bool was_success,
521                                          const int recursion_count)
522 {
523     if ( OperationCancelled )
524     {   // async_resolve was cancelled --> callbacks already called
525         GlobalLogger.info() << LogPrefix
526                             << "Ignoring CNAME results since we were cancelled";
527         return;
528     }
529     else if (was_success)
530         GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
531     else
532         GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
533         // no use to schedule retry in this case since cname resolver must have
534         // failed several times and we can just re-start the same procedure with
535         // the same information (in recursion can try different name server)
536         // --> no schedule_retry
537
538     // cname counts like one more recursion step ...
539     finalize_resolve(was_success, recursion_count+1);
540 }
541
542
543 void DnsResolver::handle_recurse(const HostAddress &name_server)
544 {
545     // get resolver for same hostname but using a different name server
546     if (Recursor)
547     {
548         if (Recursor->is_resolving())
549         {
550             GlobalLogger.warning() << LogPrefix << "Recursor is resolving! "
551                                    << "Will cancel and reset";
552             Recursor->cancel_resolve();
553         }
554         else
555             GlobalLogger.warning() << LogPrefix
556                                    << "Recursor has not been reset!";
557         Recursor.reset();
558     }
559
560     Recursor = DnsMaster::get_instance()->get_recursor_for(
561                                       Hostname, Protocol, name_server.get_ip());
562     callback_type callback = boost::bind(
563                                        &DnsResolver::recursive_resolve_callback,
564                                        this, name_server.get_ttl().get_value(),
565                                        _1, _2 );
566     Recursor->async_resolve( callback );
567
568     // do not cancel timers or reset RetryCount
569     //stop_trying();
570 }
571
572
573 void DnsResolver::recursive_resolve_callback(const uint32_t min_ttl,
574                                              const bool was_success,
575                                              const int recursion_count)
576 {
577     GlobalLogger.debug()
578         << "Recursion back at request with name server " << NameServer;
579
580     // do not need recursor any more; next time re-create from different random
581     //    name server; may have been reset already in cancel_resolve(), so that
582     //    is ok. If not, issue a warning
583     if ( !Recursor )
584     {
585         if ( !OperationCancelled )
586             GlobalLogger.warning() << LogPrefix
587                                    << "Recursor was reset before callback!";
588     }
589     else
590         Recursor.reset();
591
592     if ( OperationCancelled )
593     {   // async_resolve was cancelled --> callbacks already called
594         GlobalLogger.info() << LogPrefix
595                         << "Ignoring recursion results since we were cancelled";
596         return;
597     }
598     else if (was_success)
599     {
600         // make sure the saved TTL is not larger than the one we found here
601         ResolverBase::update_cache(min_ttl);
602         finalize_resolve(was_success, recursion_count+1);
603     }
604     else
605     {
606         GlobalLogger.info() << LogPrefix << "Recursive resolution failed";
607         schedule_retry();
608     }
609 }
610
611
612 void DnsResolver::handle_recurse_without_ip(const std::string &name_server)
613 {
614     // get resolver for name_server
615     // save in Recursor although it is a "proper" resolver (so result is cached)
616     if (Recursor)
617     {
618         if (Recursor->is_resolving())
619         {
620             GlobalLogger.warning() << LogPrefix << "Recursor is resolving! "
621                                    << "Will cancel and reset";
622             Recursor->cancel_resolve();
623         }
624         else
625             GlobalLogger.warning() << LogPrefix
626                                    << "Recursor has not been reset!";
627         Recursor.reset();
628     }
629     Recursor = DnsMaster::get_instance()->get_resolver_for(name_server,
630                                                            Protocol);
631
632     // check for IPs in cache
633     if (Recursor->get_resolved_ip_count() == 0)
634     {
635         GlobalLogger.info() << LogPrefix
636                    << "Start to resolve address of name server " << name_server;
637         callback_type callback = boost::bind(
638                                      &DnsResolver::name_server_resolve_callback,
639                                      this, _1, _2);
640         Recursor->async_resolve( callback );
641     }
642     else
643     {
644         GlobalLogger.info() << LogPrefix << "Use cached ip for name server "
645                             << name_server;
646         HostAddress ip = Recursor->get_next_ip();
647         Recursor.reset();
648         handle_recurse(ip);
649     }
650 }
651
652 void DnsResolver::name_server_resolve_callback(const bool was_success,
653                                                const int recursion_count)
654 {
655     if (OperationCancelled)
656     {
657         GlobalLogger.info() << LogPrefix
658                    << "Ignoring name server IP results since we were cancelled";
659         return;
660     }
661     else if (was_success)
662     {
663         HostAddress ip = Recursor->get_next_ip();
664         GlobalLogger.info() << LogPrefix << "Found IP " << ip.get_ip()
665             << " for name server " << Recursor->get_hostname();
666         Recursor.reset();
667         handle_recurse(ip);
668     }
669     else
670     {
671         GlobalLogger.info() << LogPrefix << "Failed to find IP for name server"
672             << Recursor->get_hostname() << " --> schedule retry";
673         Recursor.reset();
674         schedule_retry();
675     }
676 }
677
678
679 void DnsResolver::finalize_resolve(const bool was_success,
680                                    const int recursion_count)
681 {
682     // some consistency checks; failure might indicate a situation I had not
683     // anticipated during programming but might not be harmfull yet
684     if ( !IsResolving )
685         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
686                                             << "not resolving any more!";
687     if ( OperationCancelled )
688         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
689                                             << " was cancelled!";
690     if ( ResolverBase::CallbackList.empty() )
691         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
692                                             << "no callbacks!";
693     if ( RequestId != 0 )
694         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
695                                             << "waiting for DNS reply!";
696
697     // stop timers
698     stop_trying();
699
700     // schedule callbacks, clearing callback list
701     ResolverBase::schedule_callbacks(was_success, recursion_count);
702
703     // finalize
704     GlobalLogger.notice() << LogPrefix << "finalized resolve"
705                           << " with success = " << was_success
706                           << " and recursion_count = " << recursion_count;
707     IsResolving = false;
708 }
709
710
711 void DnsResolver::stop_trying()
712 {
713     // cancel timers
714     GlobalLogger.debug() << LogPrefix << "Cancelling timers";
715     ResolveTimeoutTimer.cancel();
716     PauseBeforeRetryTimer.cancel();
717     StaleDataLongtermTimer.cancel();
718
719     // clean up
720     RetryCount = 0;
721 }
722
723
724 bool DnsResolver::is_resolving()
725 {
726     return IsResolving;
727 }
728
729
730 /**
731  * cancel a earlier call to async_resolve
732  *
733  * callbacks will be called with was_success=false; all internal operations
734  * will be cancelled and internal callbacks (timers, dns results) have no
735  * effect any more
736  */
737 void DnsResolver::cancel_resolve()
738 {
739     if ( !IsResolving )
740     {
741         GlobalLogger.info() << LogPrefix
742                << "Cancel called on non-resolving resolver -- ignore";
743         return;
744     }
745     else if (OperationCancelled)
746     {
747         GlobalLogger.info() << LogPrefix
748                << "Cancel called on cancelled resolver -- ignore";
749         return;
750     }
751
752     if ( Recursor )
753         Recursor->cancel_resolve(); // does not hurt even if it is not resolving
754
755     // set before finalize_resolve so can check in finalize_resolve that ID is
756     //   always 0; ID is not used any more since handle_dns_result stops if
757     //   OperationCancelled is true
758     RequestId = 0;
759
760     bool was_success = false;
761     int recursion_count = 1;
762     finalize_resolve(was_success, recursion_count);
763
764     // set after finalize_resolve, so can check in finalize_resolve that 
765     // OperationCancelled is never true
766     OperationCancelled = true;
767
768 }
769
770
771 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
772 {
773     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
774     {
775         GlobalLogger.warning() << LogPrefix
776                                << "Resolve timeout timer was cancelled!";
777         return;
778     }
779     else if (error)
780     {
781         GlobalLogger.warning() << LogPrefix
782                                << "resolve timeout handler received error "
783                                << error << " --> retry";
784         schedule_retry();
785     }
786     else if ( OperationCancelled )
787     {   // async_resolve was cancelled --> callbacks already called
788         GlobalLogger.info() << LogPrefix
789                             << "Ignoring DNS timeout since we were cancelled";
790         return;
791     }
792     else
793     {
794         GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
795         schedule_retry();
796     }
797 }
798
799
800 void DnsResolver::schedule_retry()
801 {
802     // this function is called in all sorts of error cases
803     // --> need to clean up a bit
804     if ( Recursor )
805     {
806         Recursor->cancel_resolve();
807         Recursor.reset();
808     }
809     ResolveTimeoutTimer.cancel();
810     PauseBeforeRetryTimer.cancel();
811
812     // increment timer
813     ++RetryCount;
814
815     if ( RetryCount > DnsMaster::get_instance()
816                       ->get_max_address_resolution_attempts() )
817     {   // too many re-tries
818         GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
819                             << "RetryCount " << RetryCount << " too high";
820         handle_unavailable();   // will call stop_trying i.e. reset RetryCount
821     }
822     else
823     {   // schedule retry
824         GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
825                             << RetryCount << ")";
826         PauseBeforeRetryTimer.expires_from_now(
827                 seconds(Config::PauseBeforeRetrySeconds));
828         PauseBeforeRetryTimer.async_wait(
829                 boost::bind( &DnsResolver::wait_timer_timeout_handler,
830                              this, boost::asio::placeholders::error) );
831     }
832 }
833
834 void DnsResolver::wait_timer_timeout_handler(
835                                          const boost::system::error_code &error)
836 {
837     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
838     {   // assume that our code cancelled this timer, so callbacks will be
839         // taken care of!
840         GlobalLogger.warning() << LogPrefix
841                                << "Resolve wait timer was cancelled! ";
842     }
843     else if (error)
844     {   // not sure what to do here, but callers waiting forever for a callback
845         // is probably the worst thing to happen, so call finalize_resolve
846         GlobalLogger.warning() << LogPrefix
847                                << "resolve wait handler received error "
848                                << error << "! Try to finalize resolve";
849         bool was_success = false;
850         finalize_resolve(was_success);
851     }
852     else if ( OperationCancelled )
853     {   // async_resolve was cancelled --> callbacks already called
854         GlobalLogger.info() << LogPrefix
855                           << "Ignoring waiting timeout since we were cancelled";
856         return;
857     }
858     else
859     {
860         GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
861         do_resolve();
862     }
863 }
864
865
866 //==============================================================================
867 // RETRIEVAL
868 //==============================================================================
869
870 HostAddress DnsResolver::get_next_ip()
871 {
872     // get cached data
873     HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
874
875     // if no results cached, return default-constructed HostAddress (0.0.0.0)
876     if ( cached_data.empty() )
877     {
878         HostAddress return_value;
879         return return_value;
880     }
881
882     // check validity of index (cache may have changed since last call)
883     if (NextIpIndex >= cached_data.size())
884         NextIpIndex = 0;
885
886     // return next IP
887     return cached_data[NextIpIndex++];
888 }
889
890 bool DnsResolver::have_up_to_date_ip()
891 {
892     return ! ResolverBase::get_cached_ips_recursively("", true).empty();
893 }
894
895 int DnsResolver::get_resolved_ip_count()
896 {
897     return ResolverBase::get_cached_ips_recursively().size();
898 }
899
900 // (created using vim -- the world's best text editor)
901