tested new DNS with internal server, make more robust against caching; works nicely now
[pingcheck] / src / dns / dnsresolver.cpp
CommitLineData
36ad976b
CH
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
4e7b6ff9
CH
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
36ad976b
CH
25 */
26
c5b4902d
CH
27#include "dns/dnsresolver.h"
28
29#include <stdint.h>
e91538f0 30#include <sstream>
36ad976b 31
4e7b6ff9
CH
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>
ad83004d
CH
37#include <boost/uuid/uuid.hpp>
38#include <boost/uuid/uuid_io.hpp>
4e7b6ff9 39
36ad976b 40#include <logfunc.hpp>
4e7b6ff9 41
36ad976b 42using I2n::Logger::GlobalLogger;
4e7b6ff9 43using boost::posix_time::seconds;
36ad976b
CH
44
45namespace Config
46{
fd62d09f
CH
47 const int ResolveTimeoutSeconds = 2;
48 const int PauseBeforeRetrySeconds = 1;
49 const int StaleDataLongtermSeconds = 5*60;
36ad976b 50 const int DNS_PORT = 53;
36ad976b
CH
51}
52
4e7b6ff9
CH
53DnsResolver::DnsResolver(IoServiceItem &io_serv,
54 const std::string &hostname,
923626c0 55 const DnsIpProtocol &protocol,
96779587 56 const DnsCacheItem cache,
96779587 57 const boost::asio::ip::address &name_server)
4e7b6ff9 58 : ResolverBase( io_serv, hostname, cache )
e91538f0
CH
59 , Socket( *io_serv, ip::udp::endpoint(ip::udp::v4(), 0))
60 // just connect to anything, will specify sender/receiver later
4e7b6ff9 61 , ReceiveBuffer()
ad83004d 62 , RequestBuffer()
923626c0 63 , Protocol( protocol )
4e7b6ff9 64 , NameServer( name_server, Config::DNS_PORT )
36ad976b
CH
65 , ResolveTimeoutTimer( *io_serv )
66 , PauseBeforeRetryTimer( *io_serv )
67 , StaleDataLongtermTimer( *io_serv )
4e7b6ff9 68 , NextIpIndex( 0 )
36ad976b
CH
69 , RetryCount( 0 )
70 , IsResolving( false )
e91538f0 71 , LogPrefix( "DnsResolver" )
ad83004d
CH
72 , RandomIdGenerator()
73 , RequestId( 0 )
e18c1337 74 , OperationCancelled( false )
fd62d09f 75 , LongtermTimerIsActive( false )
e91538f0
CH
76{
77 std::stringstream temp;
78 temp << "Dns(" << ResolverBase::Hostname << "): ";
79 LogPrefix = temp.str();
ad83004d 80
e91538f0 81}
96779587 82
c5b4902d
CH
83DnsResolver::~DnsResolver()
84{
85 boost::system::error_code error;
e18c1337
CH
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
c5b4902d
CH
92
93 Socket.close(error);
94 if ( error )
e91538f0 95 GlobalLogger.warning() << LogPrefix << "Received error " << error
c5b4902d
CH
96 << " when closing socket for DNS";
97}
98
99
96779587
CH
100
101//==============================================================================
102// ASYNC RESOLVE
103//==============================================================================
36ad976b
CH
104
105/**
c5b4902d 106 * copied here code from boost::net::dns::resolve.hpp, since want async
36ad976b 107 * operation and that is used only internally, there
36ad976b 108 */
4e7b6ff9 109void DnsResolver::do_resolve()
36ad976b 110{
36ad976b 111 // check if resolving already
4e7b6ff9 112 if (IsResolving)
36ad976b 113 {
e91538f0 114 GlobalLogger.info() << LogPrefix
4e7b6ff9 115 << "Call to do_resolve ignored since resolving already";
36ad976b
CH
116 return;
117 }
ad83004d 118 IsResolving = true;
e18c1337 119 OperationCancelled = false;
36ad976b 120
e18c1337
CH
121 GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
122 << to_string(Protocol) << " using name server " << NameServer;
e91538f0 123
36ad976b
CH
124 // just to be sure: cancel timers
125 ResolveTimeoutTimer.cancel();
126 PauseBeforeRetryTimer.cancel();
127 StaleDataLongtermTimer.cancel();
fd62d09f 128 LongtermTimerIsActive = false;
36ad976b
CH
129
130 // create DNS request
e18c1337 131 boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
23f51766 132 dns_message.recursive(true);
4e7b6ff9
CH
133 dns_message.action(boost::net::dns::message::query);
134 dns_message.opcode(boost::net::dns::message::squery);
ad83004d
CH
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();
36ad976b
CH
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)
4e7b6ff9 150 );
36ad976b
CH
151
152 // schedule timeout
4e7b6ff9
CH
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) );
36ad976b
CH
158
159 // send dns request
ad83004d 160 dns_message.encode(RequestBuffer);
e91538f0
CH
161 size_t bytes_sent;
162 try
163 {
164 bytes_sent = Socket.send_to(
ad83004d 165 boost::asio::buffer(RequestBuffer.get_array()),
e91538f0
CH
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();
e18c1337 173 schedule_retry();
e91538f0
CH
174 return;
175 }
176
177 if ( bytes_sent == 0 )
178 {
179 GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
e18c1337 180 schedule_retry();
e91538f0
CH
181 return;
182 }
36ad976b
CH
183}
184
185
4e7b6ff9 186void DnsResolver::handle_dns_result(const boost::system::error_code &error,
ad83004d 187 const std::size_t bytes_transferred)
36ad976b 188{
e18c1337 189 if (error)
36ad976b 190 {
e91538f0 191 GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
fd62d09f 192 << error << " --> request retry";
e18c1337
CH
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";
36ad976b
CH
200 return;
201 }
202
ad83004d
CH
203 GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
204 << bytes_transferred << " bytes transferred)";
205
36ad976b
CH
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);
4e7b6ff9 209 boost::net::dns::message result_message;
36ad976b
CH
210 result_message.decode( ReceiveBuffer );
211
ad83004d
CH
212 // check ID
213 if (RequestId != result_message.id())
e18c1337 214 GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
ad83004d
CH
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;
e18c1337 220 RequestId = 0;
ad83004d
CH
221
222 // loop over answers, remembering ips and cnames
4e7b6ff9
CH
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
e18c1337 226 std::vector<host_addr_pair> result_ips;
dbe986b9
CH
227 std::vector<src_cname_pair> result_cnames;
228 std::vector<string_pair> result_name_servers;
36ad976b 229
c1abff61 230 GlobalLogger.debug() << LogPrefix <<"Checking ANSWERS section of dns reply";
e18c1337 231 gather_results(result_message.answers(), &result_ips, &result_cnames,
dbe986b9 232 &result_name_servers);
e18c1337
CH
233 // results should have the logical order
234 // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
ad83004d 235
e18c1337 236 // remember cname list (if there were any)
dbe986b9 237 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337 238 ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
ad83004d 239
e18c1337
CH
240 if ( !result_ips.empty() )
241 handle_ips( result_ips );
ad83004d 242 else if ( !result_cnames.empty() )
e18c1337
CH
243 // no IPs but at least one cname --> find the "last" cname and
244 // re-start resolving with that
245 handle_cname(result_cnames);
ad83004d 246 else
23f51766
CH
247 { // no answers --> cannot proceed
248 GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
fd62d09f 249 << "--> request retry";
23f51766 250 schedule_retry();
ad83004d 251 }
ad83004d
CH
252}
253
23f51766
CH
254/**
255 * gather IPs, CNAMEs and name servers from list of resource records;
256 *
257 * can be run on anwers(), autorities() and additional() sections of dns reply
258 * messages
259 *
260 * @param rr_list: input list of resource records
261 * @param result_ips: output vector of ips
262 * @param result_cnames: output vector of cnames
263 * @param result_name_servers: output vector of name servers
264 */
265void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
e18c1337 266 std::vector<host_addr_pair> *result_ips,
dbe986b9
CH
267 std::vector<src_cname_pair> *result_cnames,
268 std::vector<string_pair> *result_name_servers)
ad83004d
CH
269 const
270{
4e7b6ff9 271 using boost::net::dns::resource_base_t;
dbe986b9 272 boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
23f51766 273 BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
36ad976b 274 {
4e7b6ff9 275 boost::net::dns::type_t rr_type = rr_item->rtype();
ad83004d
CH
276 uint32_t ttl = rr_item->ttl();
277 std::string domain = rr_item->domain();
dbe986b9
CH
278 std::string expiry =
279 boost::posix_time::to_simple_string(now + seconds(ttl));
36ad976b
CH
280
281 if (rr_type == boost::net::dns::type_a)
282 { // 'A' resource records carry IPv4 addresses
923626c0
CH
283 if (Protocol == DNS_IPv6)
284 {
e91538f0
CH
285 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
286 << "because resolver was configured to only use IPv6.";
923626c0
CH
287 continue;
288 }
4e7b6ff9
CH
289 boost::asio::ip::address_v4 ip =
290 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
291 ->address();
e18c1337 292 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
dbe986b9
CH
293 GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
294 << " with TTL " << ttl << "s (until "
295 << expiry << ")";
36ad976b
CH
296 }
297 else if (rr_type == boost::net::dns::type_a6)
298 { // 'AAAA' resource records carry IPv6 addresses
923626c0
CH
299 if (Protocol == DNS_IPv4)
300 {
e91538f0
CH
301 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
302 << "because resolver was configured to only use IPv4.";
923626c0
CH
303 continue;
304 }
4e7b6ff9
CH
305 boost::asio::ip::address_v6 ip =
306 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
307 ->address();
e18c1337 308 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
dbe986b9
CH
309 GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
310 << " with TTL " << ttl << "s (until "
311 << expiry << ")";
36ad976b
CH
312 }
313 else if (rr_type == boost::net::dns::type_cname)
314 { // 'CNAME' resource records that carry aliases
4e7b6ff9
CH
315 std::string cname =
316 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
317 ->canonicalname();
dbe986b9
CH
318 result_cnames->push_back( src_cname_pair(domain,
319 Cname(cname, ttl)) );
320 GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
321 << cname << " with TTL " << ttl << "s (until "
322 << expiry << ")";
36ad976b
CH
323 }
324 else if (rr_type == boost::net::dns::type_ns)
dbe986b9
CH
325 { // NS (name_server) resource records
326 std::string name_server =
ad83004d
CH
327 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
328 ->nameserver();
dbe986b9
CH
329 result_name_servers->push_back( string_pair(domain, name_server) );
330 GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
331 << " for " << domain << " with TTL " << ttl
332 << "s (until " << expiry << ")";
ad83004d 333 }
36ad976b 334 else if (rr_type == boost::net::dns::type_soa)
e91538f0 335 GlobalLogger.debug() << LogPrefix << "SOA resource";
36ad976b 336 else if (rr_type == boost::net::dns::type_ptr)
e91538f0 337 GlobalLogger.debug() << LogPrefix << "ptr resource";
36ad976b 338 else if (rr_type == boost::net::dns::type_hinfo)
e91538f0 339 GlobalLogger.debug() << LogPrefix << "hinfo resource";
36ad976b 340 else if (rr_type == boost::net::dns::type_mx)
e91538f0 341 GlobalLogger.debug() << LogPrefix << "mx resource";
36ad976b 342 else if (rr_type == boost::net::dns::type_txt)
e91538f0 343 GlobalLogger.debug() << LogPrefix << "txt resource";
36ad976b 344 else if (rr_type == boost::net::dns::type_srv)
e91538f0 345 GlobalLogger.debug() << LogPrefix << "srv resource";
36ad976b 346 else if (rr_type == boost::net::dns::type_axfr)
e91538f0 347 GlobalLogger.debug() << LogPrefix << "axfr resource";
36ad976b 348 else
ad83004d
CH
349 GlobalLogger.debug() << LogPrefix << "unknown resource type: "
350 << std::showbase << std::hex
351 << static_cast<unsigned>(rr_item->rtype());
36ad976b 352 }
36ad976b
CH
353}
354
355
36ad976b
CH
356void DnsResolver::handle_unavailable()
357{
358 // schedule new attempt in quite a while
4e7b6ff9 359 StaleDataLongtermTimer.expires_from_now(
fd62d09f 360 seconds(Config::StaleDataLongtermSeconds));
36ad976b 361 StaleDataLongtermTimer.async_wait(
4e7b6ff9
CH
362 boost::bind( &DnsResolver::wait_timer_timeout_handler,
363 this, boost::asio::placeholders::error
36ad976b
CH
364 )
365 );
fd62d09f 366 LongtermTimerIsActive = true;
36ad976b
CH
367
368 // for now, admit failure
369 bool was_success = false;
96779587 370 finalize_resolve(was_success);
36ad976b
CH
371}
372
e18c1337
CH
373
374void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
36ad976b 375{
e18c1337
CH
376 // received at least one IP which could be for the queried host name
377 // or the cname at the "end" of the cname list;
378 // but all IPs should be for the same
379 HostAddressVec addr_list;
380 std::string only_host_for_ips = result_ips[0].first;
381 BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
382 {
383 if ( host_and_addr.first != only_host_for_ips )
384 GlobalLogger.warning() << LogPrefix
385 << "Received IPs for different hosts " << only_host_for_ips
386 << " and " << host_and_addr.first << " in one DNS result! "
387 << "--> ignore second";
388 else
389 {
390 GlobalLogger.notice() << LogPrefix << "Found IP "
391 << host_and_addr.second.get_ip() << " with TTL "
392 << host_and_addr.second.get_ttl().get_value() << "s";
393 addr_list.push_back(host_and_addr.second);
394 }
395 }
396 ResolverBase::update_cache( only_host_for_ips, addr_list );
36ad976b 397
e18c1337
CH
398 // clean up
399 bool was_success = true;
400 finalize_resolve(was_success);
401}
402
403
dbe986b9 404void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
e18c1337
CH
405{
406 // find the "last" cname in the list
407 // Hostname --> cname1 --> cname2 --> ... --> cnameN
408 // We assume here that this list might not be in order but that all cnames
dbe986b9
CH
409 // form a single list (form one connected list and not several isolated)
410
e18c1337
CH
411 std::string last_cname = "";
412 bool could_be_last;
dbe986b9 413 BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337
CH
414 {
415 could_be_last = true;
dbe986b9 416 BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
e18c1337 417 {
23f51766 418 if (other.first == host_and_cname.second.Host)
e18c1337
CH
419 { // found cname for current cname
420 could_be_last = false;
421 break;
422 }
423 }
424 if (could_be_last)
425 {
23f51766 426 last_cname = host_and_cname.second.Host;
e18c1337
CH
427 break;
428 }
429 }
430
431 if (last_cname.empty())
432 {
433 GlobalLogger.error() << LogPrefix
434 << "Could not identify \"last\" CNAME to handle -- "
435 << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
436 GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
dbe986b9 437 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337 438 GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
23f51766 439 << host_and_cname.second.Host;
e18c1337
CH
440 handle_unavailable();
441 }
442 else
443 { // check cache for IP for this cname
444 bool check_up_to_date = true;
445 HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
446 check_up_to_date);
447 if ( !cached_data.empty() )
448 {
449 bool was_success = true;
23f51766
CH
450 int cname_count = 1; // define cache access as only 1
451 finalize_resolve(was_success, cname_count);
e18c1337
CH
452 }
453 else
454 { // get resolver for canonical name
e18c1337
CH
455 ResolverItem resolver = DnsMaster::get_instance()
456 ->get_resolver_for(last_cname, Protocol);
457 callback_type callback = boost::bind(
458 &DnsResolver::cname_resolve_callback,
459 this, _1, _2 );
460 resolver->async_resolve( callback );
461
462 // treat a CNAME as a partial result: not enough to run callbacks
463 // from finalize_resolve, but enough to stop timers and reset
464 // RetryCount --> name resolution can take longer
72be9e7d 465 stop_trying(true);
e18c1337
CH
466 }
467 }
36ad976b
CH
468}
469
470
e18c1337 471void DnsResolver::cname_resolve_callback(const bool was_success,
23f51766 472 const int cname_count)
36ad976b 473{
e18c1337
CH
474 if ( OperationCancelled )
475 { // async_resolve was cancelled --> callbacks already called
476 GlobalLogger.info() << LogPrefix
477 << "Ignoring CNAME results since we were cancelled";
478 return;
479 }
480 else if (was_success)
72be9e7d 481 {
e18c1337 482 GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
72be9e7d
CH
483 finalize_resolve(was_success, cname_count+1);
484 }
96779587 485 else
72be9e7d 486 {
e18c1337
CH
487 GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
488 // no use to schedule retry in this case since cname resolver must have
23f51766 489 // failed several times and we can only re-start the same procedure with
72be9e7d
CH
490 // the same information. But can re-try later
491 handle_unavailable();
492 }
dbe986b9
CH
493}
494
495
96779587 496void DnsResolver::finalize_resolve(const bool was_success,
23f51766 497 const int cname_count)
96779587 498{
e18c1337
CH
499 // some consistency checks; failure might indicate a situation I had not
500 // anticipated during programming but might not be harmfull yet
501 if ( !IsResolving )
502 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
503 << "not resolving any more!";
504 if ( OperationCancelled )
505 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
506 << " was cancelled!";
507 if ( ResolverBase::CallbackList.empty() )
508 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
509 << "no callbacks!";
510 if ( RequestId != 0 )
511 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
512 << "waiting for DNS reply!";
513
96779587 514 // stop timers
72be9e7d 515 stop_trying(was_success);
96779587
CH
516
517 // schedule callbacks, clearing callback list
23f51766 518 ResolverBase::schedule_callbacks(was_success, cname_count);
96779587
CH
519
520 // finalize
e18c1337 521 GlobalLogger.notice() << LogPrefix << "finalized resolve"
96779587 522 << " with success = " << was_success
23f51766 523 << " and cname_count = " << cname_count;
96779587
CH
524 IsResolving = false;
525}
526
e18c1337 527
72be9e7d
CH
528/**
529 * arg was_success determines if stop trying forever or just for the moment
530 * --> determines if we cancel StaleDataLongtermTimer or not
531 */
532void DnsResolver::stop_trying(bool was_success)
96779587
CH
533{
534 // cancel timers
e91538f0 535 GlobalLogger.debug() << LogPrefix << "Cancelling timers";
96779587
CH
536 ResolveTimeoutTimer.cancel();
537 PauseBeforeRetryTimer.cancel();
72be9e7d
CH
538
539 if (was_success)
540 {
541 StaleDataLongtermTimer.cancel();
fd62d09f 542 LongtermTimerIsActive = false;
72be9e7d 543 }
96779587
CH
544
545 // clean up
546 RetryCount = 0;
547}
548
e18c1337 549
72be9e7d
CH
550/**
551 * return true if resolver is currently resolving
552 *
553 * Is true from call to async_resolve until callbacks
554 * --> returns true if waiting for result or (short-term) retry
555 *
556 * However, does NOT tell you if the (long-term) stale timeout is active!
557 * That timer has no effect on result, need to check is_waiting_to_resolve
558 * for that
559 */
560bool DnsResolver::is_resolving() const
e18c1337
CH
561{
562 return IsResolving;
563}
564
565
dbe986b9 566/**
72be9e7d
CH
567 * returns true if either is_resolving or the long-term timer is active
568 *
569 * is_resolving returns true if the short-term retry timer is active
570 */
571bool DnsResolver::is_waiting_to_resolve() const
572{
fd62d09f 573 return IsResolving || LongtermTimerIsActive;
72be9e7d
CH
574}
575
576
577/**
dbe986b9
CH
578 * cancel a earlier call to async_resolve
579 *
580 * callbacks will be called with was_success=false; all internal operations
581 * will be cancelled and internal callbacks (timers, dns results) have no
72be9e7d 582 * effect any more; cancels also the long-term stale-data timer
dbe986b9 583 */
e18c1337
CH
584void DnsResolver::cancel_resolve()
585{
fd62d09f 586 if ( !IsResolving && !LongtermTimerIsActive)
e18c1337 587 {
fd62d09f
CH
588 GlobalLogger.info() << LogPrefix << "Cancel called on non-resolving, "
589 << "non-waiting resolver -- ignore";
e18c1337
CH
590 return;
591 }
592 else if (OperationCancelled)
593 {
594 GlobalLogger.info() << LogPrefix
595 << "Cancel called on cancelled resolver -- ignore";
596 return;
597 }
fd62d09f 598 GlobalLogger.info() << LogPrefix << "Cancel resolver";
e18c1337 599
e18c1337 600 // set before finalize_resolve so can check in finalize_resolve that ID is
dbe986b9 601 // always 0; ID is not used any more since handle_dns_result stops if
e18c1337
CH
602 // OperationCancelled is true
603 RequestId = 0;
604
fd62d09f
CH
605 if ( IsResolving )
606 {
607 bool was_success = false;
608 int cname_count = 1;
609 finalize_resolve(was_success, cname_count);
610 }
e18c1337 611
72be9e7d
CH
612 // also cancel the long-term timer
613 StaleDataLongtermTimer.cancel();
fd62d09f 614 LongtermTimerIsActive = false;
72be9e7d 615
e18c1337 616 // set after finalize_resolve, so can check in finalize_resolve that
dbe986b9 617 // OperationCancelled is never true
e18c1337
CH
618 OperationCancelled = true;
619
620}
621
622
4e7b6ff9 623void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
96779587
CH
624{
625 if ( error == boost::asio::error::operation_aborted ) // cancelled
626 {
fd62d09f
CH
627 GlobalLogger.debug() << LogPrefix
628 << "Resolve timeout timer was cancelled!";
96779587
CH
629 return;
630 }
631 else if (error)
632 {
e91538f0
CH
633 GlobalLogger.warning() << LogPrefix
634 << "resolve timeout handler received error "
fd62d09f 635 << error << " --> request retry";
dbe986b9 636 schedule_retry();
96779587 637 }
e18c1337
CH
638 else if ( OperationCancelled )
639 { // async_resolve was cancelled --> callbacks already called
640 GlobalLogger.info() << LogPrefix
dbe986b9 641 << "Ignoring DNS timeout since we were cancelled";
e18c1337
CH
642 return;
643 }
644 else
645 {
646 GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
e18c1337
CH
647 schedule_retry();
648 }
649}
650
96779587 651
e18c1337
CH
652void DnsResolver::schedule_retry()
653{
23f51766 654 // cancel timers
e18c1337
CH
655 ResolveTimeoutTimer.cancel();
656 PauseBeforeRetryTimer.cancel();
36ad976b 657
96779587
CH
658 // increment timer
659 ++RetryCount;
660
923626c0
CH
661 if ( RetryCount > DnsMaster::get_instance()
662 ->get_max_address_resolution_attempts() )
e18c1337
CH
663 { // too many re-tries
664 GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
665 << "RetryCount " << RetryCount << " too high";
666 handle_unavailable(); // will call stop_trying i.e. reset RetryCount
96779587
CH
667 }
668 else
669 { // schedule retry
e18c1337
CH
670 GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
671 << RetryCount << ")";
96779587 672 PauseBeforeRetryTimer.expires_from_now(
4e7b6ff9 673 seconds(Config::PauseBeforeRetrySeconds));
96779587 674 PauseBeforeRetryTimer.async_wait(
4e7b6ff9
CH
675 boost::bind( &DnsResolver::wait_timer_timeout_handler,
676 this, boost::asio::placeholders::error) );
96779587
CH
677 }
678}
679
680void DnsResolver::wait_timer_timeout_handler(
681 const boost::system::error_code &error)
682{
683 if ( error == boost::asio::error::operation_aborted ) // cancelled
e18c1337
CH
684 { // assume that our code cancelled this timer, so callbacks will be
685 // taken care of!
fd62d09f
CH
686 GlobalLogger.debug() << LogPrefix
687 << "Resolve wait timer was cancelled! ";
e18c1337 688 }
96779587 689 else if (error)
e18c1337
CH
690 { // not sure what to do here, but callers waiting forever for a callback
691 // is probably the worst thing to happen, so call finalize_resolve
e91538f0 692 GlobalLogger.warning() << LogPrefix
e18c1337
CH
693 << "resolve wait handler received error "
694 << error << "! Try to finalize resolve";
695 bool was_success = false;
696 finalize_resolve(was_success);
697 }
698 else if ( OperationCancelled )
699 { // async_resolve was cancelled --> callbacks already called
700 GlobalLogger.info() << LogPrefix
701 << "Ignoring waiting timeout since we were cancelled";
702 return;
703 }
96779587
CH
704 else
705 {
e91538f0 706 GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
fd62d09f 707 IsResolving = false; // will be set to true immediately in do_resolve
4e7b6ff9 708 do_resolve();
96779587 709 }
36ad976b
CH
710}
711
712
96779587
CH
713//==============================================================================
714// RETRIEVAL
715//==============================================================================
716
26b0f687 717HostAddress DnsResolver::get_next_ip(bool check_up_to_date)
36ad976b
CH
718{
719 // get cached data
26b0f687 720 // (do not use arg check_up_to_date here in order to give NextIpIndex
fd62d09f 721 // a chance to stay above number of outdated IPs)
e18c1337 722 HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
96779587
CH
723
724 // if no results cached, return default-constructed HostAddress (0.0.0.0)
26b0f687 725 HostAddress return_candidate;
96779587 726 if ( cached_data.empty() )
4e7b6ff9 727 {
26b0f687
CH
728 GlobalLogger.debug() << LogPrefix << "Get next IP: nothing cached";
729 return return_candidate;
4e7b6ff9 730 }
36ad976b 731
fd62d09f 732 std::size_t n_iter = 0;
26b0f687
CH
733 std::size_t n_ips = cached_data.size();
734 uint32_t ttl_thresh = static_cast<uint32_t>( DnsMaster::get_instance()
735 ->get_resolved_ip_ttl_threshold() );
36ad976b 736
26b0f687
CH
737 GlobalLogger.info() << LogPrefix << "Get next IP from cached result of "
738 << n_ips << " IPs; first index to consider is " << NextIpIndex
72be9e7d 739 << "; TTL thresh=" << ttl_thresh << "s is used: " << check_up_to_date;
26b0f687
CH
740
741 // loop until we have found a cached result (that is up to date)
742 // or until we have tried all cached IPs
743 while (true)
744 {
745 // check index since cache size may have changed since last call
746 if (NextIpIndex >= n_ips)
72be9e7d
CH
747 {
748 GlobalLogger.debug() << LogPrefix << "Reset NextIpIndex";
26b0f687 749 NextIpIndex = 0;
72be9e7d 750 }
26b0f687 751 else if ( n_iter >= n_ips)
72be9e7d
CH
752 {
753 GlobalLogger.debug() << LogPrefix << "No IP found";
26b0f687 754 return HostAddress(); // have checked all candidates
72be9e7d 755 }
26b0f687
CH
756 else
757 { // there are candidates left to consider
fd62d09f
CH
758 GlobalLogger.debug() << LogPrefix << "Check IP candidate at index "
759 << NextIpIndex;
26b0f687
CH
760 return_candidate = cached_data[NextIpIndex++];
761 if (!check_up_to_date)
fd62d09f
CH
762 {
763 GlobalLogger.debug() << LogPrefix << "not checking ttl, accept";
26b0f687 764 return return_candidate;
fd62d09f
CH
765 }
766 else if (return_candidate.get_ttl().get_updated_value()
26b0f687 767 > ttl_thresh)
fd62d09f
CH
768 {
769 GlobalLogger.debug() << LogPrefix << "is up to date, accept";
72be9e7d 770 return return_candidate;
fd62d09f 771 }
26b0f687 772 else
fd62d09f
CH
773 {
774 GlobalLogger.debug() << LogPrefix << "is out of date ("
775 << return_candidate.get_ttl().get_updated_value()
776 << "s <= " << ttl_thresh << "s), continue";
26b0f687 777 ++n_iter;
fd62d09f 778 }
26b0f687
CH
779 }
780 }
36ad976b
CH
781}
782
923626c0
CH
783bool DnsResolver::have_up_to_date_ip()
784{
fd62d09f 785 return get_resolved_ip_count(true) > 0;
923626c0
CH
786}
787
fd62d09f 788int DnsResolver::get_resolved_ip_count(const bool check_up_to_date)
923626c0 789{
fd62d09f
CH
790 // run with empty hostname --> uses internal var Hostname
791 return ResolverBase::get_cached_ips_recursively("",check_up_to_date).size();
923626c0
CH
792}
793