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