simplified dns (no self-made recursion); merge PingScheduler and PingRotate; make...
[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     , OperationCancelled( false )
76 {
77     std::stringstream temp;
78     temp << "Dns(" << ResolverBase::Hostname << "): ";
79     LogPrefix = temp.str();
80
81 }
82
83 DnsResolver::~DnsResolver()
84 {
85     boost::system::error_code error;
86     //Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
87     //if ( error )
88     //    GlobalLogger.warning() << LogPrefix << "Received error " << error
89     //                           << " when shutting down socket for DNS";
90     // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
91     // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
92
93     Socket.close(error);
94     if ( error )
95         GlobalLogger.warning() << LogPrefix << "Received error " << error
96                                << " when closing socket for DNS";
97 }
98
99
100
101 //==============================================================================
102 // ASYNC RESOLVE
103 //==============================================================================
104
105 /**
106  * copied here code from boost::net::dns::resolve.hpp, since want async
107  * operation and that is used only internally, there
108  */
109 void DnsResolver::do_resolve()
110 {
111     // check if resolving already
112     if (IsResolving)
113     {
114         GlobalLogger.info() << LogPrefix
115             << "Call to do_resolve ignored since resolving already";
116         return;
117     }
118     IsResolving = true;
119     OperationCancelled = false;
120
121     GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
122         << to_string(Protocol) << " using name server " << NameServer;
123
124     // just to be sure: cancel timers
125     ResolveTimeoutTimer.cancel();
126     PauseBeforeRetryTimer.cancel();
127     StaleDataLongtermTimer.cancel();
128
129     // create DNS request
130     boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
131     dns_message.recursive(true);
132     dns_message.action(boost::net::dns::message::query);
133     dns_message.opcode(boost::net::dns::message::squery);
134
135     // create random ID for message
136     boost::uuids::uuid message_id = RandomIdGenerator();
137     memcpy( &RequestId, message_id.data, sizeof(RequestId) );
138     dns_message.id( RequestId );
139     GlobalLogger.debug() << LogPrefix << "Request has ID "
140                                << std::showbase << std::hex << dns_message.id();
141
142     // setup receipt of reply
143     Socket.async_receive_from(
144             boost::asio::buffer(ReceiveBuffer.get_array()),
145             NameServer,
146             boost::bind( &DnsResolver::handle_dns_result, this,
147                          boost::asio::placeholders::error,
148                          boost::asio::placeholders::bytes_transferred)
149     );
150
151     // schedule timeout
152     (void) ResolveTimeoutTimer.expires_from_now(
153                                         seconds(Config::ResolveTimeoutSeconds));
154     ResolveTimeoutTimer.async_wait( boost::bind(
155                                       &DnsResolver::handle_resolve_timeout,
156                                       this, boost::asio::placeholders::error) );
157
158     // send dns request
159     dns_message.encode(RequestBuffer);
160     size_t bytes_sent;
161     try
162     {
163         bytes_sent = Socket.send_to(
164                                 boost::asio::buffer(RequestBuffer.get_array()),
165                                 NameServer );
166     }
167     catch (boost::system::system_error &err)
168     {
169         GlobalLogger.warning() << LogPrefix
170                                << "Sending of DNS request message failed: "
171                                << err.what();
172         schedule_retry();
173         return;
174     }
175
176     if ( bytes_sent == 0 )
177     {
178         GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
179         schedule_retry();
180         return;
181     }
182 }
183
184
185 void DnsResolver::handle_dns_result(const boost::system::error_code &error,
186                                     const std::size_t bytes_transferred)
187 {
188     if (error)
189     {
190         GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
191                             << error << " --> try again after a little while";
192         schedule_retry();
193         return;
194     }
195     else if ( OperationCancelled )
196     {   // async_resolve was cancelled --> callbacks already called
197         GlobalLogger.info() << LogPrefix
198                             << "Ignoring DNS results since we were cancelled";
199         return;
200     }
201
202     GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
203                          << bytes_transferred << " bytes transferred)";
204
205     // next 3(+1) lines copied from boost/net/dns/resolver.hpp:
206     // clamp the recvBuffer with the number of bytes transferred or decode buffr
207     ReceiveBuffer.length(bytes_transferred);
208     boost::net::dns::message result_message;
209     result_message.decode( ReceiveBuffer );
210
211     // check ID
212     if (RequestId != result_message.id())
213         GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
214            << std::showbase << std::hex << result_message.id()
215            << " but expected ID " << RequestId;
216     else
217         GlobalLogger.debug() << LogPrefix << "Result has correct ID "
218                                       << std::showbase << std::hex << RequestId;
219     RequestId = 0;
220
221     // loop over answers, remembering ips and cnames
222     // work with a regular pointer to list of answers since result_message is
223     //   owner of data and that exists until end of function
224     // Items in answers list are shared_ptr to resource_base_t
225     std::vector<host_addr_pair> result_ips;
226     std::vector<src_cname_pair> result_cnames;
227     std::vector<string_pair> result_name_servers;
228
229     GlobalLogger.debug() << "Checking ANSWERS section of dns reply";
230     gather_results(result_message.answers(), &result_ips, &result_cnames,
231                                                           &result_name_servers);
232     // results should have the logical order
233     // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
234
235     // remember cname list (if there were any)
236     BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
237     {
238         GlobalLogger.info() << LogPrefix << "Remember CNAME "
239             << host_and_cname.first << " --> " << host_and_cname.second.Host;
240         ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
241     }
242
243     if ( !result_ips.empty() )
244         handle_ips( result_ips );
245     else if ( !result_cnames.empty() )
246         // no IPs but at least one cname --> find the "last" cname and
247         // re-start resolving with that
248         handle_cname(result_cnames);
249     else
250     {   // no answers --> cannot proceed
251         GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
252                                << "--> schedule retry";
253         schedule_retry();
254     }
255 }
256
257 /**
258  * gather IPs, CNAMEs and name servers from list of resource records;
259  *
260  * can be run on anwers(), autorities() and additional() sections of dns reply
261  * messages
262  * 
263  * @param rr_list: input list of resource records
264  * @param result_ips: output vector of ips
265  * @param result_cnames: output vector of cnames
266  * @param result_name_servers: output vector of name servers
267  */
268 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
269                                  std::vector<host_addr_pair> *result_ips,
270                                  std::vector<src_cname_pair> *result_cnames,
271                                  std::vector<string_pair> *result_name_servers)
272                                                                            const
273 {
274     using boost::net::dns::resource_base_t;
275     boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
276     BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
277     {
278         boost::net::dns::type_t rr_type = rr_item->rtype();
279         uint32_t ttl = rr_item->ttl();
280         std::string domain = rr_item->domain();
281         std::string expiry =
282                         boost::posix_time::to_simple_string(now + seconds(ttl));
283
284         if (rr_type == boost::net::dns::type_a)
285         {    // 'A' resource records carry IPv4 addresses
286             if (Protocol == DNS_IPv6)
287             {
288                 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
289                         << "because resolver was configured to only use IPv6.";
290                 continue;
291             }
292             boost::asio::ip::address_v4 ip =
293                 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
294                 ->address();
295             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
296             GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
297                                  << " with TTL " << ttl << "s (until "
298                                  << expiry << ")";
299         }
300         else if (rr_type == boost::net::dns::type_a6)
301         {   // 'AAAA' resource records carry IPv6 addresses
302             if (Protocol == DNS_IPv4)
303             {
304                 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
305                         << "because resolver was configured to only use IPv4.";
306                 continue;
307             }
308             boost::asio::ip::address_v6 ip =
309                 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
310                 ->address();
311             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
312             GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
313                                  << " with TTL " << ttl << "s (until "
314                                  << expiry << ")";
315         }
316         else if (rr_type == boost::net::dns::type_cname)
317         {   // 'CNAME' resource records that carry aliases
318             std::string cname =
319                 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
320                 ->canonicalname();
321             result_cnames->push_back( src_cname_pair(domain,
322                                                      Cname(cname, ttl)) );
323             GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
324                                  << cname << " with TTL " << ttl << "s (until "
325                                  << expiry << ")";
326         }
327         else if (rr_type == boost::net::dns::type_ns)
328         {   // NS (name_server) resource records
329             std::string name_server =
330                 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
331                 ->nameserver();
332             result_name_servers->push_back( string_pair(domain, name_server) );
333             GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
334                                  << " for " << domain << " with TTL " << ttl
335                                  << "s (until " << expiry << ")";
336         }
337         else if (rr_type == boost::net::dns::type_soa)
338             GlobalLogger.debug() << LogPrefix << "SOA resource";
339         else if (rr_type == boost::net::dns::type_ptr)
340             GlobalLogger.debug() << LogPrefix << "ptr resource";
341         else if (rr_type == boost::net::dns::type_hinfo)
342             GlobalLogger.debug() << LogPrefix << "hinfo resource";
343         else if (rr_type == boost::net::dns::type_mx)
344             GlobalLogger.debug() << LogPrefix << "mx resource";
345         else if (rr_type == boost::net::dns::type_txt)
346             GlobalLogger.debug() << LogPrefix << "txt resource";
347         else if (rr_type == boost::net::dns::type_srv)
348             GlobalLogger.debug() << LogPrefix << "srv resource";
349         else if (rr_type == boost::net::dns::type_axfr)
350             GlobalLogger.debug() << LogPrefix << "axfr resource";
351         else
352             GlobalLogger.debug() << LogPrefix << "unknown resource type: "
353                                  << std::showbase << std::hex
354                                  << static_cast<unsigned>(rr_item->rtype());
355     }
356 }
357
358
359 void DnsResolver::handle_unavailable()
360 {
361     // schedule new attempt in quite a while
362     StaleDataLongtermTimer.expires_from_now(
363                                      minutes(Config::StaleDataLongtermMinutes));
364     StaleDataLongtermTimer.async_wait(
365             boost::bind( &DnsResolver::wait_timer_timeout_handler,
366                          this, boost::asio::placeholders::error
367             )
368     );
369
370     // for now, admit failure
371     bool was_success = false;
372     finalize_resolve(was_success);
373 }
374
375
376 void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
377 {
378     // received at least one IP which could be for the queried host name 
379     // or the cname at the "end" of the cname list;
380     // but all IPs should be for the same
381     HostAddressVec addr_list;
382     std::string only_host_for_ips = result_ips[0].first;
383     BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
384     {
385         if ( host_and_addr.first != only_host_for_ips )
386             GlobalLogger.warning() << LogPrefix
387                 << "Received IPs for different hosts " << only_host_for_ips
388                 << " and " << host_and_addr.first << " in one DNS result! "
389                 << "--> ignore second";
390         else
391         {
392             GlobalLogger.notice() << LogPrefix << "Found IP "
393                       << host_and_addr.second.get_ip() << " with TTL "
394                       << host_and_addr.second.get_ttl().get_value() << "s";
395             addr_list.push_back(host_and_addr.second);
396         }
397     }
398     ResolverBase::update_cache( only_host_for_ips, addr_list );
399
400     // clean up
401     bool was_success = true;
402     finalize_resolve(was_success);
403 }
404
405
406 void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
407 {
408     // find the "last" cname in the list
409     // Hostname --> cname1 --> cname2 --> ... --> cnameN
410     // We assume here that this list might not be in order but that all cnames
411     //   form a single list (form one connected list and not several isolated)
412
413     std::string last_cname = "";
414     bool could_be_last;
415     BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
416     {
417         could_be_last = true;
418         BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
419         {
420             if (other.first == host_and_cname.second.Host)
421             {   // found cname for current cname
422                 could_be_last = false;
423                 break;
424             }
425         }
426         if (could_be_last)
427         {
428             last_cname = host_and_cname.second.Host;
429             break;
430         }
431     }
432
433     if (last_cname.empty())
434     {
435         GlobalLogger.error() << LogPrefix
436             << "Could not identify \"last\" CNAME to handle -- "
437             << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
438         GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
439         BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
440             GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
441                                              << host_and_cname.second.Host;
442         handle_unavailable();
443     }
444     else
445     {   // check cache for IP for this cname
446         bool check_up_to_date = true;
447         HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
448                                                               check_up_to_date);
449         if ( !cached_data.empty() )
450         {
451             bool was_success = true;
452             int cname_count = 1;  // define cache access as only 1
453             finalize_resolve(was_success, cname_count);
454         }
455         else
456         {   // get resolver for canonical name
457             ResolverItem resolver = DnsMaster::get_instance()
458                                     ->get_resolver_for(last_cname, Protocol);
459             callback_type callback = boost::bind(
460                                            &DnsResolver::cname_resolve_callback,
461                                            this, _1, _2 );
462             resolver->async_resolve( callback );
463
464             // treat a CNAME as a partial result: not enough to run callbacks
465             // from finalize_resolve, but enough to stop timers and reset
466             // RetryCount --> name resolution can take longer
467             stop_trying();
468         }
469     }
470 }
471
472
473 void DnsResolver::cname_resolve_callback(const bool was_success,
474                                          const int cname_count)
475 {
476     if ( OperationCancelled )
477     {   // async_resolve was cancelled --> callbacks already called
478         GlobalLogger.info() << LogPrefix
479                             << "Ignoring CNAME results since we were cancelled";
480         return;
481     }
482     else if (was_success)
483         GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
484     else
485         GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
486         // no use to schedule retry in this case since cname resolver must have
487         // failed several times and we can only re-start the same procedure with
488         // the same information
489
490     // cname counts like one more recursion step ...
491     finalize_resolve(was_success, cname_count+1);
492 }
493
494
495 void DnsResolver::finalize_resolve(const bool was_success,
496                                    const int cname_count)
497 {
498     // some consistency checks; failure might indicate a situation I had not
499     // anticipated during programming but might not be harmfull yet
500     if ( !IsResolving )
501         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
502                                             << "not resolving any more!";
503     if ( OperationCancelled )
504         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
505                                             << " was cancelled!";
506     if ( ResolverBase::CallbackList.empty() )
507         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
508                                             << "no callbacks!";
509     if ( RequestId != 0 )
510         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
511                                             << "waiting for DNS reply!";
512
513     // stop timers
514     stop_trying();
515
516     // schedule callbacks, clearing callback list
517     ResolverBase::schedule_callbacks(was_success, cname_count);
518
519     // finalize
520     GlobalLogger.notice() << LogPrefix << "finalized resolve"
521                           << " with success = " << was_success
522                           << " and cname_count = " << cname_count;
523     IsResolving = false;
524 }
525
526
527 void DnsResolver::stop_trying()
528 {
529     // cancel timers
530     GlobalLogger.debug() << LogPrefix << "Cancelling timers";
531     ResolveTimeoutTimer.cancel();
532     PauseBeforeRetryTimer.cancel();
533     StaleDataLongtermTimer.cancel();
534
535     // clean up
536     RetryCount = 0;
537 }
538
539
540 bool DnsResolver::is_resolving()
541 {
542     return IsResolving;
543 }
544
545
546 /**
547  * cancel a earlier call to async_resolve
548  *
549  * callbacks will be called with was_success=false; all internal operations
550  * will be cancelled and internal callbacks (timers, dns results) have no
551  * effect any more
552  */
553 void DnsResolver::cancel_resolve()
554 {
555     if ( !IsResolving )
556     {
557         GlobalLogger.info() << LogPrefix
558                << "Cancel called on non-resolving resolver -- ignore";
559         return;
560     }
561     else if (OperationCancelled)
562     {
563         GlobalLogger.info() << LogPrefix
564                << "Cancel called on cancelled resolver -- ignore";
565         return;
566     }
567
568     // set before finalize_resolve so can check in finalize_resolve that ID is
569     //   always 0; ID is not used any more since handle_dns_result stops if
570     //   OperationCancelled is true
571     RequestId = 0;
572
573     bool was_success = false;
574     int cname_count = 1;
575     finalize_resolve(was_success, cname_count);
576
577     // set after finalize_resolve, so can check in finalize_resolve that 
578     // OperationCancelled is never true
579     OperationCancelled = true;
580
581 }
582
583
584 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
585 {
586     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
587     {
588         GlobalLogger.warning() << LogPrefix
589                                << "Resolve timeout timer was cancelled!";
590         return;
591     }
592     else if (error)
593     {
594         GlobalLogger.warning() << LogPrefix
595                                << "resolve timeout handler received error "
596                                << error << " --> retry";
597         schedule_retry();
598     }
599     else if ( OperationCancelled )
600     {   // async_resolve was cancelled --> callbacks already called
601         GlobalLogger.info() << LogPrefix
602                             << "Ignoring DNS timeout since we were cancelled";
603         return;
604     }
605     else
606     {
607         GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
608         schedule_retry();
609     }
610 }
611
612
613 void DnsResolver::schedule_retry()
614 {
615     // cancel timers
616     ResolveTimeoutTimer.cancel();
617     PauseBeforeRetryTimer.cancel();
618
619     // increment timer
620     ++RetryCount;
621
622     if ( RetryCount > DnsMaster::get_instance()
623                       ->get_max_address_resolution_attempts() )
624     {   // too many re-tries
625         GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
626                             << "RetryCount " << RetryCount << " too high";
627         handle_unavailable();   // will call stop_trying i.e. reset RetryCount
628     }
629     else
630     {   // schedule retry
631         GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
632                             << RetryCount << ")";
633         PauseBeforeRetryTimer.expires_from_now(
634                 seconds(Config::PauseBeforeRetrySeconds));
635         PauseBeforeRetryTimer.async_wait(
636                 boost::bind( &DnsResolver::wait_timer_timeout_handler,
637                              this, boost::asio::placeholders::error) );
638     }
639 }
640
641 void DnsResolver::wait_timer_timeout_handler(
642                                          const boost::system::error_code &error)
643 {
644     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
645     {   // assume that our code cancelled this timer, so callbacks will be
646         // taken care of!
647         GlobalLogger.warning() << LogPrefix
648                                << "Resolve wait timer was cancelled! ";
649     }
650     else if (error)
651     {   // not sure what to do here, but callers waiting forever for a callback
652         // is probably the worst thing to happen, so call finalize_resolve
653         GlobalLogger.warning() << LogPrefix
654                                << "resolve wait handler received error "
655                                << error << "! Try to finalize resolve";
656         bool was_success = false;
657         finalize_resolve(was_success);
658     }
659     else if ( OperationCancelled )
660     {   // async_resolve was cancelled --> callbacks already called
661         GlobalLogger.info() << LogPrefix
662                           << "Ignoring waiting timeout since we were cancelled";
663         return;
664     }
665     else
666     {
667         GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
668         do_resolve();
669     }
670 }
671
672
673 //==============================================================================
674 // RETRIEVAL
675 //==============================================================================
676
677 HostAddress DnsResolver::get_next_ip()
678 {
679     // get cached data
680     HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
681
682     // if no results cached, return default-constructed HostAddress (0.0.0.0)
683     if ( cached_data.empty() )
684     {
685         HostAddress return_value;
686         return return_value;
687     }
688
689     // check validity of index (cache may have changed since last call)
690     if (NextIpIndex >= cached_data.size())
691         NextIpIndex = 0;
692
693     // return next IP
694     return cached_data[NextIpIndex++];
695 }
696
697 bool DnsResolver::have_up_to_date_ip()
698 {
699     return ! ResolverBase::get_cached_ips_recursively("", true).empty();
700 }
701
702 int DnsResolver::get_resolved_ip_count()
703 {
704     return ResolverBase::get_cached_ips_recursively().size();
705 }
706
707 // (created using vim -- the world's best text editor)
708