Browse Source

Merge branch 'work/dname/query'

Michal 'vorner' Vaner 14 years ago
parent
commit
696cec5b0e
2 changed files with 212 additions and 22 deletions
  1. 50 9
      src/bin/auth/query.cc
  2. 162 13
      src/bin/auth/tests/query_unittest.cc

+ 50 - 9
src/bin/auth/query.cc

@@ -141,13 +141,56 @@ Query::process() const {
 
     // Found a zone which is the nearest ancestor to QNAME, set the AA bit
     response_.setHeaderFlag(Message::HEADERFLAG_AA);
+    response_.setRcode(Rcode::NOERROR());
     while (keep_doing) {
         keep_doing = false;
         std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
-        Zone::FindResult db_result =
-            result.zone->find(qname_, qtype_, target.get());
+        const Zone::FindResult db_result(result.zone->find(qname_, qtype_,
+            target.get()));
 
         switch (db_result.code) {
+            case Zone::DNAME: {
+                // First, put the dname into the answer
+                response_.addRRset(Message::SECTION_ANSWER,
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                /*
+                 * Empty DNAME should never get in, as it is impossible to
+                 * create one in master file.
+                 *
+                 * FIXME: Other way to prevent this should be done
+                 */
+                assert(db_result.rrset->getRdataCount() > 0);
+                // Get the data of DNAME
+                const rdata::generic::DNAME& dname(
+                    dynamic_cast<const rdata::generic::DNAME&>(
+                    db_result.rrset->getRdataIterator()->getCurrent()));
+                // The yet unmatched prefix dname
+                const Name prefix(qname_.split(0, qname_.getLabelCount() -
+                    db_result.rrset->getName().getLabelCount()));
+                // If we put it together, will it be too long?
+                // (The prefix contains trailing ., which will be removed
+                if (prefix.getLength() - Name::ROOT_NAME().getLength() +
+                    dname.getDname().getLength() > Name::MAX_WIRE) {
+                    /*
+                     * In case the synthesized name is too long, section 4.1
+                     * of RFC 2672 mandates we return YXDOMAIN.
+                     */
+                    response_.setRcode(Rcode::YXDOMAIN());
+                    return;
+                }
+                // The new CNAME we are creating (it will be unsigned even
+                // with DNSSEC, the DNAME is signed and it can be validated
+                // by that)
+                RRsetPtr cname(new RRset(qname_, db_result.rrset->getClass(),
+                    RRType::CNAME(), db_result.rrset->getTTL()));
+                // Construct the new target by replacing the end
+                cname->addRdata(rdata::generic::CNAME(qname_.split(0,
+                    qname_.getLabelCount() -
+                    db_result.rrset->getName().getLabelCount()).
+                    concatenate(dname.getDname())));
+                response_.addRRset(Message::SECTION_ANSWER, cname);
+                break;
+            }
             case Zone::CNAME:
                 /*
                  * We don't do chaining yet. Therefore handling a CNAME is
@@ -155,10 +198,13 @@ Query::process() const {
                  * what we expected. It means no exceptions in ANY or NS
                  * on the origin (though CNAME in origin is probably
                  * forbidden anyway).
+                 *
+                 * So, just put it there.
                  */
-                // No break; here, fall trough.
+                response_.addRRset(Message::SECTION_ANSWER,
+                    boost::const_pointer_cast<RRset>(db_result.rrset));
+                break;
             case Zone::SUCCESS:
-                response_.setRcode(Rcode::NOERROR());
                 if (qtype_is_any) {
                     // If quety type is ANY, insert all RRs under the domain
                     // into answer section.
@@ -184,7 +230,6 @@ Query::process() const {
                 break;
             case Zone::DELEGATION:
                 response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
-                response_.setRcode(Rcode::NOERROR());
                 response_.addRRset(Message::SECTION_AUTHORITY,
                     boost::const_pointer_cast<RRset>(db_result.rrset));
                 getAdditional(*result.zone, *db_result.rrset);
@@ -196,12 +241,8 @@ Query::process() const {
                 break;
             case Zone::NXRRSET:
                 // Just empty answer with SOA in authority section
-                response_.setRcode(Rcode::NOERROR());
                 putSOA(*result.zone);
                 break;
-            case Zone::DNAME:
-                // TODO : replace qname, continue lookup
-                break;
         }
     }
 }

+ 162 - 13
src/bin/auth/tests/query_unittest.cc

@@ -75,6 +75,17 @@ const char* const cname_nxdom_txt =
 // CNAME Leading out of zone
 const char* const cname_out_txt =
     "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
+// The DNAME to do tests against
+const char* const dname_txt =
+    "dname.example.com. 3600 IN DNAME "
+    "somethinglong.dnametarget.example.com.\n";
+// Some data at the dname node (allowed by RFC 2672)
+const char* const dname_a_txt =
+    "dname.example.com. 3600 IN A 192.0.2.5\n";
+// This is not inside the zone, this is created at runtime
+const char* const synthetized_cname_txt =
+    "www.dname.example.com. 3600 IN CNAME "
+    "www.somethinglong.dnametarget.example.com.\n";
 // The rest of data won't be referenced from the test cases.
 const char* const other_zone_rrs =
     "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
@@ -88,13 +99,16 @@ const char* const other_zone_rrs =
 // behavior.
 // For simplicity, most names are assumed to be "in zone"; there's only
 // one zone cut at the point of name "delegation.example.com".
-// It doesn't handle empty non terminal nodes (if we need to test such cases
-// find() should have specialized code for it).
+// Another special name is "dname.example.com".  Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
 class MockZone : public Zone {
 public:
     MockZone() :
         origin_(Name("example.com")),
         delegation_name_("delegation.example.com"),
+        dname_name_("dname.example.com"),
         has_SOA_(true),
         has_apex_NS_(true),
         rrclass_(RRClass::IN())
@@ -102,7 +116,8 @@ public:
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
             delegation_txt << mx_txt << www_a_txt << cname_txt <<
-            cname_nxdom_txt << cname_out_txt << other_zone_rrs;
+            cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
+            other_zone_rrs;
 
         masterLoad(zone_stream, origin_, rrclass_,
                    boost::bind(&MockZone::loadRRset, this, _1));
@@ -131,14 +146,20 @@ private:
         if (rrset->getName() == delegation_name_ &&
             rrset->getType() == RRType::NS()) {
             delegation_rrset_ = rrset;
+        } else if (rrset->getName() == dname_name_ &&
+            rrset->getType() == RRType::DNAME()) {
+            dname_rrset_ = rrset;
         }
     }
 
     const Name origin_;
+    // Names where we delegate somewhere else
     const Name delegation_name_;
+    const Name dname_name_;
     bool has_SOA_;
     bool has_apex_NS_;
     ConstRRsetPtr delegation_rrset_;
+    ConstRRsetPtr dname_rrset_;
     const RRClass rrclass_;
 };
 
@@ -160,6 +181,10 @@ MockZone::find(const Name& name, const RRType& type,
          name.compare(delegation_name_).getRelation() ==
          NameComparisonResult::SUBDOMAIN)) {
         return (FindResult(DELEGATION, delegation_rrset_));
+    // And under DNAME
+    } else if (name.compare(dname_name_).getRelation() ==
+        NameComparisonResult::SUBDOMAIN) {
+        return (FindResult(DNAME, dname_rrset_));
     }
 
     // normal cases.  names are searched for only per exact-match basis
@@ -176,8 +201,7 @@ MockZone::find(const Name& name, const RRType& type,
         // If not found but we have a target, fill it with all RRsets here
         if (!found_domain->second.empty() && target != NULL) {
             for (found_rrset = found_domain->second.begin();
-                 found_rrset != found_domain->second.end(); found_rrset++)
-            {
+                 found_rrset != found_domain->second.end(); found_rrset++) {
                 // Insert RRs under the domain name into target
                 target->addRRset(
                     boost::const_pointer_cast<RRset>(found_rrset->second));
@@ -443,8 +467,8 @@ TEST_F(QueryTest, CNAME) {
     Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME) {
@@ -465,8 +489,8 @@ TEST_F(QueryTest, CNAME_NX_RRSET) {
     Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
@@ -488,8 +512,8 @@ TEST_F(QueryTest, CNAME_NX_DOMAIN) {
     Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_nxdom_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
@@ -513,8 +537,8 @@ TEST_F(QueryTest, CNAME_OUT) {
     Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
         response).process();
 
-    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
-        cname_out_txt, zone_ns_txt, ns_addrs_txt);
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
+        cname_out_txt, NULL, NULL);
 }
 
 TEST_F(QueryTest, explicitCNAME_OUT) {
@@ -526,4 +550,129 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
         cname_out_txt, zone_ns_txt, ns_addrs_txt);
 }
 
+/*
+ * Test a query under a domain with DNAME. We should get a synthetized CNAME
+ * as well as the DNAME.
+ *
+ * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
+ * as well. This includes tests pointing inside the zone, outside the zone,
+ * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
+ */
+TEST_F(QueryTest, DNAME) {
+    Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        (string(dname_txt) + synthetized_cname_txt).c_str(),
+        NULL, NULL);
+}
+
+/*
+ * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
+ * CNAME.
+ *
+ * ANY is handled specially sometimes. We check it is not the case with
+ * DNAME.
+ */
+TEST_F(QueryTest, DNAME_ANY) {
+    Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
+}
+
+// Test when we ask for DNAME explicitly, it does no synthetizing.
+TEST_F(QueryTest, explicitDNAME) {
+    Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        dname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME. It should not synthetize
+ * the CNAME, it should return the RRset.
+ */
+TEST_F(QueryTest, DNAME_A) {
+    Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        dname_a_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+/*
+ * Request a RRset at the domain with DNAME that is not there (NXRRSET).
+ * It should not synthetize the CNAME.
+ */
+TEST_F(QueryTest, DNAME_NX_RRSET) {
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
+        RRType::TXT(), response).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
+        NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
+/*
+ * Constructing the CNAME will result in a name that is too long. This,
+ * however, should not throw (and crash the server), but respond with
+ * YXDOMAIN.
+ */
+TEST_F(QueryTest, LongDNAME) {
+    // A name that is as long as it can be
+    Name longname(
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "dname.example.com.");
+    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+        response).process());
+
+    responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
+        dname_txt, NULL, NULL);
+}
+
+/*
+ * Constructing the CNAME will result in a name of maximal length.
+ * This tests that we don't reject valid one by some kind of off by
+ * one mistake.
+ */
+TEST_F(QueryTest, MaxLenDNAME) {
+    Name longname(
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
+        "dname.example.com.");
+    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
+        response).process());
+
+    // Check the answer is OK
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
+        NULL, NULL, NULL);
+
+    // Check that the CNAME has the maximal length.
+    bool ok(false);
+    for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
+        i != response.endSection(Message::SECTION_ANSWER); ++ i) {
+        if ((*i)->getType() == RRType::CNAME()) {
+            ok = true;
+            RdataIteratorPtr ci((*i)->getRdataIterator());
+            ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
+            /*
+             * Does anybody have a clue why, if the Name::MAX_WIRE is put
+             * directly inside ASSERT_EQ, it fails to link and complains
+             * it is unresolved external?
+             */
+            const size_t max_len(Name::MAX_WIRE);
+            ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
+                ci->getCurrent()).getCname().getLength());
+        }
+    }
+    EXPECT_TRUE(ok) << "The synthetized CNAME not found";
+}
+
 }