created and passed first unit tests for DNS; finished recovery from PingScheduler...
[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         ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
238
239     if ( !result_ips.empty() )
240         handle_ips( result_ips );
241     else if ( !result_cnames.empty() )
242         // no IPs but at least one cname --> find the "last" cname and
243         // re-start resolving with that
244         handle_cname(result_cnames);
245     else
246     {   // no answers --> cannot proceed
247         GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
248                                << "--> schedule retry";
249         schedule_retry();
250     }
251 }
252
253 /**
254  * gather IPs, CNAMEs and name servers from list of resource records;
255  *
256  * can be run on anwers(), autorities() and additional() sections of dns reply
257  * messages
258  * 
259  * @param rr_list: input list of resource records
260  * @param result_ips: output vector of ips
261  * @param result_cnames: output vector of cnames
262  * @param result_name_servers: output vector of name servers
263  */
264 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
265                                  std::vector<host_addr_pair> *result_ips,
266                                  std::vector<src_cname_pair> *result_cnames,
267                                  std::vector<string_pair> *result_name_servers)
268                                                                            const
269 {
270     using boost::net::dns::resource_base_t;
271     boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
272     BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
273     {
274         boost::net::dns::type_t rr_type = rr_item->rtype();
275         uint32_t ttl = rr_item->ttl();
276         std::string domain = rr_item->domain();
277         std::string expiry =
278                         boost::posix_time::to_simple_string(now + seconds(ttl));
279
280         if (rr_type == boost::net::dns::type_a)
281         {    // 'A' resource records carry IPv4 addresses
282             if (Protocol == DNS_IPv6)
283             {
284                 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
285                         << "because resolver was configured to only use IPv6.";
286                 continue;
287             }
288             boost::asio::ip::address_v4 ip =
289                 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
290                 ->address();
291             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
292             GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
293                                  << " with TTL " << ttl << "s (until "
294                                  << expiry << ")";
295         }
296         else if (rr_type == boost::net::dns::type_a6)
297         {   // 'AAAA' resource records carry IPv6 addresses
298             if (Protocol == DNS_IPv4)
299             {
300                 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
301                         << "because resolver was configured to only use IPv4.";
302                 continue;
303             }
304             boost::asio::ip::address_v6 ip =
305                 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
306                 ->address();
307             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
308             GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
309                                  << " with TTL " << ttl << "s (until "
310                                  << expiry << ")";
311         }
312         else if (rr_type == boost::net::dns::type_cname)
313         {   // 'CNAME' resource records that carry aliases
314             std::string cname =
315                 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
316                 ->canonicalname();
317             result_cnames->push_back( src_cname_pair(domain,
318                                                      Cname(cname, ttl)) );
319             GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
320                                  << cname << " with TTL " << ttl << "s (until "
321                                  << expiry << ")";
322         }
323         else if (rr_type == boost::net::dns::type_ns)
324         {   // NS (name_server) resource records
325             std::string name_server =
326                 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
327                 ->nameserver();
328             result_name_servers->push_back( string_pair(domain, name_server) );
329             GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
330                                  << " for " << domain << " with TTL " << ttl
331                                  << "s (until " << expiry << ")";
332         }
333         else if (rr_type == boost::net::dns::type_soa)
334             GlobalLogger.debug() << LogPrefix << "SOA resource";
335         else if (rr_type == boost::net::dns::type_ptr)
336             GlobalLogger.debug() << LogPrefix << "ptr resource";
337         else if (rr_type == boost::net::dns::type_hinfo)
338             GlobalLogger.debug() << LogPrefix << "hinfo resource";
339         else if (rr_type == boost::net::dns::type_mx)
340             GlobalLogger.debug() << LogPrefix << "mx resource";
341         else if (rr_type == boost::net::dns::type_txt)
342             GlobalLogger.debug() << LogPrefix << "txt resource";
343         else if (rr_type == boost::net::dns::type_srv)
344             GlobalLogger.debug() << LogPrefix << "srv resource";
345         else if (rr_type == boost::net::dns::type_axfr)
346             GlobalLogger.debug() << LogPrefix << "axfr resource";
347         else
348             GlobalLogger.debug() << LogPrefix << "unknown resource type: "
349                                  << std::showbase << std::hex
350                                  << static_cast<unsigned>(rr_item->rtype());
351     }
352 }
353
354
355 void DnsResolver::handle_unavailable()
356 {
357     // schedule new attempt in quite a while
358     StaleDataLongtermTimer.expires_from_now(
359                                      minutes(Config::StaleDataLongtermMinutes));
360     StaleDataLongtermTimer.async_wait(
361             boost::bind( &DnsResolver::wait_timer_timeout_handler,
362                          this, boost::asio::placeholders::error
363             )
364     );
365
366     // for now, admit failure
367     bool was_success = false;
368     finalize_resolve(was_success);
369 }
370
371
372 void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
373 {
374     // received at least one IP which could be for the queried host name 
375     // or the cname at the "end" of the cname list;
376     // but all IPs should be for the same
377     HostAddressVec addr_list;
378     std::string only_host_for_ips = result_ips[0].first;
379     BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
380     {
381         if ( host_and_addr.first != only_host_for_ips )
382             GlobalLogger.warning() << LogPrefix
383                 << "Received IPs for different hosts " << only_host_for_ips
384                 << " and " << host_and_addr.first << " in one DNS result! "
385                 << "--> ignore second";
386         else
387         {
388             GlobalLogger.notice() << LogPrefix << "Found IP "
389                       << host_and_addr.second.get_ip() << " with TTL "
390                       << host_and_addr.second.get_ttl().get_value() << "s";
391             addr_list.push_back(host_and_addr.second);
392         }
393     }
394     ResolverBase::update_cache( only_host_for_ips, addr_list );
395
396     // clean up
397     bool was_success = true;
398     finalize_resolve(was_success);
399 }
400
401
402 void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
403 {
404     // find the "last" cname in the list
405     // Hostname --> cname1 --> cname2 --> ... --> cnameN
406     // We assume here that this list might not be in order but that all cnames
407     //   form a single list (form one connected list and not several isolated)
408
409     std::string last_cname = "";
410     bool could_be_last;
411     BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
412     {
413         could_be_last = true;
414         BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
415         {
416             if (other.first == host_and_cname.second.Host)
417             {   // found cname for current cname
418                 could_be_last = false;
419                 break;
420             }
421         }
422         if (could_be_last)
423         {
424             last_cname = host_and_cname.second.Host;
425             break;
426         }
427     }
428
429     if (last_cname.empty())
430     {
431         GlobalLogger.error() << LogPrefix
432             << "Could not identify \"last\" CNAME to handle -- "
433             << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
434         GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
435         BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
436             GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
437                                              << host_and_cname.second.Host;
438         handle_unavailable();
439     }
440     else
441     {   // check cache for IP for this cname
442         bool check_up_to_date = true;
443         HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
444                                                               check_up_to_date);
445         if ( !cached_data.empty() )
446         {
447             bool was_success = true;
448             int cname_count = 1;  // define cache access as only 1
449             finalize_resolve(was_success, cname_count);
450         }
451         else
452         {   // get resolver for canonical name
453             ResolverItem resolver = DnsMaster::get_instance()
454                                     ->get_resolver_for(last_cname, Protocol);
455             callback_type callback = boost::bind(
456                                            &DnsResolver::cname_resolve_callback,
457                                            this, _1, _2 );
458             resolver->async_resolve( callback );
459
460             // treat a CNAME as a partial result: not enough to run callbacks
461             // from finalize_resolve, but enough to stop timers and reset
462             // RetryCount --> name resolution can take longer
463             stop_trying();
464         }
465     }
466 }
467
468
469 void DnsResolver::cname_resolve_callback(const bool was_success,
470                                          const int cname_count)
471 {
472     if ( OperationCancelled )
473     {   // async_resolve was cancelled --> callbacks already called
474         GlobalLogger.info() << LogPrefix
475                             << "Ignoring CNAME results since we were cancelled";
476         return;
477     }
478     else if (was_success)
479         GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
480     else
481         GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
482         // no use to schedule retry in this case since cname resolver must have
483         // failed several times and we can only re-start the same procedure with
484         // the same information
485
486     // cname counts like one more recursion step ...
487     finalize_resolve(was_success, cname_count+1);
488 }
489
490
491 void DnsResolver::finalize_resolve(const bool was_success,
492                                    const int cname_count)
493 {
494     // some consistency checks; failure might indicate a situation I had not
495     // anticipated during programming but might not be harmfull yet
496     if ( !IsResolving )
497         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
498                                             << "not resolving any more!";
499     if ( OperationCancelled )
500         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
501                                             << " was cancelled!";
502     if ( ResolverBase::CallbackList.empty() )
503         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
504                                             << "no callbacks!";
505     if ( RequestId != 0 )
506         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
507                                             << "waiting for DNS reply!";
508
509     // stop timers
510     stop_trying();
511
512     // schedule callbacks, clearing callback list
513     ResolverBase::schedule_callbacks(was_success, cname_count);
514
515     // finalize
516     GlobalLogger.notice() << LogPrefix << "finalized resolve"
517                           << " with success = " << was_success
518                           << " and cname_count = " << cname_count;
519     IsResolving = false;
520 }
521
522
523 void DnsResolver::stop_trying()
524 {
525     // cancel timers
526     GlobalLogger.debug() << LogPrefix << "Cancelling timers";
527     ResolveTimeoutTimer.cancel();
528     PauseBeforeRetryTimer.cancel();
529     StaleDataLongtermTimer.cancel();
530
531     // clean up
532     RetryCount = 0;
533 }
534
535
536 bool DnsResolver::is_resolving()
537 {
538     return IsResolving;
539 }
540
541
542 /**
543  * cancel a earlier call to async_resolve
544  *
545  * callbacks will be called with was_success=false; all internal operations
546  * will be cancelled and internal callbacks (timers, dns results) have no
547  * effect any more
548  */
549 void DnsResolver::cancel_resolve()
550 {
551     if ( !IsResolving )
552     {
553         GlobalLogger.info() << LogPrefix
554                << "Cancel called on non-resolving resolver -- ignore";
555         return;
556     }
557     else if (OperationCancelled)
558     {
559         GlobalLogger.info() << LogPrefix
560                << "Cancel called on cancelled resolver -- ignore";
561         return;
562     }
563
564     // set before finalize_resolve so can check in finalize_resolve that ID is
565     //   always 0; ID is not used any more since handle_dns_result stops if
566     //   OperationCancelled is true
567     RequestId = 0;
568
569     bool was_success = false;
570     int cname_count = 1;
571     finalize_resolve(was_success, cname_count);
572
573     // set after finalize_resolve, so can check in finalize_resolve that 
574     // OperationCancelled is never true
575     OperationCancelled = true;
576
577 }
578
579
580 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
581 {
582     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
583     {
584         GlobalLogger.info() << LogPrefix
585                             << "Resolve timeout timer was cancelled!";
586         return;
587     }
588     else if (error)
589     {
590         GlobalLogger.warning() << LogPrefix
591                                << "resolve timeout handler received error "
592                                << error << " --> retry";
593         schedule_retry();
594     }
595     else if ( OperationCancelled )
596     {   // async_resolve was cancelled --> callbacks already called
597         GlobalLogger.info() << LogPrefix
598                             << "Ignoring DNS timeout since we were cancelled";
599         return;
600     }
601     else
602     {
603         GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
604         schedule_retry();
605     }
606 }
607
608
609 void DnsResolver::schedule_retry()
610 {
611     // cancel timers
612     ResolveTimeoutTimer.cancel();
613     PauseBeforeRetryTimer.cancel();
614
615     // increment timer
616     ++RetryCount;
617
618     if ( RetryCount > DnsMaster::get_instance()
619                       ->get_max_address_resolution_attempts() )
620     {   // too many re-tries
621         GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
622                             << "RetryCount " << RetryCount << " too high";
623         handle_unavailable();   // will call stop_trying i.e. reset RetryCount
624     }
625     else
626     {   // schedule retry
627         GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
628                             << RetryCount << ")";
629         PauseBeforeRetryTimer.expires_from_now(
630                 seconds(Config::PauseBeforeRetrySeconds));
631         PauseBeforeRetryTimer.async_wait(
632                 boost::bind( &DnsResolver::wait_timer_timeout_handler,
633                              this, boost::asio::placeholders::error) );
634     }
635 }
636
637 void DnsResolver::wait_timer_timeout_handler(
638                                          const boost::system::error_code &error)
639 {
640     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
641     {   // assume that our code cancelled this timer, so callbacks will be
642         // taken care of!
643         GlobalLogger.info() << LogPrefix
644                             << "Resolve wait timer was cancelled! ";
645     }
646     else if (error)
647     {   // not sure what to do here, but callers waiting forever for a callback
648         // is probably the worst thing to happen, so call finalize_resolve
649         GlobalLogger.warning() << LogPrefix
650                                << "resolve wait handler received error "
651                                << error << "! Try to finalize resolve";
652         bool was_success = false;
653         finalize_resolve(was_success);
654     }
655     else if ( OperationCancelled )
656     {   // async_resolve was cancelled --> callbacks already called
657         GlobalLogger.info() << LogPrefix
658                           << "Ignoring waiting timeout since we were cancelled";
659         return;
660     }
661     else
662     {
663         GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
664         do_resolve();
665     }
666 }
667
668
669 //==============================================================================
670 // RETRIEVAL
671 //==============================================================================
672
673 HostAddress DnsResolver::get_next_ip(bool check_up_to_date)
674 {
675     // get cached data
676     // (do not use arg check_up_to_date here in order to give NextIpIndex
677     //  a chance to stay above number of outdate IPs)
678     HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
679
680     // if no results cached, return default-constructed HostAddress (0.0.0.0)
681     HostAddress return_candidate;
682     if ( cached_data.empty() )
683     {
684         GlobalLogger.debug() << LogPrefix << "Get next IP: nothing cached";
685         return return_candidate;
686     }
687
688     int n_iter = 0;
689     std::size_t n_ips = cached_data.size();
690     uint32_t ttl_thresh = static_cast<uint32_t>( DnsMaster::get_instance()
691                                             ->get_resolved_ip_ttl_threshold() );
692
693     GlobalLogger.info() << LogPrefix << "Get next IP from cached result of "
694         << n_ips << " IPs; first index to consider is " << NextIpIndex
695         << "; TTL thresh = " << ttl_thresh << " is used: " << check_up_to_date;
696
697     // loop until we have found a cached result (that is up to date)
698     //   or until we have tried all cached IPs
699     while (true)
700     {
701         // check index since cache size may have changed since last call
702         if (NextIpIndex >= n_ips)
703             NextIpIndex = 0;
704         else if ( n_iter >= n_ips)
705             return HostAddress();   // have checked all candidates
706         else
707         {   // there are candidates left to consider
708             return_candidate = cached_data[NextIpIndex++];
709             if (!check_up_to_date)
710                 return return_candidate;
711             else if (cached_data[NextIpIndex].get_ttl().get_updated_value()
712                      > ttl_thresh)
713                 return cached_data[++NextIpIndex];
714             else
715                 ++n_iter;
716         }
717     }
718 }
719
720 bool DnsResolver::have_up_to_date_ip()
721 {
722     return get_resolved_ip_count() > 0;
723 }
724
725 int DnsResolver::get_resolved_ip_count()
726 {
727     // run with empty hostname (--> uses internal var Hostname)
728     // and check_up_to_date = true
729     return ResolverBase::get_cached_ips_recursively("", true).size();
730 }
731
732 // (created using vim -- the world's best text editor)
733