123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- // Copyright (C) 2011 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 <vector>
- #include <datasrc/database.h>
- #include <datasrc/data_source.h>
- #include <datasrc/iterator.h>
- #include <exceptions/exceptions.h>
- #include <dns/name.h>
- #include <dns/rrclass.h>
- #include <dns/rdata.h>
- #include <dns/rdataclass.h>
- #include <datasrc/data_source.h>
- #include <datasrc/logger.h>
- #include <boost/foreach.hpp>
- #include <string>
- using namespace isc::dns;
- using std::string;
- namespace isc {
- namespace datasrc {
- DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
- database) :
- database_(database)
- {
- if (database_.get() == NULL) {
- isc_throw(isc::InvalidParameter,
- "No database provided to DatabaseClient");
- }
- }
- DataSourceClient::FindResult
- DatabaseClient::findZone(const Name& name) const {
- std::pair<bool, int> zone(database_->getZone(name));
- // Try exact first
- if (zone.first) {
- return (FindResult(result::SUCCESS,
- ZoneFinderPtr(new Finder(database_,
- zone.second, name))));
- }
- // Then super domains
- // Start from 1, as 0 is covered above
- for (size_t i(1); i < name.getLabelCount(); ++i) {
- isc::dns::Name superdomain(name.split(i));
- zone = database_->getZone(superdomain);
- if (zone.first) {
- return (FindResult(result::PARTIALMATCH,
- ZoneFinderPtr(new Finder(database_,
- zone.second,
- superdomain))));
- }
- }
- // No, really nothing
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
- database, int zone_id,
- const isc::dns::Name& origin) :
- database_(database),
- zone_id_(zone_id),
- origin_(origin)
- { }
- namespace {
- // Adds the given Rdata to the given RRset
- // If the rrset is an empty pointer, a new one is
- // created with the given name, class, type and ttl
- // The type is checked if the rrset exists, but the
- // name is not.
- //
- // Then adds the given rdata to the set
- //
- // Raises a DataSourceError if the type does not
- // match, or if the given rdata string does not
- // parse correctly for the given type and class
- //
- // The DatabaseAccessor is passed to print the
- // database name in the log message if the TTL is
- // modified
- void addOrCreate(isc::dns::RRsetPtr& rrset,
- const isc::dns::Name& name,
- const isc::dns::RRClass& cls,
- const isc::dns::RRType& type,
- const isc::dns::RRTTL& ttl,
- const std::string& rdata_str,
- const DatabaseAccessor& db
- )
- {
- if (!rrset) {
- rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
- } else {
- // This is a check to make sure find() is not messing things up
- assert(type == rrset->getType());
- if (ttl != rrset->getTTL()) {
- if (ttl < rrset->getTTL()) {
- rrset->setTTL(ttl);
- }
- logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
- .arg(db.getDBName()).arg(name).arg(cls)
- .arg(type).arg(rrset->getTTL());
- }
- }
- try {
- rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
- } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
- // at this point, rrset may have been initialised for no reason,
- // and won't be used. But the caller would drop the shared_ptr
- // on such an error anyway, so we don't care.
- isc_throw(DataSourceError,
- "bad rdata in database for " << name << " "
- << type << ": " << ivrt.what());
- }
- }
- // This class keeps a short-lived store of RRSIG records encountered
- // during a call to find(). If the backend happens to return signatures
- // before the actual data, we might not know which signatures we will need
- // So if they may be relevant, we store the in this class.
- //
- // (If this class seems useful in other places, we might want to move
- // it to util. That would also provide an opportunity to add unit tests)
- class RRsigStore {
- public:
- // Adds the given signature Rdata to the store
- // The signature rdata MUST be of the RRSIG rdata type
- // (the caller must make sure of this).
- // NOTE: if we move this class to a public namespace,
- // we should add a type_covered argument, so as not
- // to have to do this cast here.
- void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
- const isc::dns::RRType& type_covered =
- static_cast<isc::dns::rdata::generic::RRSIG*>(
- sig_rdata.get())->typeCovered();
- sigs[type_covered].push_back(sig_rdata);
- }
- // If the store contains signatures for the type of the given
- // rrset, they are appended to it.
- void appendSignatures(isc::dns::RRsetPtr& rrset) const {
- std::map<isc::dns::RRType,
- std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
- found = sigs.find(rrset->getType());
- if (found != sigs.end()) {
- BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
- rrset->addRRsig(sig);
- }
- }
- }
- private:
- std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
- };
- }
- std::pair<bool, isc::dns::RRsetPtr>
- DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
- const isc::dns::RRType* type,
- bool want_cname, bool want_dname,
- bool want_ns)
- {
- RRsigStore sig_store;
- bool records_found = false;
- isc::dns::RRsetPtr result_rrset;
- // Request the context
- DatabaseAccessor::IteratorContextPtr
- context(database_->getRecords(name, zone_id_));
- // It must not return NULL, that's a bug of the implementation
- if (context == DatabaseAccessor::IteratorContextPtr()) {
- isc_throw(isc::Unexpected, "Iterator context null at " +
- name.toText());
- }
- std::string columns[DatabaseAccessor::COLUMN_COUNT];
- while (context->getNext(columns, DatabaseAccessor::COLUMN_COUNT)) {
- if (!records_found) {
- records_found = true;
- }
- try {
- const isc::dns::RRType cur_type(columns[DatabaseAccessor::
- TYPE_COLUMN]);
- const isc::dns::RRTTL cur_ttl(columns[DatabaseAccessor::
- TTL_COLUMN]);
- // Ths sigtype column was an optimization for finding the
- // relevant RRSIG RRs for a lookup. Currently this column is
- // not used in this revised datasource implementation. We
- // should either start using it again, or remove it from use
- // completely (i.e. also remove it from the schema and the
- // backend implementation).
- // Note that because we don't use it now, we also won't notice
- // it if the value is wrong (i.e. if the sigtype column
- // contains an rrtype that is different from the actual value
- // of the 'type covered' field in the RRSIG Rdata).
- //cur_sigtype(columns[SIGTYPE_COLUMN]);
- // Check for delegations before checking for the right type.
- // This is needed to properly delegate request for the NS
- // record itself.
- //
- // This happens with NS only, CNAME must be alone and DNAME
- // is not checked in the exact queried domain.
- if (want_ns && cur_type == isc::dns::RRType::NS()) {
- if (result_rrset &&
- result_rrset->getType() != isc::dns::RRType::NS()) {
- isc_throw(DataSourceError, "NS found together with data"
- " in non-apex domain " + name.toText());
- }
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
- columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
- } else if (type != NULL && cur_type == *type) {
- if (result_rrset &&
- result_rrset->getType() == isc::dns::RRType::CNAME()) {
- isc_throw(DataSourceError, "CNAME found but it is not "
- "the only record for " + name.toText());
- } else if (result_rrset && want_ns &&
- result_rrset->getType() == isc::dns::RRType::NS()) {
- isc_throw(DataSourceError, "NS found together with data"
- " in non-apex domain " + name.toText());
- }
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
- columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
- } else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
- // There should be no other data, so result_rrset should
- // be empty.
- if (result_rrset) {
- isc_throw(DataSourceError, "CNAME found but it is not "
- "the only record for " + name.toText());
- }
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
- columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
- } else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
- // There should be max one RR of DNAME present
- if (result_rrset &&
- result_rrset->getType() == isc::dns::RRType::DNAME()) {
- isc_throw(DataSourceError, "DNAME with multiple RRs in " +
- name.toText());
- }
- addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
- columns[DatabaseAccessor::RDATA_COLUMN],
- *database_);
- } else if (cur_type == isc::dns::RRType::RRSIG()) {
- // If we get signatures before we get the actual data, we
- // can't know which ones to keep and which to drop...
- // So we keep a separate store of any signature that may be
- // relevant and add them to the final RRset when we are
- // done.
- // A possible optimization here is to not store them for
- // types we are certain we don't need
- sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
- getClass(), columns[DatabaseAccessor::RDATA_COLUMN]));
- }
- } catch (const isc::dns::InvalidRRType& irt) {
- isc_throw(DataSourceError, "Invalid RRType in database for " <<
- name << ": " << columns[DatabaseAccessor::
- TYPE_COLUMN]);
- } catch (const isc::dns::InvalidRRTTL& irttl) {
- isc_throw(DataSourceError, "Invalid TTL in database for " <<
- name << ": " << columns[DatabaseAccessor::
- TTL_COLUMN]);
- } catch (const isc::dns::rdata::InvalidRdataText& ird) {
- isc_throw(DataSourceError, "Invalid rdata in database for " <<
- name << ": " << columns[DatabaseAccessor::
- RDATA_COLUMN]);
- }
- }
- if (result_rrset) {
- sig_store.appendSignatures(result_rrset);
- }
- return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
- }
- ZoneFinder::FindResult
- DatabaseClient::Finder::find(const isc::dns::Name& name,
- const isc::dns::RRType& type,
- isc::dns::RRsetList*,
- const FindOptions options)
- {
- // This variable is used to determine the difference between
- // NXDOMAIN and NXRRSET
- bool records_found = false;
- bool glue_ok(options & FIND_GLUE_OK);
- isc::dns::RRsetPtr result_rrset;
- ZoneFinder::Result result_status = SUCCESS;
- std::pair<bool, isc::dns::RRsetPtr> found;
- logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
- .arg(database_->getDBName()).arg(name).arg(type);
- try {
- // First, do we have any kind of delegation (NS/DNAME) here?
- Name origin(getOrigin());
- size_t origin_label_count(origin.getLabelCount());
- size_t current_label_count(name.getLabelCount());
- // This is how many labels we remove to get origin
- size_t remove_labels(current_label_count - origin_label_count);
- // Now go trough all superdomains from origin down
- for (int i(remove_labels); i > 0; --i) {
- Name superdomain(name.split(i));
- // Look if there's NS or DNAME (but ignore the NS in origin)
- found = getRRset(superdomain, NULL, false, true,
- i != remove_labels && !glue_ok);
- if (found.second) {
- // We found something redirecting somewhere else
- // (it can be only NS or DNAME here)
- result_rrset = found.second;
- if (result_rrset->getType() == isc::dns::RRType::NS()) {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DELEGATION).
- arg(database_->getDBName()).arg(superdomain);
- result_status = DELEGATION;
- } else {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DNAME).
- arg(database_->getDBName()).arg(superdomain);
- result_status = DNAME;
- }
- // Don't search more
- break;
- }
- }
- if (!result_rrset) { // Only if we didn't find a redirect already
- // Try getting the final result and extract it
- // It is special if there's a CNAME or NS, DNAME is ignored here
- // And we don't consider the NS in origin
- found = getRRset(name, &type, true, false,
- name != origin && !glue_ok);
- records_found = found.first;
- result_rrset = found.second;
- if (result_rrset && name != origin && !glue_ok &&
- result_rrset->getType() == isc::dns::RRType::NS()) {
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
- arg(database_->getDBName()).arg(name);
- result_status = DELEGATION;
- } else if (result_rrset && type != isc::dns::RRType::CNAME() &&
- result_rrset->getType() == isc::dns::RRType::CNAME()) {
- result_status = CNAME;
- }
- }
- } catch (const DataSourceError& dse) {
- logger.error(DATASRC_DATABASE_FIND_ERROR)
- .arg(database_->getDBName()).arg(dse.what());
- // call cleanup and rethrow
- database_->resetSearch();
- throw;
- } catch (const isc::Exception& isce) {
- logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR)
- .arg(database_->getDBName()).arg(isce.what());
- // cleanup, change it to a DataSourceError and rethrow
- database_->resetSearch();
- isc_throw(DataSourceError, isce.what());
- } catch (const std::exception& ex) {
- logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR)
- .arg(database_->getDBName()).arg(ex.what());
- database_->resetSearch();
- throw;
- }
- if (!result_rrset) {
- if (records_found) {
- logger.debug(DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_NXRRSET)
- .arg(database_->getDBName()).arg(name)
- .arg(getClass()).arg(type);
- result_status = NXRRSET;
- } else {
- logger.debug(DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_NXDOMAIN)
- .arg(database_->getDBName()).arg(name)
- .arg(getClass()).arg(type);
- result_status = NXDOMAIN;
- }
- } else {
- logger.debug(DBG_TRACE_DETAILED,
- DATASRC_DATABASE_FOUND_RRSET)
- .arg(database_->getDBName()).arg(*result_rrset);
- }
- return (FindResult(result_status, result_rrset));
- }
- Name
- DatabaseClient::Finder::getOrigin() const {
- return (origin_);
- }
- isc::dns::RRClass
- DatabaseClient::Finder::getClass() const {
- // TODO Implement
- return isc::dns::RRClass::IN();
- }
- namespace {
- /*
- * This needs, beside of converting all data from textual representation, group
- * together rdata of the same RRsets. To do this, we hold one row of data ahead
- * of iteration. When we get a request to provide data, we create it from this
- * data and load a new one. If it is to be put to the same rrset, we add it.
- * Otherwise we just return what we have and keep the row as the one ahead
- * for next time.
- */
- class DatabaseIterator : public ZoneIterator {
- public:
- DatabaseIterator(const DatabaseAccessor::IteratorContextPtr& context,
- const RRClass& rrclass) :
- context_(context),
- class_(rrclass),
- ready_(true)
- {
- // Prepare data for the next time
- getData();
- }
- virtual isc::dns::ConstRRsetPtr getNextRRset() {
- if (!ready_) {
- isc_throw(isc::Unexpected, "Iterating past the zone end");
- }
- if (!data_ready_) {
- // At the end of zone
- ready_ = false;
- LOG_DEBUG(logger, DBG_TRACE_DETAILED,
- DATASRC_DATABASE_ITERATE_END);
- return (ConstRRsetPtr());
- }
- string name_str(name_), rtype_str(rtype_), ttl(ttl_);
- Name name(name_str);
- RRType rtype(rtype_str);
- RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
- while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
- if (ttl_ != ttl) {
- if (ttl < ttl_) {
- ttl_ = ttl;
- rrset->setTTL(RRTTL(ttl));
- }
- LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
- arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
- }
- rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
- getData();
- }
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
- arg(rrset->getName()).arg(rrset->getType());
- return (rrset);
- }
- private:
- // Load next row of data
- void getData() {
- string data[DatabaseAccessor::COLUMN_COUNT];
- data_ready_ = context_->getNext(data, DatabaseAccessor::COLUMN_COUNT);
- name_ = data[DatabaseAccessor::NAME_COLUMN];
- rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
- ttl_ = data[DatabaseAccessor::TTL_COLUMN];
- rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
- }
- // The context
- const DatabaseAccessor::IteratorContextPtr context_;
- // Class of the zone
- RRClass class_;
- // Status
- bool ready_, data_ready_;
- // Data of the next row
- string name_, rtype_, rdata_, ttl_;
- };
- }
- ZoneIteratorPtr
- DatabaseClient::getIterator(const isc::dns::Name& name) const {
- // Get the zone
- std::pair<bool, int> zone(database_->getZone(name));
- if (!zone.first) {
- // No such zone, can't continue
- isc_throw(DataSourceError, "Zone " + name.toText() +
- " can not be iterated, because it doesn't exist "
- "in this data source");
- }
- // Request the context
- DatabaseAccessor::IteratorContextPtr
- context(database_->getAllRecords(name, zone.second));
- // It must not return NULL, that's a bug of the implementation
- if (context == DatabaseAccessor::IteratorContextPtr()) {
- isc_throw(isc::Unexpected, "Iterator context null at " +
- name.toText());
- }
- // Create the iterator and return it
- // TODO: Once #1062 is merged with this, we need to get the
- // actual zone class from the connection, as the DatabaseClient
- // doesn't know it and the iterator needs it (so it wouldn't query
- // it each time)
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
- arg(name);
- return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
- }
- }
- }
|