Browse Source

added CNAME support for MemoryZone: handle CNAME cases for adding and
finding.

JINMEI Tatuya 14 years ago
parent
commit
29bfd26b74

+ 37 - 8
src/lib/datasrc/memory_datasrc.cc

@@ -73,6 +73,15 @@ struct MemoryZone::MemoryZoneImpl {
         if (!rrset) {
             isc_throw(NullRRset, "The rrset provided is NULL");
         }
+        if (rrset->getType() == RRType::CNAME() &&
+            rrset->getRdataCount() > 1) {
+            // XXX: this is not only for CNAME.  We should generalize this
+            // code for all other "singleton RR types" (such as SOA) in a
+            // separate task.
+            isc_throw(AddError, "multiple RRs of singleton type for "
+                      << rrset->getName());
+        }
+
         Name name(rrset->getName());
         NameComparisonResult compare(origin_.compare(name));
         if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
@@ -92,7 +101,7 @@ struct MemoryZone::MemoryZoneImpl {
             default:
                 assert(0);
         }
-        assert(node);
+        assert(node != NULL);
 
         // Now get the domain
         DomainPtr domain;
@@ -104,6 +113,25 @@ struct MemoryZone::MemoryZoneImpl {
             domain = node->getData();
         }
 
+        // Ensure CNAME and other type of RR don't coexist for the same
+        // owner name.
+        // Note: when the check fails and the exception is thrown, it may
+        // break strong exception guarantee.  At the moment we prefer
+        // code simplicity and don't bother to introduce complicated
+        // recovery code.
+        if (rrset->getType() == RRType::CNAME()) {
+            // XXX: this check will become incorrect when we support DNSSEC
+            // (depending on how we support DNSSEC).  We should revisit it
+            // at that point.
+            if (!domain->empty()) {
+                isc_throw(AddError, "CNAME can't be added with other data for "
+                          << rrset->getName());
+            }
+        } else if (domain->find(RRType::CNAME()) != domain->end()) {
+            isc_throw(AddError, "CNAME and " << rrset->getType() <<
+                      " can't coexist for " << rrset->getName());
+        }
+
         // Try inserting the rrset there
         if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
             // Ok, we just put it in
@@ -226,14 +254,15 @@ struct MemoryZone::MemoryZoneImpl {
         if (found != node->getData()->end()) {
             // Good, it is here
             return (FindResult(SUCCESS, found->second));
-        } else {
-            /*
-             * TODO Look for CNAME and DNAME (it should be OK to do so when
-             * the value is not found, as CNAME/DNAME domain should be
-             * empty otherwise.)
-             */
-            return (FindResult(NXRRSET, ConstRRsetPtr()));
+        } else if (state.zonecut_node_ == NULL) {
+            // Next, try CNAME unless we are under a zone cut.
+            found = node->getData()->find(RRType::CNAME());
+            if (found != node->getData()->end()) {
+                return (FindResult(CNAME, found->second));
+            }
         }
+        // No exact match or CNAME.  Return NXRRSET.
+        return (FindResult(NXRRSET, ConstRRsetPtr()));
     }
 };
 

+ 24 - 4
src/lib/datasrc/memory_datasrc.h

@@ -71,10 +71,15 @@ public:
     ///
     /// It puts another RRset into the zone.
     ///
-    /// It throws NullRRset or OutOfZone if the provided rrset is invalid. It
-    /// might throw standard allocation exceptions, in which case this function
-    /// does not guarantee strong exception safety (it is currently not needed,
-    /// if it is needed in future, it should be implemented).
+    /// Except for NullRRset and OutOfZone, this method does not guarantee
+    /// strong exception safety (it is currently not needed, if it is needed
+    /// in future, it should be implemented).
+    ///
+    /// \throw NullRRset \c rrset is a NULL pointer.
+    /// \throw OutOfZone The owner name of \c rrset is outside of the
+    /// origin of the zone.
+    /// \throw AddError Other general errors.
+    /// \throw Others This method might throw standard allocation exceptions.
     ///
     /// \param rrset The set to add.
     /// \return SUCCESS or EXIST (if an rrset for given name and type already
@@ -100,6 +105,21 @@ public:
         { }
     };
 
+    /// \brief General failure exception for \c add().
+    ///
+    /// This is thrown against general error cases in adding an RRset
+    /// to the zone.
+    ///
+    /// Note: this exception would cover cases for \c OutOfZone or
+    /// \c NullRRset.  We'll need to clarify and unify the granularity
+    /// of exceptions eventually.  For now, exceptions are added as
+    /// developers see the need for it.
+    struct AddError : public InvalidParameter {
+        AddError(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
     /// Return the master file name of the zone
     ///
     /// This method returns the name of the zone's master file to be loaded.

+ 50 - 2
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -15,6 +15,8 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/masterload.h>
@@ -24,6 +26,7 @@
 #include <gtest/gtest.h>
 
 using namespace isc::dns;
+using namespace isc::dns::rdata;
 using namespace isc::datasrc;
 
 namespace {
@@ -142,6 +145,7 @@ public:
         class_(RRClass::IN()),
         origin_("example.org"),
         ns_name_("ns.example.org"),
+        cname_name_("cname.example.org"),
         child_ns_name_("child.example.org"),
         child_glue_name_("ns.child.example.org"),
         grandchild_ns_name_("grand.child.example.org"),
@@ -153,6 +157,8 @@ public:
         rr_ns_a_(new RRset(ns_name_, class_, RRType::A(), RRTTL(300))),
         rr_ns_aaaa_(new RRset(ns_name_, class_, RRType::AAAA(), RRTTL(300))),
         rr_a_(new RRset(origin_, class_, RRType::A(), RRTTL(300))),
+        rr_cname_(new RRset(cname_name_, class_, RRType::CNAME(), RRTTL(300))),
+        rr_cname_a_(new RRset(cname_name_, class_, RRType::A(), RRTTL(300))),
         rr_child_ns_(new RRset(child_ns_name_, class_, RRType::NS(),
                                RRTTL(300))),
         rr_child_glue_(new RRset(child_glue_name_, class_, RRType::A(),
@@ -165,8 +171,8 @@ public:
     }
     // Some data to test with
     const RRClass class_;
-    const Name origin_, ns_name_, child_ns_name_, child_glue_name_,
-        grandchild_ns_name_, grandchild_glue_name_;
+    const Name origin_, ns_name_, cname_name_, child_ns_name_,
+        child_glue_name_, grandchild_ns_name_, grandchild_glue_name_;
     // The zone to torture by tests
     MemoryZone zone_;
 
@@ -187,6 +193,8 @@ public:
         rr_ns_aaaa_,
         // A of example.org
         rr_a_;
+    RRsetPtr rr_cname_;         // CNAME in example.org (RDATA will be added)
+    ConstRRsetPtr rr_cname_a_; // for mixed CNAME + A case
     ConstRRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
     ConstRRsetPtr rr_child_glue_; // glue RR of the child domain
     ConstRRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
@@ -264,6 +272,46 @@ TEST_F(MemoryZoneTest, add) {
     EXPECT_NO_THROW(EXPECT_EQ(EXIST, zone_.add(rr_ns_a_)));
 }
 
+TEST_F(MemoryZoneTest, addMultipleCNAMEs) {
+    rr_cname_->addRdata(generic::CNAME("canonical1.example.org."));
+    rr_cname_->addRdata(generic::CNAME("canonical2.example.org."));
+    EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addCNAMEThenOther) {
+    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
+    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
+    EXPECT_THROW(zone_.add(rr_cname_a_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, addOtherThenCNAME) {
+    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
+    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_a_));
+    EXPECT_THROW(zone_.add(rr_cname_), MemoryZone::AddError);
+}
+
+TEST_F(MemoryZoneTest, findCNAME) {
+    // install CNAME RR
+    rr_cname_->addRdata(generic::CNAME("canonical.example.org."));
+    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_));
+
+    // Find A RR of the same.  Should match the CNAME
+    findTest(cname_name_, RRType::NS(), Zone::CNAME, true, rr_cname_);
+
+    // Find the CNAME itself.  Should result in normal SUCCESS
+    findTest(cname_name_, RRType::CNAME(), Zone::SUCCESS, true, rr_cname_);
+}
+
+TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
+    EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
+    RRsetPtr rr_cname_under_cut_(new RRset(Name("cname.child.example.org"),
+                                           class_, RRType::CNAME(),
+                                           RRTTL(300)));
+    EXPECT_EQ(SUCCESS, zone_.add(rr_cname_under_cut_));
+    findTest(Name("cname.child.example.org"), RRType::AAAA(),
+             Zone::NXRRSET, false, ConstRRsetPtr(), NULL, Zone::FIND_GLUE_OK);
+}
+
 // Test adding child zones and zone cut handling
 TEST_F(MemoryZoneTest, delegationNS) {
     // add in-zone data

+ 7 - 2
src/lib/datasrc/zone.h

@@ -155,8 +155,13 @@ public:
     /// - If there is a matching name but no RRset of the search type, it
     ///   returns the code of \c NXRRSET, and, if DNSSEC is required,
     ///   the NSEC RRset for that name.
-    /// - If there is a matching name with CNAME, it returns the code of
-    ///   \c CNAME and that CNAME RR.
+    /// - If there is a CNAME RR of the searched name but there is no
+    ///   RR of the searched type of the name (so this type is different from
+    ///   CNAME), it returns the code of \c CNAME and that CNAME RR.
+    ///   This special rule does not apply on or under a zone cut (which is
+    ///   possible when the \c GLUE_OK option is specified).
+    ///   Note also that if the searched RR type is CNAME, it is considered
+    ///   a successful match, and the code of \c SUCCESS will be returned.
     /// - If the search name matches a delegation point of DNAME, it returns
     ///   the code of \c DNAME and that DNAME RR.
     ///