Browse Source

[2268] Add ZoneDataUpdater class

Mukund Sivaraman 12 years ago
parent
commit
371a652b90

+ 2 - 1
src/lib/datasrc/memory/Makefile.am

@@ -16,10 +16,11 @@ libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.cc
 libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
 libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
 libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += segment_object_holder.h
 libdatasrc_memory_la_SOURCES += segment_object_holder.h
-libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 libdatasrc_memory_la_SOURCES += logger.h logger.cc
 libdatasrc_memory_la_SOURCES += logger.h logger.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
 libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
+libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc
+libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 
 
 EXTRA_DIST  = rdata_serialization_priv.cc
 EXTRA_DIST  = rdata_serialization_priv.cc

+ 341 - 0
src/lib/datasrc/memory/zone_data_updater.cc

@@ -0,0 +1,341 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/zone.h>
+
+#include <dns/rdataclass.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+void
+ZoneDataUpdater::addWildcards(const Name& name) {
+    Name wname(name);
+    const unsigned int labels(wname.getLabelCount());
+    const unsigned int origin_labels(zone_name_.getLabelCount());
+    for (unsigned int l = labels;
+         l > origin_labels;
+         --l, wname = wname.split(1))
+    {
+        if (wname.isWildcard()) {
+            // Ensure a separate level exists for the "wildcarding"
+            // name, and mark the node as "wild".
+            ZoneNode* node;
+            zone_data_.insertName(mem_sgmt_, wname.split(1), &node);
+            node->setFlag(ZoneData::WILDCARD_NODE);
+
+            // 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.
+            zone_data_.insertName(mem_sgmt_, wname, &node);
+        }
+    }
+}
+
+void
+ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
+                              const RdataSet* set) const {
+    // Ensure CNAME and other type of RR don't coexist for the same
+    // owner name except with NSEC, which is the only RR that can
+    // coexist with CNAME (and also RRSIG, which is handled separately)
+    if (rrset.getType() == RRType::CNAME()) {
+        for (const RdataSet* sp = set; sp != NULL; sp = sp->getNext()) {
+            if (sp->type != RRType::NSEC()) {
+                isc_throw(AddError,
+                          "CNAME can't be added with " << sp->type
+                          << " RRType for " << rrset.getName());
+            }
+        }
+    } else if ((rrset.getType() != RRType::NSEC()) &&
+               (RdataSet::find(set, RRType::CNAME()) != NULL))
+    {
+        isc_throw(AddError,
+                  "CNAME and " << rrset.getType() <<
+                  " can't coexist for " << rrset.getName());
+    }
+
+    // Similar with DNAME, but it must not coexist only with NS and only
+    // in non-apex domains.  RFC 2672 section 3 mentions that it is
+    // implied from it and RFC 2181.
+    if (rrset.getName() != zone_name_ &&
+        // Adding DNAME, NS already there
+        ((rrset.getType() == RRType::DNAME() &&
+          RdataSet::find(set, RRType::NS()) != NULL) ||
+         // Adding NS, DNAME already there
+         (rrset.getType() == RRType::NS() &&
+          RdataSet::find(set, RRType::DNAME()) != NULL)))
+    {
+        isc_throw(AddError, "DNAME can't coexist with NS in non-apex domain: "
+                  << rrset.getName());
+    }
+}
+
+void
+ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
+    if (!rrset) {
+        isc_throw(NullRRset, "The rrset provided is NULL");
+    }
+
+    if (rrset->getRdataCount() == 0) {
+        isc_throw(AddError,
+                  "The rrset provided is empty: "
+                  << rrset->getName() << "/" << rrset->getType());
+    }
+
+    // Check for singleton RRs. It should probably handled at a different
+    // layer in future.
+    if ((rrset->getType() == RRType::CNAME() ||
+         rrset->getType() == RRType::DNAME()) &&
+        rrset->getRdataCount() > 1)
+    {
+        // XXX: this is not only for CNAME or DNAME. 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());
+    }
+
+    // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
+    // implementation requests it be so at the moment.
+    if ((rrset->getType() == RRType::NSEC3() ||
+         rrset->getType() == RRType::NSEC3PARAM()) &&
+        (rrset->getRdataCount() > 1))
+    {
+        isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
+                  << rrset->getName() << " which isn't supported");
+    }
+
+    // For RRSIGs, check consistency of the type covered.  We know the
+    // RRset isn't empty, so the following check is safe.
+    if (rrset->getType() == RRType::RRSIG()) {
+        RdataIteratorPtr rit = rrset->getRdataIterator();
+        const RRType covered = dynamic_cast<const generic::RRSIG&>(
+            rit->getCurrent()).typeCovered();
+        for (rit->next(); !rit->isLast(); rit->next()) {
+            if (dynamic_cast<const generic::RRSIG&>(
+                     rit->getCurrent()).typeCovered() != covered)
+            {
+                isc_throw(AddError, "RRSIG contains mixed covered types: "
+                          << rrset->toText());
+            }
+        }
+    }
+
+    const NameComparisonResult compare = zone_name_.compare(rrset->getName());
+    if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
+        compare.getRelation() != NameComparisonResult::EQUAL)
+    {
+        isc_throw(OutOfZone,
+                  "The name " << rrset->getName() <<
+                  " is not contained in zone " << zone_name_);
+    }
+
+    // Some RR types do not really work well with a wildcard.  Even
+    // though the protocol specifically doesn't completely ban such
+    // usage, we refuse to load a zone containing such RR in order to
+    // keep the lookup logic simpler and more predictable.  See RFC4592
+    // and (for DNAME) RFC6672 for more technical background.  Note also
+    // that BIND 9 refuses NS at a wildcard, so in that sense we simply
+    // provide compatible behavior.
+    if (rrset->getName().isWildcard()) {
+        if (rrset->getType() == RRType::NS()) {
+            isc_throw(AddError, "Invalid NS owner name (wildcard): "
+                      << rrset->getName());
+        }
+
+        if (rrset->getType() == RRType::DNAME()) {
+            isc_throw(AddError, "Invalid DNAME owner name (wildcard): "
+                      << rrset->getName());
+        }
+    }
+
+    // Owner names of NSEC3 have special format as defined in RFC5155,
+    // and cannot be a wildcard name or must be one label longer than
+    // the zone origin.  While the RFC doesn't prohibit other forms of
+    // names, no sane zone would have such names for NSEC3.  BIND 9 also
+    // refuses NSEC3 at wildcard.
+    if (rrset->getType() == RRType::NSEC3() &&
+        (rrset->getName().isWildcard() ||
+         rrset->getName().getLabelCount() !=
+         zone_name_.getLabelCount() + 1))
+    {
+        isc_throw(AddError, "Invalid NSEC3 owner name: " <<
+                  rrset->getName() << "; zone: " << zone_name_);
+    }
+}
+
+void
+ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset,
+                          const ConstRRsetPtr rrsig) {
+    // We know rrset has exactly one RDATA
+    const generic::NSEC3& nsec3_rdata =
+        dynamic_cast<const generic::NSEC3&>(
+            rrset->getRdataIterator()->getCurrent());
+
+    NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+    if (nsec3_data == NULL) {
+        nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata);
+        zone_data_.setNSEC3Data(nsec3_data);
+        zone_data_.setSigned(true);
+    } else {
+        size_t salt_len = nsec3_data->getSaltLen();
+        const uint8_t* salt_data = nsec3_data->getSaltData();
+        const std::vector<uint8_t>& salt_data_2 = nsec3_rdata.getSalt();
+
+        if ((nsec3_rdata.getHashalg() != nsec3_data->hashalg) ||
+            (nsec3_rdata.getIterations() != nsec3_data->iterations) ||
+            (salt_data_2.size() != salt_len)) {
+            isc_throw(AddError,
+                      "NSEC3 with inconsistent parameters: " <<
+                      rrset->toText());
+        }
+
+        if ((salt_len > 0) &&
+            (std::memcmp(&salt_data_2[0], salt_data, salt_len) != 0)) {
+            isc_throw(AddError,
+                      "NSEC3 with inconsistent parameters: " <<
+                      rrset->toText());
+        }
+    }
+
+    ZoneNode* node;
+    nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
+
+    RdataSet* set = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
+    RdataSet* old_set = node->setData(set);
+    if (old_set != NULL) {
+        RdataSet::destroy(mem_sgmt_, rrclass_, old_set);
+    }
+}
+
+void
+ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
+                             const ConstRRsetPtr rrsig) {
+    if (rrset->getType() == RRType::NSEC3()) {
+        addNSEC3(rrset, rrsig);
+    } else {
+        ZoneNode* node;
+        zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
+
+        RdataSet* rdataset_head = node->getData();
+
+        // Checks related to the surrounding data.  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.
+        contextCheck(*rrset, rdataset_head);
+
+        if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
+            isc_throw(AddError,
+                      "RRset of the type already exists: "
+                      << rrset->getName() << " (type: "
+                      << rrset->getType() << ")");
+        }
+
+        RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_,
+                                              rrset, rrsig);
+        rdataset->next = rdataset_head;
+        node->setData(rdataset);
+
+        // Ok, we just put it in
+
+        // If this RRset creates a zone cut at this node, mark the node
+        // indicating the need for callback in find().
+        if (rrset->getType() == RRType::NS() &&
+            rrset->getName() != zone_name_) {
+            node->setFlag(ZoneNode::FLAG_CALLBACK);
+            // If it is DNAME, we have a callback as well here
+        } else if (rrset->getType() == RRType::DNAME()) {
+            node->setFlag(ZoneNode::FLAG_CALLBACK);
+        }
+
+        // If we've added NSEC3PARAM at zone origin, set up NSEC3
+        // specific data or check consistency with already set up
+        // parameters.
+        if (rrset->getType() == RRType::NSEC3PARAM() &&
+            rrset->getName() == zone_name_) {
+            // We know rrset has exactly one RDATA
+            const generic::NSEC3PARAM& param =
+                dynamic_cast<const generic::NSEC3PARAM&>(
+                    rrset->getRdataIterator()->getCurrent());
+
+            NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+            if (nsec3_data == NULL) {
+                nsec3_data = NSEC3Data::create(mem_sgmt_, param);
+                zone_data_.setNSEC3Data(nsec3_data);
+                zone_data_.setSigned(true);
+            } else {
+                size_t salt_len = nsec3_data->getSaltLen();
+                const uint8_t* salt_data = nsec3_data->getSaltData();
+                const std::vector<uint8_t>& salt_data_2 = param.getSalt();
+
+                if ((param.getHashalg() != nsec3_data->hashalg) ||
+                    (param.getIterations() != nsec3_data->iterations) ||
+                    (salt_data_2.size() != salt_len)) {
+                    isc_throw(AddError,
+                              "NSEC3PARAM with inconsistent parameters: "
+                              << rrset->toText());
+                }
+
+                if ((salt_len > 0) &&
+                    (std::memcmp(&salt_data_2[0],
+                                 salt_data, salt_len) != 0)) {
+                    isc_throw(AddError,
+                              "NSEC3PARAM with inconsistent parameters: "
+                              << rrset->toText());
+                }
+            }
+        } else if (rrset->getType() == RRType::NSEC()) {
+            // If it is NSEC signed zone, we mark the zone as signed
+            // (conceptually "signed" is a broader notion but our
+            // current zone finder implementation regards "signed" as
+            // "NSEC signed")
+            zone_data_.setSigned(true);
+        }
+    }
+}
+
+void
+ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
+                     const ConstRRsetPtr& sig_rrset) {
+    // Validate input.  This will cause an exception to be thrown if the
+    // input RRset is empty.
+    validate(rrset);
+    if (sig_rrset) {
+        validate(sig_rrset);
+    }
+
+    // OK, can add the RRset.
+
+    // Add wildcards possibly contained in the owner name to the domain
+    // tree.  This can only happen for the normal (non-NSEC3) tree.
+    // Note: this can throw an exception, breaking strong exception
+    // guarantee.  (see also the note for the call to contextCheck()
+    // above).
+    if (rrset->getType() != RRType::NSEC3()) {
+        addWildcards(rrset->getName());
+    }
+
+    addRdataSet(rrset, sig_rrset);
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc

+ 132 - 0
src/lib/datasrc/memory/zone_data_updater.h

@@ -0,0 +1,132 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_ZONE_DATA_UPDATER_H
+#define DATASRC_ZONE_DATA_UPDATER_H 1
+
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/rdata_serialization.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <util/memory_segment.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+class ZoneDataUpdater : boost::noncopyable {
+public:
+    ZoneDataUpdater(util::MemorySegment& mem_sgmt,
+		    isc::dns::RRClass rrclass,
+		    const isc::dns::Name& zone_name,
+		    ZoneData& zone_data) :
+       mem_sgmt_(mem_sgmt),
+       rrclass_(rrclass),
+       zone_name_(zone_name),
+       zone_data_(zone_data)
+    {}
+
+    /// The destructor.
+    ~ZoneDataUpdater()
+    {}
+
+    //@}
+
+    /// This is thrown if the provided RRset parameter is NULL.
+    struct NullRRset : public InvalidParameter {
+        NullRRset(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \brief Zone is empty exception.
+    ///
+    /// This is thrown if we have an empty zone created as a result of
+    /// load().
+    struct EmptyZone : public InvalidParameter {
+        EmptyZone(const char* file, size_t line, const char* what) :
+            InvalidParameter(file, line, what)
+        { }
+    };
+
+    /// \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)
+        { }
+    };
+
+    void add(const isc::dns::ConstRRsetPtr& rrset,
+	     const isc::dns::ConstRRsetPtr& sig_rrset);
+
+private:
+    // Add the necessary magic for any wildcard contained in 'name'
+    // (including itself) to be found in the zone.
+    //
+    // In order for wildcard matching to work correctly in the zone finder,
+    // we must ensure that a node for the wildcarding level exists in the
+    // backend ZoneTree.
+    // E.g. if the wildcard name is "*.sub.example." then we must ensure
+    // that "sub.example." exists and is marked as a wildcard level.
+    // Note: the "wildcarding level" is for the parent name of the wildcard
+    // name (such as "sub.example.").
+    //
+    // We also perform the same trick for empty wild card names possibly
+    // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
+    void addWildcards(const isc::dns::Name& name);
+
+    // Does some checks in context of the data that are already in the
+    // zone.  Currently checks for forbidden combinations of RRsets in
+    // the same domain (CNAME+anything, DNAME+NS).  If such condition is
+    // found, it throws AddError.
+    void contextCheck(const isc::dns::AbstractRRset& rrset,
+		      const RdataSet* set) const;
+
+    // Validate rrset before adding it to the zone.  If something is wrong
+    // it throws an exception.  It doesn't modify the zone, and provides
+    // the strong exception guarantee.
+    void validate(const isc::dns::ConstRRsetPtr rrset) const;
+
+    void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
+		  const isc::dns::ConstRRsetPtr rrsig);
+    void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
+		     const isc::dns::ConstRRsetPtr rrsig);
+
+    util::MemorySegment& mem_sgmt_;
+    const isc::dns::RRClass rrclass_;
+    const isc::dns::Name& zone_name_;
+    ZoneData& zone_data_;
+    RdataEncoder encoder_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_ZONE_DATA_UPDATER_H
+
+// Local Variables:
+// mode: c++
+// End: