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