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