recursive_query.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <config.h>
  15. #include <unistd.h> // for some IPC/network system calls
  16. #include <sys/socket.h>
  17. #include <netinet/in.h>
  18. #include <stdlib.h>
  19. #include <asio/ip/address.hpp>
  20. #include <asio.hpp>
  21. #include <asiolink/dns_service.h>
  22. #include <asiolink/io_fetch.h>
  23. #include <asiolink/io_service.h>
  24. #include <asiolink/recursive_query.h>
  25. #include <log/dummylog.h>
  26. #include <boost/lexical_cast.hpp>
  27. #include <boost/bind.hpp>
  28. #include <dns/question.h>
  29. #include <dns/message.h>
  30. #include <resolve/resolve.h>
  31. using isc::log::dlog;
  32. using namespace isc::dns;
  33. namespace asiolink {
  34. typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
  35. // Here we do not use the typedef above, as the SunStudio compiler
  36. // mishandles this in its name mangling, and wouldn't compile.
  37. // We can probably use a typedef, but need to move it to a central
  38. // location and use it consistently.
  39. RecursiveQuery::RecursiveQuery(DNSService& dns_service,
  40. const std::vector<std::pair<std::string, uint16_t> >& upstream,
  41. const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
  42. int query_timeout, int client_timeout, int lookup_timeout,
  43. unsigned retries) :
  44. dns_service_(dns_service), upstream_(new AddressVector(upstream)),
  45. upstream_root_(new AddressVector(upstream_root)),
  46. query_timeout_(query_timeout), client_timeout_(client_timeout),
  47. lookup_timeout_(lookup_timeout), retries_(retries)
  48. {}
  49. namespace {
  50. typedef std::pair<std::string, uint16_t> addr_t;
  51. /*
  52. * This is a query in progress. When a new query is made, this one holds
  53. * the context information about it, like how many times we are allowed
  54. * to retry on failure, what to do when we succeed, etc.
  55. *
  56. * Used by RecursiveQuery::sendQuery.
  57. */
  58. class RunningQuery : public IOFetch::Callback {
  59. private:
  60. // The io service to handle async calls
  61. IOService& io_;
  62. // Info for (re)sending the query (the question and destination)
  63. Question question_;
  64. // This is where we build and store our final answer
  65. MessagePtr answer_message_;
  66. // currently we use upstream as the current list of NS records
  67. // we should differentiate between forwarding and resolving
  68. boost::shared_ptr<AddressVector> upstream_;
  69. // root servers...just copied over to the zone_servers_
  70. boost::shared_ptr<AddressVector> upstream_root_;
  71. // Buffer to store the result.
  72. OutputBufferPtr buffer_;
  73. // Server to notify when we succeed or fail
  74. //shared_ptr<DNSServer> server_;
  75. isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
  76. // To prevent both unreasonably long cname chains and cname loops,
  77. // we simply keep a counter of the number of CNAMEs we have
  78. // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
  79. // from lib/resolve/response_classifier.h)
  80. unsigned cname_count_;
  81. /*
  82. * TODO Do something more clever with timeouts. In the long term, some
  83. * computation of average RTT, increase with each retry, etc.
  84. */
  85. // Timeout information
  86. int query_timeout_;
  87. unsigned retries_;
  88. // normal query state
  89. // Not using NSAS at this moment, so we keep a list
  90. // of 'current' zone servers
  91. std::vector<addr_t> zone_servers_;
  92. // Update the question that will be sent to the server
  93. void setQuestion(const Question& new_question) {
  94. question_ = new_question;
  95. }
  96. // TODO: replace by our wrapper
  97. asio::deadline_timer client_timer;
  98. asio::deadline_timer lookup_timer;
  99. size_t queries_out_;
  100. // If we timed out ourselves (lookup timeout), stop issuing queries
  101. bool done_;
  102. // If we have a client timeout, we send back an answer, but don't
  103. // stop. We use this variable to make sure we don't send another
  104. // answer if we do find one later (or if we have a lookup_timeout)
  105. bool answer_sent_;
  106. // (re)send the query to the server.
  107. void send() {
  108. const int uc = upstream_->size();
  109. const int zs = zone_servers_.size();
  110. buffer_->clear();
  111. if (uc > 0) {
  112. int serverIndex = rand() % uc;
  113. dlog("Sending upstream query (" + question_.toText() +
  114. ") to " + upstream_->at(serverIndex).first);
  115. IOFetch query(IPPROTO_UDP, io_, question_,
  116. upstream_->at(serverIndex).first,
  117. upstream_->at(serverIndex).second, buffer_, this,
  118. query_timeout_);
  119. ++queries_out_;
  120. io_.get_io_service().post(query);
  121. } else if (zs > 0) {
  122. int serverIndex = rand() % zs;
  123. dlog("Sending query to zone server (" + question_.toText() +
  124. ") to " + zone_servers_.at(serverIndex).first);
  125. IOFetch query(IPPROTO_IDP, io_, question_,
  126. zone_servers_.at(serverIndex).first,
  127. zone_servers_.at(serverIndex).second, buffer_, this,
  128. query_timeout_);
  129. ++queries_out_;
  130. io_.get_io_service().post(query);
  131. } else {
  132. dlog("Error, no upstream servers to send to.");
  133. }
  134. }
  135. // This function is called by operator() if there is an actual
  136. // answer from a server and we are in recursive mode
  137. // depending on the contents, we go on recursing or return
  138. //
  139. // Note that the footprint may change as this function may
  140. // need to append data to the answer we are building later.
  141. //
  142. // returns true if we are done (either we have an answer or an
  143. // error message)
  144. // returns false if we are not done
  145. bool handleRecursiveAnswer(const Message& incoming) {
  146. dlog("Handle response");
  147. // In case we get a CNAME, we store the target
  148. // here (classify() will set it when it walks through
  149. // the cname chain to verify it).
  150. Name cname_target(question_.getName());
  151. isc::resolve::ResponseClassifier::Category category =
  152. isc::resolve::ResponseClassifier::classify(
  153. question_, incoming, cname_target, cname_count_, true);
  154. bool found_ns_address = false;
  155. switch (category) {
  156. case isc::resolve::ResponseClassifier::ANSWER:
  157. case isc::resolve::ResponseClassifier::ANSWERCNAME:
  158. // Done. copy and return.
  159. isc::resolve::copyResponseMessage(incoming, answer_message_);
  160. return true;
  161. break;
  162. case isc::resolve::ResponseClassifier::CNAME:
  163. dlog("Response is CNAME!");
  164. // (unfinished) CNAME. We set our question_ to the CNAME
  165. // target, then start over at the beginning (for now, that
  166. // is, we reset our 'current servers' to the root servers).
  167. if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
  168. // just give up
  169. dlog("CNAME chain too long");
  170. isc::resolve::makeErrorMessage(answer_message_,
  171. Rcode::SERVFAIL());
  172. return true;
  173. }
  174. answer_message_->appendSection(Message::SECTION_ANSWER,
  175. incoming);
  176. setZoneServersToRoot();
  177. question_ = Question(cname_target, question_.getClass(),
  178. question_.getType());
  179. dlog("Following CNAME chain to " + question_.toText());
  180. send();
  181. return false;
  182. break;
  183. case isc::resolve::ResponseClassifier::NXDOMAIN:
  184. // NXDOMAIN, just copy and return.
  185. isc::resolve::copyResponseMessage(incoming, answer_message_);
  186. return true;
  187. break;
  188. case isc::resolve::ResponseClassifier::REFERRAL:
  189. // Referral. For now we just take the first glue address
  190. // we find and continue with that
  191. zone_servers_.clear();
  192. for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
  193. rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
  194. rrsi++) {
  195. ConstRRsetPtr rrs = *rrsi;
  196. if (rrs->getType() == RRType::A()) {
  197. // found address
  198. RdataIteratorPtr rdi = rrs->getRdataIterator();
  199. // just use the first for now
  200. if (!rdi->isLast()) {
  201. std::string addr_str = rdi->getCurrent().toText();
  202. dlog("[XX] first address found: " + addr_str);
  203. // now we have one address, simply
  204. // resend that exact same query
  205. // to that address and yield, when it
  206. // returns, loop again.
  207. // TODO should use NSAS
  208. zone_servers_.push_back(addr_t(addr_str, 53));
  209. found_ns_address = true;
  210. }
  211. }
  212. }
  213. if (found_ns_address) {
  214. // next resolver round
  215. send();
  216. return false;
  217. } else {
  218. dlog("[XX] no ready-made addresses in additional. need nsas.");
  219. // TODO this will result in answering with the delegation. oh well
  220. isc::resolve::copyResponseMessage(incoming, answer_message_);
  221. return true;
  222. }
  223. break;
  224. case isc::resolve::ResponseClassifier::EMPTY:
  225. case isc::resolve::ResponseClassifier::EXTRADATA:
  226. case isc::resolve::ResponseClassifier::INVNAMCLASS:
  227. case isc::resolve::ResponseClassifier::INVTYPE:
  228. case isc::resolve::ResponseClassifier::MISMATQUEST:
  229. case isc::resolve::ResponseClassifier::MULTICLASS:
  230. case isc::resolve::ResponseClassifier::NOTONEQUEST:
  231. case isc::resolve::ResponseClassifier::NOTRESPONSE:
  232. case isc::resolve::ResponseClassifier::NOTSINGLE:
  233. case isc::resolve::ResponseClassifier::OPCODE:
  234. case isc::resolve::ResponseClassifier::RCODE:
  235. case isc::resolve::ResponseClassifier::TRUNCATED:
  236. // Should we try a different server rather than SERVFAIL?
  237. isc::resolve::makeErrorMessage(answer_message_,
  238. Rcode::SERVFAIL());
  239. return true;
  240. break;
  241. }
  242. // should not be reached. assert here?
  243. dlog("[FATAL] unreachable code");
  244. return true;
  245. }
  246. public:
  247. RunningQuery(IOService& io,
  248. const Question &question,
  249. MessagePtr answer_message,
  250. boost::shared_ptr<AddressVector> upstream,
  251. boost::shared_ptr<AddressVector> upstream_root,
  252. OutputBufferPtr buffer,
  253. isc::resolve::ResolverInterface::CallbackPtr cb,
  254. int query_timeout, int client_timeout, int lookup_timeout,
  255. unsigned retries) :
  256. io_(io),
  257. question_(question),
  258. answer_message_(answer_message),
  259. upstream_(upstream),
  260. upstream_root_(upstream_root),
  261. buffer_(buffer),
  262. resolvercallback_(cb),
  263. cname_count_(0),
  264. query_timeout_(query_timeout),
  265. retries_(retries),
  266. client_timer(io.get_io_service()),
  267. lookup_timer(io.get_io_service()),
  268. queries_out_(0),
  269. done_(false),
  270. answer_sent_(false)
  271. {
  272. // Setup the timer to stop trying (lookup_timeout)
  273. if (lookup_timeout >= 0) {
  274. lookup_timer.expires_from_now(
  275. boost::posix_time::milliseconds(lookup_timeout));
  276. lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
  277. }
  278. // Setup the timer to send an answer (client_timeout)
  279. if (client_timeout >= 0) {
  280. client_timer.expires_from_now(
  281. boost::posix_time::milliseconds(client_timeout));
  282. client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
  283. }
  284. // should use NSAS for root servers
  285. // Adding root servers if not a forwarder
  286. if (upstream_->empty()) {
  287. setZoneServersToRoot();
  288. }
  289. send();
  290. }
  291. void setZoneServersToRoot() {
  292. zone_servers_.clear();
  293. if (upstream_root_->empty()) { //if no root ips given, use this
  294. zone_servers_.push_back(addr_t("192.5.5.241", 53));
  295. } else {
  296. // copy the list
  297. dlog("Size is " +
  298. boost::lexical_cast<std::string>(upstream_root_->size()) +
  299. "\n");
  300. for(AddressVector::iterator it = upstream_root_->begin();
  301. it < upstream_root_->end(); ++it) {
  302. zone_servers_.push_back(addr_t(it->first,it->second));
  303. dlog("Put " + zone_servers_.back().first + "into root list\n");
  304. }
  305. }
  306. }
  307. virtual void clientTimeout() {
  308. // Return a SERVFAIL, but do not stop until
  309. // we have an answer or timeout ourselves
  310. isc::resolve::makeErrorMessage(answer_message_,
  311. Rcode::SERVFAIL());
  312. resolvercallback_->success(answer_message_);
  313. answer_sent_ = true;
  314. }
  315. virtual void stop(bool resume) {
  316. // if we cancel our timers, we will still get an event for
  317. // that, so we cannot delete ourselves just yet (those events
  318. // would be bound to a deleted object)
  319. // cancel them one by one, both cancels should get us back
  320. // here again.
  321. // same goes if we have an outstanding query (can't delete
  322. // until that one comes back to us)
  323. done_ = true;
  324. if (resume && !answer_sent_) {
  325. resolvercallback_->success(answer_message_);
  326. } else {
  327. resolvercallback_->failure();
  328. }
  329. if (lookup_timer.cancel() != 0) {
  330. return;
  331. }
  332. if (client_timer.cancel() != 0) {
  333. return;
  334. }
  335. if (queries_out_ > 0) {
  336. return;
  337. }
  338. delete this;
  339. }
  340. // This function is used as callback from DNSQuery.
  341. virtual void operator()(IOFetch::Result result) {
  342. // XXX is this the place for TCP retry?
  343. --queries_out_;
  344. if (!done_ && result != IOFetch::TIME_OUT) {
  345. // we got an answer
  346. Message incoming(Message::PARSE);
  347. InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
  348. incoming.fromWire(ibuf);
  349. if (upstream_->size() == 0 &&
  350. incoming.getRcode() == Rcode::NOERROR()) {
  351. done_ = handleRecursiveAnswer(incoming);
  352. } else {
  353. isc::resolve::copyResponseMessage(incoming, answer_message_);
  354. done_ = true;
  355. }
  356. if (done_) {
  357. stop(true);
  358. }
  359. } else if (!done_ && retries_--) {
  360. // We timed out, but we have some retries, so send again
  361. dlog("Timeout, resending query");
  362. send();
  363. } else {
  364. // out of retries, give up for now
  365. stop(false);
  366. }
  367. }
  368. };
  369. }
  370. void
  371. RecursiveQuery::resolve(const QuestionPtr& question,
  372. const isc::resolve::ResolverInterface::CallbackPtr callback)
  373. {
  374. IOService& io = dns_service_.getIOService();
  375. MessagePtr answer_message(new Message(Message::RENDER));
  376. OutputBufferPtr buffer(new OutputBuffer(0));
  377. // It will delete itself when it is done
  378. new RunningQuery(io, *question, answer_message, upstream_,
  379. upstream_root_, buffer, callback, query_timeout_,
  380. client_timeout_, lookup_timeout_, retries_);
  381. }
  382. void
  383. RecursiveQuery::resolve(const Question& question,
  384. MessagePtr answer_message,
  385. OutputBufferPtr buffer,
  386. DNSServer* server)
  387. {
  388. // XXX: eventually we will need to be able to determine whether
  389. // the message should be sent via TCP or UDP, or sent initially via
  390. // UDP and then fall back to TCP on failure, but for the moment
  391. // we're only going to handle UDP.
  392. IOService& io = dns_service_.getIOService();
  393. isc::resolve::ResolverInterface::CallbackPtr crs(
  394. new isc::resolve::ResolverCallbackServer(server));
  395. // It will delete itself when it is done
  396. new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
  397. buffer, crs, query_timeout_, client_timeout_,
  398. lookup_timeout_, retries_);
  399. }
  400. } // namespace asiolink