Commit | Line | Data |
---|---|---|
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 | 42 | using I2n::Logger::GlobalLogger; |
4e7b6ff9 CH |
43 | using boost::posix_time::seconds; |
44 | using boost::posix_time::minutes; | |
36ad976b CH |
45 | |
46 | namespace 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 |
54 | DnsResolver::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 |
84 | DnsResolver::~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 | 110 | void 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 | 186 | void 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 | ||
309 | void 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 |
392 | void 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 | |
409 | void 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 | ||
439 | void 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 | 508 | void 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 |
531 | void 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 |
561 | void 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 | ||
600 | void 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 |
632 | void 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 | |
645 | bool DnsResolver::is_resolving() | |
646 | { | |
647 | return IsResolving; | |
648 | } | |
649 | ||
650 | ||
651 | void 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 | 685 | void 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 |
715 | void 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 | ||
748 | void 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 | 784 | HostAddress 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 |
804 | bool 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 | ||
825 | int 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 |