query_unittest.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. // Copyright (C) 2010 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 <sstream>
  15. #include <vector>
  16. #include <map>
  17. #include <boost/bind.hpp>
  18. #include <dns/masterload.h>
  19. #include <dns/message.h>
  20. #include <dns/name.h>
  21. #include <dns/opcode.h>
  22. #include <dns/rcode.h>
  23. #include <dns/rrttl.h>
  24. #include <dns/rrtype.h>
  25. #include <dns/rdataclass.h>
  26. #include <datasrc/memory_datasrc.h>
  27. #include <auth/query.h>
  28. #include <testutils/dnsmessage_test.h>
  29. #include <gtest/gtest.h>
  30. using namespace std;
  31. using namespace isc::dns;
  32. using namespace isc::dns::rdata;
  33. using namespace isc::datasrc;
  34. using namespace isc::auth;
  35. using namespace isc::testutils;
  36. namespace {
  37. // This is the content of the mock zone (see below).
  38. // It's a sequence of textual RRs that is supposed to be parsed by
  39. // dns::masterLoad(). Some of the RRs are also used as the expected
  40. // data in specific tests, in which case they are referenced via specific
  41. // local variables (such as soa_txt).
  42. const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
  43. const char* const zone_ns_txt =
  44. "example.com. 3600 IN NS glue.delegation.example.com.\n"
  45. "example.com. 3600 IN NS noglue.example.com.\n"
  46. "example.com. 3600 IN NS example.net.\n";
  47. const char* const ns_addrs_txt =
  48. "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
  49. "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
  50. "noglue.example.com. 3600 IN A 192.0.2.53\n";
  51. const char* const delegation_txt =
  52. "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
  53. "delegation.example.com. 3600 IN NS noglue.example.com.\n"
  54. "delegation.example.com. 3600 IN NS cname.example.com.\n"
  55. "delegation.example.com. 3600 IN NS example.org.\n";
  56. const char* const mx_txt =
  57. "mx.example.com. 3600 IN MX 10 www.example.com.\n"
  58. "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
  59. "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
  60. const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
  61. // The rest of data won't be referenced from the test cases.
  62. const char* const other_zone_rrs =
  63. "cname.example.com. 3600 IN CNAME www.example.com.\n"
  64. "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
  65. "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
  66. "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
  67. // This is a mock Zone class for testing.
  68. // It is a derived class of Zone for the convenient of tests.
  69. // Its find() method emulates the common behavior of protocol compliant
  70. // zone classes, but simplifies some minor cases and also supports broken
  71. // behavior.
  72. // For simplicity, most names are assumed to be "in zone"; there's only
  73. // one zone cut at the point of name "delegation.example.com".
  74. // It doesn't handle empty non terminal nodes (if we need to test such cases
  75. // find() should have specialized code for it).
  76. class MockZone : public Zone {
  77. public:
  78. MockZone() :
  79. origin_(Name("example.com")),
  80. delegation_name_("delegation.example.com"),
  81. has_SOA_(true),
  82. has_apex_NS_(true),
  83. rrclass_(RRClass::IN())
  84. {
  85. stringstream zone_stream;
  86. zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
  87. delegation_txt << mx_txt << www_a_txt << other_zone_rrs;
  88. masterLoad(zone_stream, origin_, rrclass_,
  89. boost::bind(&MockZone::loadRRset, this, _1));
  90. }
  91. virtual const isc::dns::Name& getOrigin() const { return (origin_); }
  92. virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
  93. virtual FindResult find(const isc::dns::Name& name,
  94. const isc::dns::RRType& type,
  95. RRsetList* target = NULL,
  96. const FindOptions options = FIND_DEFAULT) const;
  97. // If false is passed, it makes the zone broken as if it didn't have the
  98. // SOA.
  99. void setSOAFlag(bool on) { has_SOA_ = on; }
  100. // If false is passed, it makes the zone broken as if it didn't have
  101. // the apex NS.
  102. void setApexNSFlag(bool on) { has_apex_NS_ = on; }
  103. private:
  104. typedef map<RRType, ConstRRsetPtr> RRsetStore;
  105. typedef map<Name, RRsetStore> Domains;
  106. Domains domains_;
  107. void loadRRset(ConstRRsetPtr rrset) {
  108. domains_[rrset->getName()][rrset->getType()] = rrset;
  109. if (rrset->getName() == delegation_name_ &&
  110. rrset->getType() == RRType::NS()) {
  111. delegation_rrset_ = rrset;
  112. }
  113. }
  114. const Name origin_;
  115. const Name delegation_name_;
  116. bool has_SOA_;
  117. bool has_apex_NS_;
  118. ConstRRsetPtr delegation_rrset_;
  119. const RRClass rrclass_;
  120. };
  121. Zone::FindResult
  122. MockZone::find(const Name& name, const RRType& type,
  123. RRsetList* target, const FindOptions options) const
  124. {
  125. // Emulating a broken zone: mandatory apex RRs are missing if specifically
  126. // configured so (which are rare cases).
  127. if (name == origin_ && type == RRType::SOA() && !has_SOA_) {
  128. return (FindResult(NXDOMAIN, RRsetPtr()));
  129. } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
  130. return (FindResult(NXDOMAIN, RRsetPtr()));
  131. }
  132. // Special case for names on or under a zone cut
  133. if ((options & FIND_GLUE_OK) == 0 &&
  134. (name == delegation_name_ ||
  135. name.compare(delegation_name_).getRelation() ==
  136. NameComparisonResult::SUBDOMAIN)) {
  137. return (FindResult(DELEGATION, delegation_rrset_));
  138. }
  139. // normal cases. names are searched for only per exact-match basis
  140. // for simplicity.
  141. const Domains::const_iterator found_domain = domains_.find(name);
  142. if (found_domain != domains_.end()) {
  143. // First, try exact match.
  144. RRsetStore::const_iterator found_rrset =
  145. found_domain->second.find(type);
  146. if (found_rrset != found_domain->second.end()) {
  147. return (FindResult(SUCCESS, found_rrset->second));
  148. }
  149. // If not found but the qtype is ANY, return the first RRset
  150. if (!found_domain->second.empty() && type == RRType::ANY()) {
  151. for (found_rrset = found_domain->second.begin();
  152. found_rrset != found_domain->second.end(); found_rrset++)
  153. {
  154. // Insert RRs under the domain name into target
  155. target->addRRset(
  156. boost::const_pointer_cast<RRset>(found_rrset->second));
  157. }
  158. return (FindResult(SUCCESS, found_domain->second.begin()->second));
  159. }
  160. // Otherwise, if this domain name has CNAME, return it.
  161. found_rrset = found_domain->second.find(RRType::CNAME());
  162. if (found_rrset != found_domain->second.end()) {
  163. return (FindResult(CNAME, found_rrset->second));
  164. }
  165. // Otherwise it's NXRRSET case.
  166. return (FindResult(NXRRSET, RRsetPtr()));
  167. }
  168. // query name isn't found in our domains. returns NXDOMAIN.
  169. return (FindResult(NXDOMAIN, RRsetPtr()));
  170. }
  171. class QueryTest : public ::testing::Test {
  172. protected:
  173. QueryTest() :
  174. qname(Name("www.example.com")), qclass(RRClass::IN()),
  175. qtype(RRType::A()), response(Message::RENDER),
  176. qid(response.getQid()), query_code(Opcode::QUERY().getCode())
  177. {
  178. response.setRcode(Rcode::NOERROR());
  179. response.setOpcode(Opcode::QUERY());
  180. // create and add a matching zone.
  181. mock_zone = new MockZone();
  182. memory_datasrc.addZone(ZonePtr(mock_zone));
  183. }
  184. MockZone* mock_zone;
  185. MemoryDataSrc memory_datasrc;
  186. const Name qname;
  187. const RRClass qclass;
  188. const RRType qtype;
  189. Message response;
  190. const qid_t qid;
  191. const uint16_t query_code;
  192. };
  193. // A wrapper to check resulting response message commonly used in
  194. // tests below.
  195. // check_origin needs to be specified only when the authority section has
  196. // an SOA RR. The interface is not generic enough but should be okay
  197. // for our test cases in practice.
  198. void
  199. responseCheck(Message& response, const isc::dns::Rcode& rcode,
  200. unsigned int flags, const unsigned int ancount,
  201. const unsigned int nscount, const unsigned int arcount,
  202. const char* const expected_answer,
  203. const char* const expected_authority,
  204. const char* const expected_additional,
  205. const Name& check_origin = Name::ROOT_NAME())
  206. {
  207. // In our test cases QID, Opcode, and QDCOUNT should be constant, so
  208. // we don't bother the test cases specifying these values.
  209. headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
  210. flags, 0, ancount, nscount, arcount);
  211. if (expected_answer != NULL) {
  212. rrsetsCheck(expected_answer,
  213. response.beginSection(Message::SECTION_ANSWER),
  214. response.endSection(Message::SECTION_ANSWER),
  215. check_origin);
  216. }
  217. if (expected_authority != NULL) {
  218. rrsetsCheck(expected_authority,
  219. response.beginSection(Message::SECTION_AUTHORITY),
  220. response.endSection(Message::SECTION_AUTHORITY),
  221. check_origin);
  222. }
  223. if (expected_additional != NULL) {
  224. rrsetsCheck(expected_additional,
  225. response.beginSection(Message::SECTION_ADDITIONAL),
  226. response.endSection(Message::SECTION_ADDITIONAL));
  227. }
  228. }
  229. TEST_F(QueryTest, noZone) {
  230. // There's no zone in the memory datasource. So the response should have
  231. // REFUSED.
  232. MemoryDataSrc empty_memory_datasrc;
  233. Query nozone_query(empty_memory_datasrc, qname, qtype, response);
  234. EXPECT_NO_THROW(nozone_query.process());
  235. EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
  236. }
  237. TEST_F(QueryTest, exactMatch) {
  238. Query query(memory_datasrc, qname, qtype, response);
  239. EXPECT_NO_THROW(query.process());
  240. // find match rrset
  241. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
  242. www_a_txt, zone_ns_txt, ns_addrs_txt);
  243. }
  244. TEST_F(QueryTest, exactAddrMatch) {
  245. // find match rrset, omit additional data which has already been provided
  246. // in the answer section from the additional.
  247. EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
  248. response).process());
  249. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
  250. "noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
  251. "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
  252. "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
  253. }
  254. TEST_F(QueryTest, apexNSMatch) {
  255. // find match rrset, omit authority data which has already been provided
  256. // in the answer section from the authority section.
  257. EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
  258. response).process());
  259. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
  260. zone_ns_txt, NULL, ns_addrs_txt);
  261. }
  262. // test type any query logic
  263. TEST_F(QueryTest, exactAnyMatch) {
  264. // find match rrset, omit additional data which has already been provided
  265. // in the answer section from the additional.
  266. EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
  267. RRType::ANY(), response).process());
  268. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
  269. "noglue.example.com. 3600 IN A 192.0.2.53\n",
  270. zone_ns_txt,
  271. "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
  272. "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
  273. }
  274. TEST_F(QueryTest, apexAnyMatch) {
  275. // find match rrset, omit additional data which has already been provided
  276. // in the answer section from the additional.
  277. EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"),
  278. RRType::ANY(), response).process());
  279. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 0,
  280. "example.com. 3600 IN SOA . . 0 0 0 0 0\n"
  281. "example.com. 3600 IN NS glue.delegation.example.com.\n"
  282. "example.com. 3600 IN NS noglue.example.com.\n"
  283. "example.com. 3600 IN NS example.net.\n",
  284. NULL, NULL, mock_zone->getOrigin());
  285. }
  286. TEST_F(QueryTest, glueANYMatch) {
  287. EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
  288. RRType::ANY(), response).process());
  289. responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
  290. NULL, delegation_txt, ns_addrs_txt);
  291. }
  292. TEST_F(QueryTest, nodomainANY) {
  293. EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
  294. RRType::ANY(), response).process());
  295. responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
  296. NULL, soa_txt, NULL, mock_zone->getOrigin());
  297. }
  298. // This tests that when we need to look up Zone's apex NS records for
  299. // authoritative answer, and there is no apex NS records. It should
  300. // throw in that case.
  301. TEST_F(QueryTest, noApexNS) {
  302. // Disable apex NS record
  303. mock_zone->setApexNSFlag(false);
  304. EXPECT_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
  305. response).process(), Query::NoApexNS);
  306. // We don't look into the response, as it threw
  307. }
  308. TEST_F(QueryTest, delegation) {
  309. EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
  310. qtype, response).process());
  311. responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
  312. NULL, delegation_txt, ns_addrs_txt);
  313. }
  314. TEST_F(QueryTest, nxdomain) {
  315. EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
  316. response).process());
  317. responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
  318. NULL, soa_txt, NULL, mock_zone->getOrigin());
  319. }
  320. TEST_F(QueryTest, nxrrset) {
  321. EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
  322. RRType::TXT(), response).process());
  323. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
  324. NULL, soa_txt, NULL, mock_zone->getOrigin());
  325. }
  326. /*
  327. * This tests that when there's no SOA and we need a negative answer. It should
  328. * throw in that case.
  329. */
  330. TEST_F(QueryTest, noSOA) {
  331. // disable zone's SOA RR.
  332. mock_zone->setSOAFlag(false);
  333. // The NX Domain
  334. EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
  335. qtype, response).process(), Query::NoSOA);
  336. // Of course, we don't look into the response, as it throwed
  337. // NXRRSET
  338. EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
  339. qtype, response).process(), Query::NoSOA);
  340. }
  341. TEST_F(QueryTest, noMatchZone) {
  342. // there's a zone in the memory datasource but it doesn't match the qname.
  343. // should result in REFUSED.
  344. Query(memory_datasrc, Name("example.org"), qtype, response).process();
  345. EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
  346. }
  347. /*
  348. * Test MX additional processing.
  349. *
  350. * The MX RRset has two RRs, one pointing to a known domain with
  351. * A record, other to unknown out of zone one.
  352. */
  353. TEST_F(QueryTest, MX) {
  354. Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
  355. response).process();
  356. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
  357. mx_txt, NULL,
  358. (string(ns_addrs_txt) + string(www_a_txt)).c_str());
  359. }
  360. /*
  361. * Test when we ask for MX whose exchange is an alias (CNAME in this case).
  362. *
  363. * This should not trigger the additional processing for the exchange.
  364. */
  365. TEST_F(QueryTest, MXAlias) {
  366. Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
  367. response).process();
  368. // there shouldn't be no additional RRs for the exchanges (we have 3
  369. // RRs for the NS). The normal MX case is tested separately so we don't
  370. // bother to examine the answer (and authority) sections.
  371. responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
  372. NULL, NULL, ns_addrs_txt);
  373. }
  374. }