From: Mukund Sivaraman Date: Wed, 3 Oct 2012 07:55:34 +0000 (+0530) Subject: [2268] Add ZoneDataUpdater class X-Git-Tag: trac2402_base~78 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=371a652b90f43c0a2759b2be4d2fb6ef95d57ff0;p=thirdparty%2Fkea.git [2268] Add ZoneDataUpdater class --- diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am index 3bdec0d06a..d46a9078ee 100644 --- a/src/lib/datasrc/memory/Makefile.am +++ b/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 += zone_data.h zone_data.cc 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 += zone_table.h zone_table.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 EXTRA_DIST = rdata_serialization_priv.cc diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc new file mode 100644 index 0000000000..53768c8d01 --- /dev/null +++ b/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 +#include + +#include + +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( + rit->getCurrent()).typeCovered(); + for (rit->next(); !rit->isLast(); rit->next()) { + if (dynamic_cast( + 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( + 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& 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( + 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& 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 diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h new file mode 100644 index 0000000000..1e15935f67 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#include + +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: