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