Browse Source

Merge branch 'work/wildcard/main'

Michal 'vorner' Vaner 14 years ago
parent
commit
72c4f50469
2 changed files with 283 additions and 35 deletions
  1. 70 13
      src/lib/datasrc/memory_datasrc.cc
  2. 213 22
      src/lib/datasrc/tests/memory_datasrc_unittest.cc

+ 70 - 13
src/lib/datasrc/memory_datasrc.cc

@@ -94,22 +94,22 @@ struct MemoryZone::MemoryZoneImpl {
              l > origin_labels;
              --l, wname = wname.split(1)) {
             if (wname.isWildcard()) {
-                // Ensure a separate level exists for the wildcard name.
-                // Note: for 'name' itself we do this later anyway, but the
-                // overhead should be marginal because wildcard names should
-                // be rare.
+                // Ensure a separate level exists for the "wildcarding" name,
+                // and mark the node as "wild".
                 DomainNode* node;
                 DomainTree::Result result(domains.insert(wname.split(1),
                                                          &node));
                 assert(result == DomainTree::SUCCESS ||
                        result == DomainTree::ALREADYEXISTS);
+                node->setFlag(DOMAINFLAG_WILD);
 
-                // Ensure a separate level exists for the "wildcarding" name,
-                // and mark the node as "wild".
+                // Ensure a separate level exists for the wildcard name.
+                // Note: for 'name' itself we do this later anyway, but the
+                // overhead should be marginal because wildcard names should
+                // be rare.
                 result = domains.insert(wname, &node);
                 assert(result == DomainTree::SUCCESS ||
                        result == DomainTree::ALREADYEXISTS);
-                node->setFlag(DOMAINFLAG_WILD);
             }
         }
     }
@@ -351,6 +351,35 @@ struct MemoryZone::MemoryZoneImpl {
         return (false);
     }
 
+    /*
+     * Prepares a rrset to be return as a result.
+     *
+     * If rename is false, it returns the one provided. If it is true, it
+     * creates a new rrset with the same data but with provided name.
+     * It is designed for wildcard case, where we create the rrsets
+     * dynamically.
+     */
+    static ConstRRsetPtr prepareRRset(const Name& name, const ConstRRsetPtr&
+        rrset, bool rename)
+    {
+        if (rename) {
+            /*
+             * We lose a signature here. But it would be wrong anyway, because
+             * the name changed. This might turn out to be unimportant in
+             * future, because wildcards will probably be handled somehow
+             * by DNSSEC.
+             */
+            RRsetPtr result(new RRset(name, rrset->getClass(),
+                rrset->getType(), rrset->getTTL()));
+            for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
+                i->next()) {
+                result->addRdata(i->getCurrent());
+            }
+            return (result);
+        } else {
+            return (rrset);
+        }
+    }
 
     // Implementation of MemoryZone::find
     FindResult find(const Name& name, RRType type,
@@ -360,6 +389,7 @@ struct MemoryZone::MemoryZoneImpl {
         DomainNode* node(NULL);
         FindState state(options);
         RBTreeNodeChain<Domain> node_path;
+        bool rename(false);
         switch (domains_.find(name, &node, node_path, cutCallback, &state)) {
             case DomainTree::PARTIALMATCH:
                 /*
@@ -383,10 +413,33 @@ struct MemoryZone::MemoryZoneImpl {
                 if (state.dname_node_ != NULL) {
                     // We were traversing a DNAME node (and wanted to go
                     // lower below it), so return the DNAME
-                    return (FindResult(DNAME, state.rrset_));
+                    return (FindResult(DNAME, prepareRRset(name, state.rrset_,
+                        rename)));
                 }
                 if (state.zonecut_node_ != NULL) {
-                    return (FindResult(DELEGATION, state.rrset_));
+                    return (FindResult(DELEGATION, prepareRRset(name,
+                        state.rrset_, rename)));
+                }
+                /*
+                 * No redirection anywhere. Let's try if it is a wildcard.
+                 */
+                if (node->getFlag(DOMAINFLAG_WILD)) {
+                    Name wildcard(Name("*").concatenate(
+                        node_path.getAbsoluteName()));
+                    DomainTree::Result result(domains_.find(wildcard, &node));
+                    /*
+                     * Otherwise, why would the DOMAINFLAG_WILD be there if
+                     * there was no wildcard under it?
+                     */
+                    assert(result = DomainTree::EXACTMATCH);
+                    /*
+                     * We have the wildcard node now. Jump below the switch,
+                     * where handling of the common (exact-match) case is.
+                     *
+                     * However, rename it to the searched name.
+                     */
+                    rename = true;
+                    break;
                 }
 
                 // If the RBTree search stopped at a node for a super domain
@@ -420,7 +473,8 @@ struct MemoryZone::MemoryZoneImpl {
         if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_) {
             found = node->getData()->find(RRType::NS());
             if (found != node->getData()->end()) {
-                return (FindResult(DELEGATION, found->second));
+                return (FindResult(DELEGATION, prepareRRset(name,
+                    found->second, rename)));
             }
         }
 
@@ -431,7 +485,8 @@ struct MemoryZone::MemoryZoneImpl {
                  found != node->getData()->end(); found++)
             {
                 target->addRRset(
-                    boost::const_pointer_cast<RRset>(found->second));
+                    boost::const_pointer_cast<RRset>(prepareRRset(name,
+                    found->second, rename)));
             }
             return (FindResult(SUCCESS, ConstRRsetPtr()));
         }
@@ -439,12 +494,14 @@ struct MemoryZone::MemoryZoneImpl {
         found = node->getData()->find(type);
         if (found != node->getData()->end()) {
             // Good, it is here
-            return (FindResult(SUCCESS, found->second));
+            return (FindResult(SUCCESS, prepareRRset(name, found->second,
+                rename)));
         } else {
             // Next, try CNAME.
             found = node->getData()->find(RRType::CNAME());
             if (found != node->getData()->end()) {
-                return (FindResult(CNAME, found->second));
+                return (FindResult(CNAME, prepareRRset(name, found->second,
+                    rename)));
             }
         }
         // No exact match or CNAME.  Return NXRRSET.

+ 213 - 22
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -197,12 +197,14 @@ public:
              &rr_child_dname_},
             {"example.com. 300 IN A 192.0.2.10", &rr_out_},
             {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+            {"foo.wild.example.org. 300 IN A 192.0.2.3", &rr_under_wild_},
             {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
             {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
              &rr_nested_emptywild_},
             {"*.nswild.example.org. 300 IN NS nswild.example.", &rr_nswild_},
             {"*.dnamewild.example.org. 300 IN DNAME dnamewild.example.",
              &rr_dnamewild_},
+            {"*.child.example.org. 300 IN A 192.0.2.1", &rr_child_wild_},
             {NULL, NULL}
         };
 
@@ -252,6 +254,8 @@ public:
     RRsetPtr rr_emptywild_;
     RRsetPtr rr_nested_emptywild_;
     RRsetPtr rr_nswild_, rr_dnamewild_;
+    RRsetPtr rr_child_wild_;
+    RRsetPtr rr_under_wild_;
 
     /**
      * \brief Test one find query to the zone.
@@ -268,13 +272,18 @@ public:
      * \param answer The expected rrset, if any should be returned.
      * \param zone Check different MemoryZone object than zone_ (if NULL,
      *     uses zone_)
+     * \param check_wild_answer Checks that the answer has the same RRs, type
+     *     class and TTL as the eqxpected answer and that the name corresponds
+     *     to the one searched. It is meant for checking answers for wildcard
+     *     queries.
      */
     void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
                   bool check_answer = true,
                   const ConstRRsetPtr& answer = ConstRRsetPtr(),
                   RRsetList* target = NULL,
                   MemoryZone* zone = NULL,
-                  Zone::FindOptions options = Zone::FIND_DEFAULT)
+                  Zone::FindOptions options = Zone::FIND_DEFAULT,
+                  bool check_wild_answer = false)
     {
         if (!zone) {
             zone = &zone_;
@@ -288,6 +297,29 @@ public:
                 EXPECT_EQ(result, find_result.code);
                 if (check_answer) {
                     EXPECT_EQ(answer, find_result.rrset);
+                } else if (check_wild_answer) {
+                    RdataIteratorPtr expectedIt(answer->getRdataIterator());
+                    RdataIteratorPtr actualIt(
+                        find_result.rrset->getRdataIterator());
+                    while (!expectedIt->isLast() && !actualIt->isLast()) {
+                        EXPECT_EQ(0, expectedIt->getCurrent().compare(
+                            actualIt->getCurrent())) << "The RRs differ ('" <<
+                            expectedIt->getCurrent().toText() << "', '" <<
+                            actualIt->getCurrent().toText() << "')";
+                        expectedIt->next();
+                        actualIt->next();
+                    }
+                    EXPECT_TRUE(expectedIt->isLast()) <<
+                        "Result has less RRs than expected";
+                    EXPECT_TRUE(actualIt->isLast()) <<
+                        "Result has more RRs than expected";
+                    EXPECT_EQ(answer->getClass(),
+                        find_result.rrset->getClass());
+                    EXPECT_EQ(answer->getType(),
+                        find_result.rrset->getType());
+                    EXPECT_EQ(answer->getTTL(),
+                        find_result.rrset->getTTL());
+                    EXPECT_EQ(name, find_result.rrset->getName());
                 }
             });
     }
@@ -542,11 +574,6 @@ TEST_F(MemoryZoneTest, glue) {
     findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
              true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
 
-    // TODO:
-    // glue name would match a wildcard under a zone cut: wildcard match
-    // shouldn't happen under a cut and result must be PARTIALMATCH
-    // (This case cannot be tested yet)
-
     // nested cut case.  The glue should be found.
     findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
              Zone::SUCCESS,
@@ -656,11 +683,11 @@ TEST_F(MemoryZoneTest, load) {
         MasterLoadError);
 }
 
-// Note: once #507 is merged, findTest() would succeed whether or not
-// we load the wildcard correctly, so the test will become meaningless.
-// The plan is to clean them up when we complete #551 (then the effect of
-// load will be indirectly tested via find() tests).
-TEST_F(MemoryZoneTest, loadWildcard) {
+/*
+ * Test that puts a (simple) wildcard into the zone and checks we can
+ * correctly find the data.
+ */
+TEST_F(MemoryZoneTest, wildcard) {
     /*
      *            example.org.
      *                 |
@@ -669,11 +696,96 @@ TEST_F(MemoryZoneTest, loadWildcard) {
      *                 *
      */
     EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
-    findTest(Name("wild.example.org"), RRType::A(), Zone::NXRRSET);
+
+    // Search at the parent. The parent will not have the A, but it will
+    // be in the wildcard (so check the wildcard isn't matched at the parent)
+    {
+        SCOPED_TRACE("Search at parrent");
+        findTest(Name("wild.example.org"), RRType::A(), Zone::NXRRSET);
+    }
+
+    // Search the original name of wildcard
+    {
+        SCOPED_TRACE("Search directly at *");
+        findTest(Name("*.wild.example.org"), RRType::A(), Zone::SUCCESS, true,
+            rr_wild_);
+    }
+    // Search "created" name.
+    {
+        SCOPED_TRACE("Search at created child");
+        findTest(Name("a.wild.example.org"), RRType::A(), Zone::SUCCESS, false,
+            rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+    }
+
+    // Search another created name, this time little bit lower
+    {
+        SCOPED_TRACE("Search at created grand-child");
+        findTest(Name("a.b.wild.example.org"), RRType::A(), Zone::SUCCESS,
+            false, rr_wild_, NULL, NULL, Zone::FIND_DEFAULT, true);
+    }
+
+    EXPECT_EQ(SUCCESS, zone_.add(rr_under_wild_));
+    {
+        SCOPED_TRACE("Search under non-wildcard");
+        findTest(Name("bar.foo.wild.example.org"), RRType::A(),
+            Zone::NXDOMAIN);
+    }
+}
+
+/*
+ * Test that we don't match a wildcard if we get under delegation.
+ * By 4.3.3 of RFC1034:
+ * "Wildcard RRs do not apply:
+ *   - When the query is in another zone.  That is, delegation cancels
+ *     the wildcard defaults."
+ */
+TEST_F(MemoryZoneTest, delegatedWildcard) {
+    EXPECT_EQ(SUCCESS, zone_.add(rr_child_wild_));
+    EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_));
+
+    {
+        SCOPED_TRACE("Looking under delegation point");
+        findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+            true, rr_child_ns_);
+    }
+
+    {
+        SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
+        findTest(Name("a.child.example.org"), RRType::A(), Zone::DELEGATION,
+            true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
+    }
+}
+
+// Tests combination of wildcard and ANY.
+TEST_F(MemoryZoneTest, anyWildcard) {
+    EXPECT_EQ(SUCCESS, zone_.add(rr_wild_));
+
+    // First try directly the name (normal match)
+    {
+        SCOPED_TRACE("Asking direcly for *");
+        RRsetList target;
+        findTest(Name("*.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+            true, ConstRRsetPtr(), &target);
+        ASSERT_EQ(1, target.size());
+        EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+        EXPECT_EQ(Name("*.wild.example.org"), (*target.begin())->getName());
+    }
+
+    // Then a wildcard match
+    {
+        SCOPED_TRACE("Asking in the wild way");
+        RRsetList target;
+        findTest(Name("a.wild.example.org"), RRType::ANY(), Zone::SUCCESS,
+            true, ConstRRsetPtr(), &target);
+        ASSERT_EQ(1, target.size());
+        EXPECT_EQ(RRType::A(), (*target.begin())->getType());
+        EXPECT_EQ(Name("a.wild.example.org"), (*target.begin())->getName());
+    }
 }
 
-// same note as loadWildcard applies.
-TEST_F(MemoryZoneTest, loadEmptyWildcard) {
+// Test there's nothing in the wildcard in the middle if we load
+// wild.*.foo.example.org.
+TEST_F(MemoryZoneTest, emptyWildcard) {
     /*
      *            example.org.
      *                foo
@@ -681,17 +793,96 @@ TEST_F(MemoryZoneTest, loadEmptyWildcard) {
      *               wild
      */
     EXPECT_EQ(SUCCESS, zone_.add(rr_emptywild_));
-    findTest(Name("*.foo.example.org"), RRType::A(), Zone::NXRRSET);
-    findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+
+    {
+        SCOPED_TRACE("Asking for the original record under wildcard");
+        findTest(Name("wild.*.foo.example.org"), RRType::A(), Zone::SUCCESS,
+            true, rr_emptywild_);
+    }
+
+    {
+        SCOPED_TRACE("Asking for A record");
+        findTest(Name("a.foo.example.org"), RRType::A(), Zone::NXRRSET);
+        findTest(Name("*.foo.example.org"), RRType::A(), Zone::NXRRSET);
+        findTest(Name("foo.example.org"), RRType::A(), Zone::NXRRSET);
+    }
+
+    {
+        SCOPED_TRACE("Asking for ANY record");
+        RRsetList normalTarget;
+        findTest(Name("*.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+            ConstRRsetPtr(), &normalTarget);
+        EXPECT_EQ(0, normalTarget.size());
+
+        RRsetList wildTarget;
+        findTest(Name("a.foo.example.org"), RRType::ANY(), Zone::NXRRSET, true,
+            ConstRRsetPtr(), &wildTarget);
+        EXPECT_EQ(0, wildTarget.size());
+    }
+
+    {
+        SCOPED_TRACE("Asking on the non-terminal");
+        findTest(Name("wild.bar.foo.example.org"), RRType::A(),
+            Zone::NXRRSET);
+    }
 }
 
-// same note as loadWildcard applies.
-TEST_F(MemoryZoneTest, loadNestedEmptyWildcard) {
+// Same as emptyWildcard, but with multiple * in the path.
+TEST_F(MemoryZoneTest, nestedEmptyWildcard) {
     EXPECT_EQ(SUCCESS, zone_.add(rr_nested_emptywild_));
-    findTest(Name("*.foo.*.bar.example.org"), RRType::A(), Zone::NXRRSET);
-    findTest(Name("foo.*.bar.example.org"), RRType::A(), Zone::NXRRSET);
-    findTest(Name("*.bar.example.org"), RRType::A(), Zone::NXRRSET);
-    findTest(Name("bar.example.org"), RRType::A(), Zone::NXRRSET);
+
+    {
+        SCOPED_TRACE("Asking for the original record under wildcards");
+        findTest(Name("wild.*.foo.*.bar.example.org"), RRType::A(),
+            Zone::SUCCESS, true, rr_nested_emptywild_);
+    }
+
+    {
+        SCOPED_TRACE("Matching wildcard against empty nonterminal");
+
+        const char* names[] = {
+            "baz.foo.*.bar.example.org",
+            "baz.foo.baz.bar.example.org",
+            "*.foo.baz.bar.example.org",
+            NULL
+        };
+
+        for (const char** name(names); *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+            findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+        }
+    }
+
+    // Domains to test
+    const char* names[] = {
+        "*.foo.*.bar.example.org",
+        "foo.*.bar.example.org",
+        "*.bar.example.org",
+        "bar.example.org",
+        NULL
+    };
+
+    {
+        SCOPED_TRACE("Asking directly for A on parent nodes");
+
+        for (const char** name(names); *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+            findTest(Name(*name), RRType::A(), Zone::NXRRSET);
+        }
+    }
+
+    {
+        SCOPED_TRACE("Asking for ANY on parent nodes");
+
+        for (const char** name(names); *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+
+            RRsetList target;
+            findTest(Name(*name), RRType::ANY(), Zone::NXRRSET, true,
+                ConstRRsetPtr(), &target);
+            EXPECT_EQ(0, target.size());
+        }
+    }
 }
 
 TEST_F(MemoryZoneTest, loadBadWildcard) {