database.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <vector>
  15. #include <datasrc/database.h>
  16. #include <datasrc/data_source.h>
  17. #include <datasrc/iterator.h>
  18. #include <exceptions/exceptions.h>
  19. #include <dns/name.h>
  20. #include <dns/rrclass.h>
  21. #include <dns/rdata.h>
  22. #include <dns/rdataclass.h>
  23. #include <datasrc/data_source.h>
  24. #include <datasrc/logger.h>
  25. #include <boost/foreach.hpp>
  26. #include <string>
  27. using namespace isc::dns;
  28. using std::string;
  29. namespace isc {
  30. namespace datasrc {
  31. DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
  32. database) :
  33. database_(database)
  34. {
  35. if (database_.get() == NULL) {
  36. isc_throw(isc::InvalidParameter,
  37. "No database provided to DatabaseClient");
  38. }
  39. }
  40. DataSourceClient::FindResult
  41. DatabaseClient::findZone(const Name& name) const {
  42. std::pair<bool, int> zone(database_->getZone(name));
  43. // Try exact first
  44. if (zone.first) {
  45. return (FindResult(result::SUCCESS,
  46. ZoneFinderPtr(new Finder(database_,
  47. zone.second, name))));
  48. }
  49. // Then super domains
  50. // Start from 1, as 0 is covered above
  51. for (size_t i(1); i < name.getLabelCount(); ++i) {
  52. isc::dns::Name superdomain(name.split(i));
  53. zone = database_->getZone(superdomain);
  54. if (zone.first) {
  55. return (FindResult(result::PARTIALMATCH,
  56. ZoneFinderPtr(new Finder(database_,
  57. zone.second,
  58. superdomain))));
  59. }
  60. }
  61. // No, really nothing
  62. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  63. }
  64. DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor>
  65. database, int zone_id,
  66. const isc::dns::Name& origin) :
  67. database_(database),
  68. zone_id_(zone_id),
  69. origin_(origin)
  70. { }
  71. namespace {
  72. // Adds the given Rdata to the given RRset
  73. // If the rrset is an empty pointer, a new one is
  74. // created with the given name, class, type and ttl
  75. // The type is checked if the rrset exists, but the
  76. // name is not.
  77. //
  78. // Then adds the given rdata to the set
  79. //
  80. // Raises a DataSourceError if the type does not
  81. // match, or if the given rdata string does not
  82. // parse correctly for the given type and class
  83. //
  84. // The DatabaseAccessor is passed to print the
  85. // database name in the log message if the TTL is
  86. // modified
  87. void addOrCreate(isc::dns::RRsetPtr& rrset,
  88. const isc::dns::Name& name,
  89. const isc::dns::RRClass& cls,
  90. const isc::dns::RRType& type,
  91. const isc::dns::RRTTL& ttl,
  92. const std::string& rdata_str,
  93. const DatabaseAccessor& db
  94. )
  95. {
  96. if (!rrset) {
  97. rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
  98. } else {
  99. // This is a check to make sure find() is not messing things up
  100. assert(type == rrset->getType());
  101. if (ttl != rrset->getTTL()) {
  102. if (ttl < rrset->getTTL()) {
  103. rrset->setTTL(ttl);
  104. }
  105. logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
  106. .arg(db.getDBName()).arg(name).arg(cls)
  107. .arg(type).arg(rrset->getTTL());
  108. }
  109. }
  110. try {
  111. rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
  112. } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
  113. // at this point, rrset may have been initialised for no reason,
  114. // and won't be used. But the caller would drop the shared_ptr
  115. // on such an error anyway, so we don't care.
  116. isc_throw(DataSourceError,
  117. "bad rdata in database for " << name << " "
  118. << type << ": " << ivrt.what());
  119. }
  120. }
  121. // This class keeps a short-lived store of RRSIG records encountered
  122. // during a call to find(). If the backend happens to return signatures
  123. // before the actual data, we might not know which signatures we will need
  124. // So if they may be relevant, we store the in this class.
  125. //
  126. // (If this class seems useful in other places, we might want to move
  127. // it to util. That would also provide an opportunity to add unit tests)
  128. class RRsigStore {
  129. public:
  130. // Adds the given signature Rdata to the store
  131. // The signature rdata MUST be of the RRSIG rdata type
  132. // (the caller must make sure of this).
  133. // NOTE: if we move this class to a public namespace,
  134. // we should add a type_covered argument, so as not
  135. // to have to do this cast here.
  136. void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
  137. const isc::dns::RRType& type_covered =
  138. static_cast<isc::dns::rdata::generic::RRSIG*>(
  139. sig_rdata.get())->typeCovered();
  140. sigs[type_covered].push_back(sig_rdata);
  141. }
  142. // If the store contains signatures for the type of the given
  143. // rrset, they are appended to it.
  144. void appendSignatures(isc::dns::RRsetPtr& rrset) const {
  145. std::map<isc::dns::RRType,
  146. std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
  147. found = sigs.find(rrset->getType());
  148. if (found != sigs.end()) {
  149. BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
  150. rrset->addRRsig(sig);
  151. }
  152. }
  153. }
  154. private:
  155. std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
  156. };
  157. }
  158. std::pair<bool, isc::dns::RRsetPtr>
  159. DatabaseClient::Finder::getRRset(const isc::dns::Name& name,
  160. const isc::dns::RRType* type,
  161. bool want_cname, bool want_dname,
  162. bool want_ns)
  163. {
  164. RRsigStore sig_store;
  165. bool records_found = false;
  166. isc::dns::RRsetPtr result_rrset;
  167. // Request the context
  168. DatabaseAccessor::IteratorContextPtr
  169. context(database_->getRecords(name, zone_id_));
  170. // It must not return NULL, that's a bug of the implementation
  171. if (context == DatabaseAccessor::IteratorContextPtr()) {
  172. isc_throw(isc::Unexpected, "Iterator context null at " +
  173. name.toText());
  174. }
  175. std::string columns[DatabaseAccessor::COLUMN_COUNT];
  176. while (context->getNext(columns, DatabaseAccessor::COLUMN_COUNT)) {
  177. if (!records_found) {
  178. records_found = true;
  179. }
  180. try {
  181. const isc::dns::RRType cur_type(columns[DatabaseAccessor::
  182. TYPE_COLUMN]);
  183. const isc::dns::RRTTL cur_ttl(columns[DatabaseAccessor::
  184. TTL_COLUMN]);
  185. // Ths sigtype column was an optimization for finding the
  186. // relevant RRSIG RRs for a lookup. Currently this column is
  187. // not used in this revised datasource implementation. We
  188. // should either start using it again, or remove it from use
  189. // completely (i.e. also remove it from the schema and the
  190. // backend implementation).
  191. // Note that because we don't use it now, we also won't notice
  192. // it if the value is wrong (i.e. if the sigtype column
  193. // contains an rrtype that is different from the actual value
  194. // of the 'type covered' field in the RRSIG Rdata).
  195. //cur_sigtype(columns[SIGTYPE_COLUMN]);
  196. // Check for delegations before checking for the right type.
  197. // This is needed to properly delegate request for the NS
  198. // record itself.
  199. //
  200. // This happens with NS only, CNAME must be alone and DNAME
  201. // is not checked in the exact queried domain.
  202. if (want_ns && cur_type == isc::dns::RRType::NS()) {
  203. if (result_rrset &&
  204. result_rrset->getType() != isc::dns::RRType::NS()) {
  205. isc_throw(DataSourceError, "NS found together with data"
  206. " in non-apex domain " + name.toText());
  207. }
  208. addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
  209. columns[DatabaseAccessor::RDATA_COLUMN],
  210. *database_);
  211. } else if (type != NULL && cur_type == *type) {
  212. if (result_rrset &&
  213. result_rrset->getType() == isc::dns::RRType::CNAME()) {
  214. isc_throw(DataSourceError, "CNAME found but it is not "
  215. "the only record for " + name.toText());
  216. } else if (result_rrset && want_ns &&
  217. result_rrset->getType() == isc::dns::RRType::NS()) {
  218. isc_throw(DataSourceError, "NS found together with data"
  219. " in non-apex domain " + name.toText());
  220. }
  221. addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
  222. columns[DatabaseAccessor::RDATA_COLUMN],
  223. *database_);
  224. } else if (want_cname && cur_type == isc::dns::RRType::CNAME()) {
  225. // There should be no other data, so result_rrset should
  226. // be empty.
  227. if (result_rrset) {
  228. isc_throw(DataSourceError, "CNAME found but it is not "
  229. "the only record for " + name.toText());
  230. }
  231. addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
  232. columns[DatabaseAccessor::RDATA_COLUMN],
  233. *database_);
  234. } else if (want_dname && cur_type == isc::dns::RRType::DNAME()) {
  235. // There should be max one RR of DNAME present
  236. if (result_rrset &&
  237. result_rrset->getType() == isc::dns::RRType::DNAME()) {
  238. isc_throw(DataSourceError, "DNAME with multiple RRs in " +
  239. name.toText());
  240. }
  241. addOrCreate(result_rrset, name, getClass(), cur_type, cur_ttl,
  242. columns[DatabaseAccessor::RDATA_COLUMN],
  243. *database_);
  244. } else if (cur_type == isc::dns::RRType::RRSIG()) {
  245. // If we get signatures before we get the actual data, we
  246. // can't know which ones to keep and which to drop...
  247. // So we keep a separate store of any signature that may be
  248. // relevant and add them to the final RRset when we are
  249. // done.
  250. // A possible optimization here is to not store them for
  251. // types we are certain we don't need
  252. sig_store.addSig(isc::dns::rdata::createRdata(cur_type,
  253. getClass(), columns[DatabaseAccessor::RDATA_COLUMN]));
  254. }
  255. } catch (const isc::dns::InvalidRRType& irt) {
  256. isc_throw(DataSourceError, "Invalid RRType in database for " <<
  257. name << ": " << columns[DatabaseAccessor::
  258. TYPE_COLUMN]);
  259. } catch (const isc::dns::InvalidRRTTL& irttl) {
  260. isc_throw(DataSourceError, "Invalid TTL in database for " <<
  261. name << ": " << columns[DatabaseAccessor::
  262. TTL_COLUMN]);
  263. } catch (const isc::dns::rdata::InvalidRdataText& ird) {
  264. isc_throw(DataSourceError, "Invalid rdata in database for " <<
  265. name << ": " << columns[DatabaseAccessor::
  266. RDATA_COLUMN]);
  267. }
  268. }
  269. if (result_rrset) {
  270. sig_store.appendSignatures(result_rrset);
  271. }
  272. return (std::pair<bool, isc::dns::RRsetPtr>(records_found, result_rrset));
  273. }
  274. ZoneFinder::FindResult
  275. DatabaseClient::Finder::find(const isc::dns::Name& name,
  276. const isc::dns::RRType& type,
  277. isc::dns::RRsetList*,
  278. const FindOptions options)
  279. {
  280. // This variable is used to determine the difference between
  281. // NXDOMAIN and NXRRSET
  282. bool records_found = false;
  283. bool glue_ok(options & FIND_GLUE_OK);
  284. isc::dns::RRsetPtr result_rrset;
  285. ZoneFinder::Result result_status = SUCCESS;
  286. std::pair<bool, isc::dns::RRsetPtr> found;
  287. logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
  288. .arg(database_->getDBName()).arg(name).arg(type);
  289. try {
  290. // First, do we have any kind of delegation (NS/DNAME) here?
  291. Name origin(getOrigin());
  292. size_t origin_label_count(origin.getLabelCount());
  293. size_t current_label_count(name.getLabelCount());
  294. // This is how many labels we remove to get origin
  295. size_t remove_labels(current_label_count - origin_label_count);
  296. // Now go trough all superdomains from origin down
  297. for (int i(remove_labels); i > 0; --i) {
  298. Name superdomain(name.split(i));
  299. // Look if there's NS or DNAME (but ignore the NS in origin)
  300. found = getRRset(superdomain, NULL, false, true,
  301. i != remove_labels && !glue_ok);
  302. if (found.second) {
  303. // We found something redirecting somewhere else
  304. // (it can be only NS or DNAME here)
  305. result_rrset = found.second;
  306. if (result_rrset->getType() == isc::dns::RRType::NS()) {
  307. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  308. DATASRC_DATABASE_FOUND_DELEGATION).
  309. arg(database_->getDBName()).arg(superdomain);
  310. result_status = DELEGATION;
  311. } else {
  312. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  313. DATASRC_DATABASE_FOUND_DNAME).
  314. arg(database_->getDBName()).arg(superdomain);
  315. result_status = DNAME;
  316. }
  317. // Don't search more
  318. break;
  319. }
  320. }
  321. if (!result_rrset) { // Only if we didn't find a redirect already
  322. // Try getting the final result and extract it
  323. // It is special if there's a CNAME or NS, DNAME is ignored here
  324. // And we don't consider the NS in origin
  325. found = getRRset(name, &type, true, false,
  326. name != origin && !glue_ok);
  327. records_found = found.first;
  328. result_rrset = found.second;
  329. if (result_rrset && name != origin && !glue_ok &&
  330. result_rrset->getType() == isc::dns::RRType::NS()) {
  331. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  332. DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
  333. arg(database_->getDBName()).arg(name);
  334. result_status = DELEGATION;
  335. } else if (result_rrset && type != isc::dns::RRType::CNAME() &&
  336. result_rrset->getType() == isc::dns::RRType::CNAME()) {
  337. result_status = CNAME;
  338. }
  339. }
  340. } catch (const DataSourceError& dse) {
  341. logger.error(DATASRC_DATABASE_FIND_ERROR)
  342. .arg(database_->getDBName()).arg(dse.what());
  343. // call cleanup and rethrow
  344. database_->resetSearch();
  345. throw;
  346. } catch (const isc::Exception& isce) {
  347. logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ISC_ERROR)
  348. .arg(database_->getDBName()).arg(isce.what());
  349. // cleanup, change it to a DataSourceError and rethrow
  350. database_->resetSearch();
  351. isc_throw(DataSourceError, isce.what());
  352. } catch (const std::exception& ex) {
  353. logger.error(DATASRC_DATABASE_FIND_UNCAUGHT_ERROR)
  354. .arg(database_->getDBName()).arg(ex.what());
  355. database_->resetSearch();
  356. throw;
  357. }
  358. if (!result_rrset) {
  359. if (records_found) {
  360. logger.debug(DBG_TRACE_DETAILED,
  361. DATASRC_DATABASE_FOUND_NXRRSET)
  362. .arg(database_->getDBName()).arg(name)
  363. .arg(getClass()).arg(type);
  364. result_status = NXRRSET;
  365. } else {
  366. logger.debug(DBG_TRACE_DETAILED,
  367. DATASRC_DATABASE_FOUND_NXDOMAIN)
  368. .arg(database_->getDBName()).arg(name)
  369. .arg(getClass()).arg(type);
  370. result_status = NXDOMAIN;
  371. }
  372. } else {
  373. logger.debug(DBG_TRACE_DETAILED,
  374. DATASRC_DATABASE_FOUND_RRSET)
  375. .arg(database_->getDBName()).arg(*result_rrset);
  376. }
  377. return (FindResult(result_status, result_rrset));
  378. }
  379. Name
  380. DatabaseClient::Finder::getOrigin() const {
  381. return (origin_);
  382. }
  383. isc::dns::RRClass
  384. DatabaseClient::Finder::getClass() const {
  385. // TODO Implement
  386. return isc::dns::RRClass::IN();
  387. }
  388. namespace {
  389. /*
  390. * This needs, beside of converting all data from textual representation, group
  391. * together rdata of the same RRsets. To do this, we hold one row of data ahead
  392. * of iteration. When we get a request to provide data, we create it from this
  393. * data and load a new one. If it is to be put to the same rrset, we add it.
  394. * Otherwise we just return what we have and keep the row as the one ahead
  395. * for next time.
  396. */
  397. class DatabaseIterator : public ZoneIterator {
  398. public:
  399. DatabaseIterator(const DatabaseAccessor::IteratorContextPtr& context,
  400. const RRClass& rrclass) :
  401. context_(context),
  402. class_(rrclass),
  403. ready_(true)
  404. {
  405. // Prepare data for the next time
  406. getData();
  407. }
  408. virtual isc::dns::ConstRRsetPtr getNextRRset() {
  409. if (!ready_) {
  410. isc_throw(isc::Unexpected, "Iterating past the zone end");
  411. }
  412. if (!data_ready_) {
  413. // At the end of zone
  414. ready_ = false;
  415. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  416. DATASRC_DATABASE_ITERATE_END);
  417. return (ConstRRsetPtr());
  418. }
  419. string name_str(name_), rtype_str(rtype_), ttl(ttl_);
  420. Name name(name_str);
  421. RRType rtype(rtype_str);
  422. RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
  423. while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
  424. if (ttl_ != ttl) {
  425. if (ttl < ttl_) {
  426. ttl_ = ttl;
  427. rrset->setTTL(RRTTL(ttl));
  428. }
  429. LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
  430. arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
  431. }
  432. rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
  433. getData();
  434. }
  435. LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
  436. arg(rrset->getName()).arg(rrset->getType());
  437. return (rrset);
  438. }
  439. private:
  440. // Load next row of data
  441. void getData() {
  442. string data[DatabaseAccessor::COLUMN_COUNT];
  443. data_ready_ = context_->getNext(data, DatabaseAccessor::COLUMN_COUNT);
  444. name_ = data[DatabaseAccessor::NAME_COLUMN];
  445. rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
  446. ttl_ = data[DatabaseAccessor::TTL_COLUMN];
  447. rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
  448. }
  449. // The context
  450. const DatabaseAccessor::IteratorContextPtr context_;
  451. // Class of the zone
  452. RRClass class_;
  453. // Status
  454. bool ready_, data_ready_;
  455. // Data of the next row
  456. string name_, rtype_, rdata_, ttl_;
  457. };
  458. }
  459. ZoneIteratorPtr
  460. DatabaseClient::getIterator(const isc::dns::Name& name) const {
  461. // Get the zone
  462. std::pair<bool, int> zone(database_->getZone(name));
  463. if (!zone.first) {
  464. // No such zone, can't continue
  465. isc_throw(DataSourceError, "Zone " + name.toText() +
  466. " can not be iterated, because it doesn't exist "
  467. "in this data source");
  468. }
  469. // Request the context
  470. DatabaseAccessor::IteratorContextPtr
  471. context(database_->getAllRecords(name, zone.second));
  472. // It must not return NULL, that's a bug of the implementation
  473. if (context == DatabaseAccessor::IteratorContextPtr()) {
  474. isc_throw(isc::Unexpected, "Iterator context null at " +
  475. name.toText());
  476. }
  477. // Create the iterator and return it
  478. // TODO: Once #1062 is merged with this, we need to get the
  479. // actual zone class from the connection, as the DatabaseClient
  480. // doesn't know it and the iterator needs it (so it wouldn't query
  481. // it each time)
  482. LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
  483. arg(name);
  484. return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
  485. }
  486. }
  487. }