2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 Christian Herdtweck, Intra2net AG 2015
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
27 #include "dns/dnsresolver.h"
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>
37 #include <boost/uuid/uuid.hpp>
38 #include <boost/uuid/uuid_io.hpp>
40 #include <logfunc.hpp>
42 using I2n::Logger::GlobalLogger;
43 using boost::posix_time::seconds;
44 using boost::posix_time::minutes;
48 const int ResolveTimeoutSeconds = 3;
49 const int PauseBeforeRetrySeconds = 10;
50 const int StaleDataLongtermMinutes = 15;
51 const int DNS_PORT = 53;
54 DnsResolver::DnsResolver(IoServiceItem &io_serv,
55 const std::string &hostname,
56 const DnsIpProtocol &protocol,
57 const DnsCacheItem cache,
58 const boost::asio::ip::address &name_server)
59 : ResolverBase( io_serv, hostname, cache )
60 , Socket( *io_serv, ip::udp::endpoint(ip::udp::v4(), 0))
61 // just connect to anything, will specify sender/receiver later
64 , Protocol( protocol )
65 , NameServer( name_server, Config::DNS_PORT )
66 , ResolveTimeoutTimer( *io_serv )
67 , PauseBeforeRetryTimer( *io_serv )
68 , StaleDataLongtermTimer( *io_serv )
71 , IsResolving( false )
72 , LogPrefix( "DnsResolver" )
75 , OperationCancelled( false )
77 std::stringstream temp;
78 temp << "Dns(" << ResolverBase::Hostname << "): ";
79 LogPrefix = temp.str();
83 DnsResolver::~DnsResolver()
85 boost::system::error_code error;
86 //Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
88 // GlobalLogger.warning() << LogPrefix << "Received error " << error
89 // << " when shutting down socket for DNS";
90 // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
91 // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
95 GlobalLogger.warning() << LogPrefix << "Received error " << error
96 << " when closing socket for DNS";
101 //==============================================================================
103 //==============================================================================
106 * copied here code from boost::net::dns::resolve.hpp, since want async
107 * operation and that is used only internally, there
109 void DnsResolver::do_resolve()
111 // check if resolving already
114 GlobalLogger.info() << LogPrefix
115 << "Call to do_resolve ignored since resolving already";
119 OperationCancelled = false;
121 GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
122 << to_string(Protocol) << " using name server " << NameServer;
124 // just to be sure: cancel timers
125 ResolveTimeoutTimer.cancel();
126 PauseBeforeRetryTimer.cancel();
127 StaleDataLongtermTimer.cancel();
129 // create DNS request
130 boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
131 dns_message.recursive(true);
132 dns_message.action(boost::net::dns::message::query);
133 dns_message.opcode(boost::net::dns::message::squery);
135 // create random ID for message
136 boost::uuids::uuid message_id = RandomIdGenerator();
137 memcpy( &RequestId, message_id.data, sizeof(RequestId) );
138 dns_message.id( RequestId );
139 GlobalLogger.debug() << LogPrefix << "Request has ID "
140 << std::showbase << std::hex << dns_message.id();
142 // setup receipt of reply
143 Socket.async_receive_from(
144 boost::asio::buffer(ReceiveBuffer.get_array()),
146 boost::bind( &DnsResolver::handle_dns_result, this,
147 boost::asio::placeholders::error,
148 boost::asio::placeholders::bytes_transferred)
152 (void) ResolveTimeoutTimer.expires_from_now(
153 seconds(Config::ResolveTimeoutSeconds));
154 ResolveTimeoutTimer.async_wait( boost::bind(
155 &DnsResolver::handle_resolve_timeout,
156 this, boost::asio::placeholders::error) );
159 dns_message.encode(RequestBuffer);
163 bytes_sent = Socket.send_to(
164 boost::asio::buffer(RequestBuffer.get_array()),
167 catch (boost::system::system_error &err)
169 GlobalLogger.warning() << LogPrefix
170 << "Sending of DNS request message failed: "
176 if ( bytes_sent == 0 )
178 GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
185 void DnsResolver::handle_dns_result(const boost::system::error_code &error,
186 const std::size_t bytes_transferred)
190 GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
191 << error << " --> try again after a little while";
195 else if ( OperationCancelled )
196 { // async_resolve was cancelled --> callbacks already called
197 GlobalLogger.info() << LogPrefix
198 << "Ignoring DNS results since we were cancelled";
202 GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
203 << bytes_transferred << " bytes transferred)";
205 // next 3(+1) lines copied from boost/net/dns/resolver.hpp:
206 // clamp the recvBuffer with the number of bytes transferred or decode buffr
207 ReceiveBuffer.length(bytes_transferred);
208 boost::net::dns::message result_message;
209 result_message.decode( ReceiveBuffer );
212 if (RequestId != result_message.id())
213 GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
214 << std::showbase << std::hex << result_message.id()
215 << " but expected ID " << RequestId;
217 GlobalLogger.debug() << LogPrefix << "Result has correct ID "
218 << std::showbase << std::hex << RequestId;
221 // loop over answers, remembering ips and cnames
222 // work with a regular pointer to list of answers since result_message is
223 // owner of data and that exists until end of function
224 // Items in answers list are shared_ptr to resource_base_t
225 std::vector<host_addr_pair> result_ips;
226 std::vector<src_cname_pair> result_cnames;
227 std::vector<string_pair> result_name_servers;
229 GlobalLogger.debug() << "Checking ANSWERS section of dns reply";
230 gather_results(result_message.answers(), &result_ips, &result_cnames,
231 &result_name_servers);
232 // results should have the logical order
233 // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
235 // remember cname list (if there were any)
236 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
238 GlobalLogger.info() << LogPrefix << "Remember CNAME "
239 << host_and_cname.first << " --> " << host_and_cname.second.Host;
240 ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
243 if ( !result_ips.empty() )
244 handle_ips( result_ips );
245 else if ( !result_cnames.empty() )
246 // no IPs but at least one cname --> find the "last" cname and
247 // re-start resolving with that
248 handle_cname(result_cnames);
250 { // no answers --> cannot proceed
251 GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
252 << "--> schedule retry";
258 * gather IPs, CNAMEs and name servers from list of resource records;
260 * can be run on anwers(), autorities() and additional() sections of dns reply
263 * @param rr_list: input list of resource records
264 * @param result_ips: output vector of ips
265 * @param result_cnames: output vector of cnames
266 * @param result_name_servers: output vector of name servers
268 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
269 std::vector<host_addr_pair> *result_ips,
270 std::vector<src_cname_pair> *result_cnames,
271 std::vector<string_pair> *result_name_servers)
274 using boost::net::dns::resource_base_t;
275 boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
276 BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
278 boost::net::dns::type_t rr_type = rr_item->rtype();
279 uint32_t ttl = rr_item->ttl();
280 std::string domain = rr_item->domain();
282 boost::posix_time::to_simple_string(now + seconds(ttl));
284 if (rr_type == boost::net::dns::type_a)
285 { // 'A' resource records carry IPv4 addresses
286 if (Protocol == DNS_IPv6)
288 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
289 << "because resolver was configured to only use IPv6.";
292 boost::asio::ip::address_v4 ip =
293 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
295 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
296 GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
297 << " with TTL " << ttl << "s (until "
300 else if (rr_type == boost::net::dns::type_a6)
301 { // 'AAAA' resource records carry IPv6 addresses
302 if (Protocol == DNS_IPv4)
304 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
305 << "because resolver was configured to only use IPv4.";
308 boost::asio::ip::address_v6 ip =
309 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
311 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
312 GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
313 << " with TTL " << ttl << "s (until "
316 else if (rr_type == boost::net::dns::type_cname)
317 { // 'CNAME' resource records that carry aliases
319 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
321 result_cnames->push_back( src_cname_pair(domain,
322 Cname(cname, ttl)) );
323 GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
324 << cname << " with TTL " << ttl << "s (until "
327 else if (rr_type == boost::net::dns::type_ns)
328 { // NS (name_server) resource records
329 std::string name_server =
330 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
332 result_name_servers->push_back( string_pair(domain, name_server) );
333 GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
334 << " for " << domain << " with TTL " << ttl
335 << "s (until " << expiry << ")";
337 else if (rr_type == boost::net::dns::type_soa)
338 GlobalLogger.debug() << LogPrefix << "SOA resource";
339 else if (rr_type == boost::net::dns::type_ptr)
340 GlobalLogger.debug() << LogPrefix << "ptr resource";
341 else if (rr_type == boost::net::dns::type_hinfo)
342 GlobalLogger.debug() << LogPrefix << "hinfo resource";
343 else if (rr_type == boost::net::dns::type_mx)
344 GlobalLogger.debug() << LogPrefix << "mx resource";
345 else if (rr_type == boost::net::dns::type_txt)
346 GlobalLogger.debug() << LogPrefix << "txt resource";
347 else if (rr_type == boost::net::dns::type_srv)
348 GlobalLogger.debug() << LogPrefix << "srv resource";
349 else if (rr_type == boost::net::dns::type_axfr)
350 GlobalLogger.debug() << LogPrefix << "axfr resource";
352 GlobalLogger.debug() << LogPrefix << "unknown resource type: "
353 << std::showbase << std::hex
354 << static_cast<unsigned>(rr_item->rtype());
359 void DnsResolver::handle_unavailable()
361 // schedule new attempt in quite a while
362 StaleDataLongtermTimer.expires_from_now(
363 minutes(Config::StaleDataLongtermMinutes));
364 StaleDataLongtermTimer.async_wait(
365 boost::bind( &DnsResolver::wait_timer_timeout_handler,
366 this, boost::asio::placeholders::error
370 // for now, admit failure
371 bool was_success = false;
372 finalize_resolve(was_success);
376 void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
378 // received at least one IP which could be for the queried host name
379 // or the cname at the "end" of the cname list;
380 // but all IPs should be for the same
381 HostAddressVec addr_list;
382 std::string only_host_for_ips = result_ips[0].first;
383 BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
385 if ( host_and_addr.first != only_host_for_ips )
386 GlobalLogger.warning() << LogPrefix
387 << "Received IPs for different hosts " << only_host_for_ips
388 << " and " << host_and_addr.first << " in one DNS result! "
389 << "--> ignore second";
392 GlobalLogger.notice() << LogPrefix << "Found IP "
393 << host_and_addr.second.get_ip() << " with TTL "
394 << host_and_addr.second.get_ttl().get_value() << "s";
395 addr_list.push_back(host_and_addr.second);
398 ResolverBase::update_cache( only_host_for_ips, addr_list );
401 bool was_success = true;
402 finalize_resolve(was_success);
406 void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
408 // find the "last" cname in the list
409 // Hostname --> cname1 --> cname2 --> ... --> cnameN
410 // We assume here that this list might not be in order but that all cnames
411 // form a single list (form one connected list and not several isolated)
413 std::string last_cname = "";
415 BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
417 could_be_last = true;
418 BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
420 if (other.first == host_and_cname.second.Host)
421 { // found cname for current cname
422 could_be_last = false;
428 last_cname = host_and_cname.second.Host;
433 if (last_cname.empty())
435 GlobalLogger.error() << LogPrefix
436 << "Could not identify \"last\" CNAME to handle -- "
437 << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
438 GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
439 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
440 GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
441 << host_and_cname.second.Host;
442 handle_unavailable();
445 { // check cache for IP for this cname
446 bool check_up_to_date = true;
447 HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
449 if ( !cached_data.empty() )
451 bool was_success = true;
452 int cname_count = 1; // define cache access as only 1
453 finalize_resolve(was_success, cname_count);
456 { // get resolver for canonical name
457 ResolverItem resolver = DnsMaster::get_instance()
458 ->get_resolver_for(last_cname, Protocol);
459 callback_type callback = boost::bind(
460 &DnsResolver::cname_resolve_callback,
462 resolver->async_resolve( callback );
464 // treat a CNAME as a partial result: not enough to run callbacks
465 // from finalize_resolve, but enough to stop timers and reset
466 // RetryCount --> name resolution can take longer
473 void DnsResolver::cname_resolve_callback(const bool was_success,
474 const int cname_count)
476 if ( OperationCancelled )
477 { // async_resolve was cancelled --> callbacks already called
478 GlobalLogger.info() << LogPrefix
479 << "Ignoring CNAME results since we were cancelled";
482 else if (was_success)
483 GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
485 GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
486 // no use to schedule retry in this case since cname resolver must have
487 // failed several times and we can only re-start the same procedure with
488 // the same information
490 // cname counts like one more recursion step ...
491 finalize_resolve(was_success, cname_count+1);
495 void DnsResolver::finalize_resolve(const bool was_success,
496 const int cname_count)
498 // some consistency checks; failure might indicate a situation I had not
499 // anticipated during programming but might not be harmfull yet
501 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
502 << "not resolving any more!";
503 if ( OperationCancelled )
504 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
505 << " was cancelled!";
506 if ( ResolverBase::CallbackList.empty() )
507 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
509 if ( RequestId != 0 )
510 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
511 << "waiting for DNS reply!";
516 // schedule callbacks, clearing callback list
517 ResolverBase::schedule_callbacks(was_success, cname_count);
520 GlobalLogger.notice() << LogPrefix << "finalized resolve"
521 << " with success = " << was_success
522 << " and cname_count = " << cname_count;
527 void DnsResolver::stop_trying()
530 GlobalLogger.debug() << LogPrefix << "Cancelling timers";
531 ResolveTimeoutTimer.cancel();
532 PauseBeforeRetryTimer.cancel();
533 StaleDataLongtermTimer.cancel();
540 bool DnsResolver::is_resolving()
547 * cancel a earlier call to async_resolve
549 * callbacks will be called with was_success=false; all internal operations
550 * will be cancelled and internal callbacks (timers, dns results) have no
553 void DnsResolver::cancel_resolve()
557 GlobalLogger.info() << LogPrefix
558 << "Cancel called on non-resolving resolver -- ignore";
561 else if (OperationCancelled)
563 GlobalLogger.info() << LogPrefix
564 << "Cancel called on cancelled resolver -- ignore";
568 // set before finalize_resolve so can check in finalize_resolve that ID is
569 // always 0; ID is not used any more since handle_dns_result stops if
570 // OperationCancelled is true
573 bool was_success = false;
575 finalize_resolve(was_success, cname_count);
577 // set after finalize_resolve, so can check in finalize_resolve that
578 // OperationCancelled is never true
579 OperationCancelled = true;
584 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
586 if ( error == boost::asio::error::operation_aborted ) // cancelled
588 GlobalLogger.warning() << LogPrefix
589 << "Resolve timeout timer was cancelled!";
594 GlobalLogger.warning() << LogPrefix
595 << "resolve timeout handler received error "
596 << error << " --> retry";
599 else if ( OperationCancelled )
600 { // async_resolve was cancelled --> callbacks already called
601 GlobalLogger.info() << LogPrefix
602 << "Ignoring DNS timeout since we were cancelled";
607 GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
613 void DnsResolver::schedule_retry()
616 ResolveTimeoutTimer.cancel();
617 PauseBeforeRetryTimer.cancel();
622 if ( RetryCount > DnsMaster::get_instance()
623 ->get_max_address_resolution_attempts() )
624 { // too many re-tries
625 GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
626 << "RetryCount " << RetryCount << " too high";
627 handle_unavailable(); // will call stop_trying i.e. reset RetryCount
631 GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
632 << RetryCount << ")";
633 PauseBeforeRetryTimer.expires_from_now(
634 seconds(Config::PauseBeforeRetrySeconds));
635 PauseBeforeRetryTimer.async_wait(
636 boost::bind( &DnsResolver::wait_timer_timeout_handler,
637 this, boost::asio::placeholders::error) );
641 void DnsResolver::wait_timer_timeout_handler(
642 const boost::system::error_code &error)
644 if ( error == boost::asio::error::operation_aborted ) // cancelled
645 { // assume that our code cancelled this timer, so callbacks will be
647 GlobalLogger.warning() << LogPrefix
648 << "Resolve wait timer was cancelled! ";
651 { // not sure what to do here, but callers waiting forever for a callback
652 // is probably the worst thing to happen, so call finalize_resolve
653 GlobalLogger.warning() << LogPrefix
654 << "resolve wait handler received error "
655 << error << "! Try to finalize resolve";
656 bool was_success = false;
657 finalize_resolve(was_success);
659 else if ( OperationCancelled )
660 { // async_resolve was cancelled --> callbacks already called
661 GlobalLogger.info() << LogPrefix
662 << "Ignoring waiting timeout since we were cancelled";
667 GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
673 //==============================================================================
675 //==============================================================================
677 HostAddress DnsResolver::get_next_ip()
680 HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
682 // if no results cached, return default-constructed HostAddress (0.0.0.0)
683 if ( cached_data.empty() )
685 HostAddress return_value;
689 // check validity of index (cache may have changed since last call)
690 if (NextIpIndex >= cached_data.size())
694 return cached_data[NextIpIndex++];
697 bool DnsResolver::have_up_to_date_ip()
699 return ! ResolverBase::get_cached_ips_recursively("", true).empty();
702 int DnsResolver::get_resolved_ip_count()
704 return ResolverBase::get_cached_ips_recursively().size();
707 // (created using vim -- the world's best text editor)