database.cc 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  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 <string>
  15. #include <vector>
  16. #include <datasrc/database.h>
  17. #include <datasrc/data_source.h>
  18. #include <datasrc/iterator.h>
  19. #include <exceptions/exceptions.h>
  20. #include <dns/name.h>
  21. #include <dns/rrclass.h>
  22. #include <dns/rrttl.h>
  23. #include <dns/rrset.h>
  24. #include <dns/rdata.h>
  25. #include <dns/rdataclass.h>
  26. #include <datasrc/data_source.h>
  27. #include <datasrc/logger.h>
  28. #include <boost/foreach.hpp>
  29. using namespace isc::dns;
  30. using namespace std;
  31. using boost::shared_ptr;
  32. using namespace isc::dns::rdata;
  33. namespace isc {
  34. namespace datasrc {
  35. DatabaseClient::DatabaseClient(RRClass rrclass,
  36. boost::shared_ptr<DatabaseAccessor>
  37. accessor) :
  38. rrclass_(rrclass), accessor_(accessor)
  39. {
  40. if (!accessor_) {
  41. isc_throw(isc::InvalidParameter,
  42. "No database provided to DatabaseClient");
  43. }
  44. }
  45. DataSourceClient::FindResult
  46. DatabaseClient::findZone(const Name& name) const {
  47. std::pair<bool, int> zone(accessor_->getZone(name.toText()));
  48. // Try exact first
  49. if (zone.first) {
  50. return (FindResult(result::SUCCESS,
  51. ZoneFinderPtr(new Finder(accessor_,
  52. zone.second, name))));
  53. }
  54. // Then super domains
  55. // Start from 1, as 0 is covered above
  56. for (size_t i(1); i < name.getLabelCount(); ++i) {
  57. isc::dns::Name superdomain(name.split(i));
  58. zone = accessor_->getZone(superdomain.toText());
  59. if (zone.first) {
  60. return (FindResult(result::PARTIALMATCH,
  61. ZoneFinderPtr(new Finder(accessor_,
  62. zone.second,
  63. superdomain))));
  64. }
  65. }
  66. // No, really nothing
  67. return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
  68. }
  69. DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
  70. int zone_id, const isc::dns::Name& origin) :
  71. accessor_(accessor),
  72. zone_id_(zone_id),
  73. origin_(origin)
  74. { }
  75. namespace {
  76. // Adds the given Rdata to the given RRset
  77. // If the rrset is an empty pointer, a new one is
  78. // created with the given name, class, type and ttl
  79. // The type is checked if the rrset exists, but the
  80. // name is not.
  81. //
  82. // Then adds the given rdata to the set
  83. //
  84. // Raises a DataSourceError if the type does not
  85. // match, or if the given rdata string does not
  86. // parse correctly for the given type and class
  87. //
  88. // The DatabaseAccessor is passed to print the
  89. // database name in the log message if the TTL is
  90. // modified
  91. void addOrCreate(isc::dns::RRsetPtr& rrset,
  92. const isc::dns::Name& name,
  93. const isc::dns::RRClass& cls,
  94. const isc::dns::RRType& type,
  95. const isc::dns::RRTTL& ttl,
  96. const std::string& rdata_str,
  97. const DatabaseAccessor& db
  98. )
  99. {
  100. if (!rrset) {
  101. rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
  102. } else {
  103. // This is a check to make sure find() is not messing things up
  104. assert(type == rrset->getType());
  105. if (ttl != rrset->getTTL()) {
  106. if (ttl < rrset->getTTL()) {
  107. rrset->setTTL(ttl);
  108. }
  109. logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
  110. .arg(db.getDBName()).arg(name).arg(cls)
  111. .arg(type).arg(rrset->getTTL());
  112. }
  113. }
  114. try {
  115. rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
  116. } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
  117. // at this point, rrset may have been initialised for no reason,
  118. // and won't be used. But the caller would drop the shared_ptr
  119. // on such an error anyway, so we don't care.
  120. isc_throw(DataSourceError,
  121. "bad rdata in database for " << name << " "
  122. << type << ": " << ivrt.what());
  123. }
  124. }
  125. // This class keeps a short-lived store of RRSIG records encountered
  126. // during a call to find(). If the backend happens to return signatures
  127. // before the actual data, we might not know which signatures we will need
  128. // So if they may be relevant, we store the in this class.
  129. //
  130. // (If this class seems useful in other places, we might want to move
  131. // it to util. That would also provide an opportunity to add unit tests)
  132. class RRsigStore {
  133. public:
  134. // Adds the given signature Rdata to the store
  135. // The signature rdata MUST be of the RRSIG rdata type
  136. // (the caller must make sure of this).
  137. // NOTE: if we move this class to a public namespace,
  138. // we should add a type_covered argument, so as not
  139. // to have to do this cast here.
  140. void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
  141. const isc::dns::RRType& type_covered =
  142. static_cast<isc::dns::rdata::generic::RRSIG*>(
  143. sig_rdata.get())->typeCovered();
  144. sigs[type_covered].push_back(sig_rdata);
  145. }
  146. // If the store contains signatures for the type of the given
  147. // rrset, they are appended to it.
  148. void appendSignatures(isc::dns::RRsetPtr& rrset) const {
  149. std::map<isc::dns::RRType,
  150. std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
  151. found = sigs.find(rrset->getType());
  152. if (found != sigs.end()) {
  153. BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
  154. rrset->addRRsig(sig);
  155. }
  156. }
  157. }
  158. private:
  159. std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
  160. };
  161. }
  162. DatabaseClient::Finder::FoundRRsets
  163. DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
  164. bool check_ns, const string* construct_name)
  165. {
  166. RRsigStore sig_store;
  167. bool records_found = false;
  168. std::map<RRType, RRsetPtr> result;
  169. // Request the context
  170. DatabaseAccessor::IteratorContextPtr
  171. context(accessor_->getRecords(name, zone_id_));
  172. // It must not return NULL, that's a bug of the implementation
  173. if (!context) {
  174. isc_throw(isc::Unexpected, "Iterator context null at " + name);
  175. }
  176. std::string columns[DatabaseAccessor::COLUMN_COUNT];
  177. if (construct_name == NULL) {
  178. construct_name = &name;
  179. }
  180. const Name construct_name_object(*construct_name);
  181. bool seen_cname(false);
  182. bool seen_ds(false);
  183. bool seen_other(false);
  184. bool seen_ns(false);
  185. while (context->getNext(columns)) {
  186. // The domain is not empty
  187. records_found = true;
  188. try {
  189. const RRType cur_type(columns[DatabaseAccessor::TYPE_COLUMN]);
  190. if (cur_type == RRType::RRSIG()) {
  191. // If we get signatures before we get the actual data, we
  192. // can't know which ones to keep and which to drop...
  193. // So we keep a separate store of any signature that may be
  194. // relevant and add them to the final RRset when we are
  195. // done.
  196. // A possible optimization here is to not store them for
  197. // types we are certain we don't need
  198. sig_store.addSig(rdata::createRdata(cur_type, getClass(),
  199. columns[DatabaseAccessor::RDATA_COLUMN]));
  200. }
  201. if (types.find(cur_type) != types.end()) {
  202. // This type is requested, so put it into result
  203. const RRTTL cur_ttl(columns[DatabaseAccessor::TTL_COLUMN]);
  204. // Ths sigtype column was an optimization for finding the
  205. // relevant RRSIG RRs for a lookup. Currently this column is
  206. // not used in this revised datasource implementation. We
  207. // should either start using it again, or remove it from use
  208. // completely (i.e. also remove it from the schema and the
  209. // backend implementation).
  210. // Note that because we don't use it now, we also won't notice
  211. // it if the value is wrong (i.e. if the sigtype column
  212. // contains an rrtype that is different from the actual value
  213. // of the 'type covered' field in the RRSIG Rdata).
  214. //cur_sigtype(columns[SIGTYPE_COLUMN]);
  215. addOrCreate(result[cur_type], construct_name_object,
  216. getClass(), cur_type, cur_ttl,
  217. columns[DatabaseAccessor::RDATA_COLUMN],
  218. *accessor_);
  219. }
  220. if (cur_type == RRType::CNAME()) {
  221. seen_cname = true;
  222. } else if (cur_type == RRType::NS()) {
  223. seen_ns = true;
  224. } else if (cur_type == RRType::DS()) {
  225. seen_ds = true;
  226. } else if (cur_type != RRType::RRSIG() &&
  227. cur_type != RRType::NSEC3() &&
  228. cur_type != RRType::NSEC()) {
  229. // NSEC and RRSIG can coexist with anything, otherwise
  230. // we've seen something that can't live together with potential
  231. // CNAME or NS
  232. //
  233. // NSEC3 lives in separate namespace from everything, therefore
  234. // we just ignore it here for these checks as well.
  235. seen_other = true;
  236. }
  237. } catch (const InvalidRRType&) {
  238. isc_throw(DataSourceError, "Invalid RRType in database for " <<
  239. name << ": " << columns[DatabaseAccessor::
  240. TYPE_COLUMN]);
  241. } catch (const InvalidRRTTL&) {
  242. isc_throw(DataSourceError, "Invalid TTL in database for " <<
  243. name << ": " << columns[DatabaseAccessor::
  244. TTL_COLUMN]);
  245. } catch (const rdata::InvalidRdataText&) {
  246. isc_throw(DataSourceError, "Invalid rdata in database for " <<
  247. name << ": " << columns[DatabaseAccessor::
  248. RDATA_COLUMN]);
  249. }
  250. }
  251. if (seen_cname && (seen_other || seen_ns || seen_ds)) {
  252. isc_throw(DataSourceError, "CNAME shares domain " << name <<
  253. " with something else");
  254. }
  255. if (check_ns && seen_ns && seen_other) {
  256. isc_throw(DataSourceError, "NS shares domain " << name <<
  257. " with something else");
  258. }
  259. // Add signatures to all found RRsets
  260. for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
  261. i != result.end(); ++ i) {
  262. sig_store.appendSignatures(i->second);
  263. }
  264. return (FoundRRsets(records_found, result));
  265. }
  266. bool
  267. DatabaseClient::Finder::hasSubdomains(const std::string& name) {
  268. // Request the context
  269. DatabaseAccessor::IteratorContextPtr
  270. context(accessor_->getRecords(name, zone_id_, true));
  271. // It must not return NULL, that's a bug of the implementation
  272. if (!context) {
  273. isc_throw(isc::Unexpected, "Iterator context null at " + name);
  274. }
  275. std::string columns[DatabaseAccessor::COLUMN_COUNT];
  276. return (context->getNext(columns));
  277. }
  278. // Some manipulation with RRType sets
  279. namespace {
  280. // Bunch of functions to construct specific sets of RRTypes we will
  281. // ask from it.
  282. typedef std::set<RRType> WantedTypes;
  283. const WantedTypes&
  284. NSEC_TYPES() {
  285. static bool initialized(false);
  286. static WantedTypes result;
  287. if (!initialized) {
  288. result.insert(RRType::NSEC());
  289. initialized = true;
  290. }
  291. return (result);
  292. }
  293. const WantedTypes&
  294. DELEGATION_TYPES() {
  295. static bool initialized(false);
  296. static WantedTypes result;
  297. if (!initialized) {
  298. result.insert(RRType::DNAME());
  299. result.insert(RRType::NS());
  300. initialized = true;
  301. }
  302. return (result);
  303. }
  304. const WantedTypes&
  305. FINAL_TYPES() {
  306. static bool initialized(false);
  307. static WantedTypes result;
  308. if (!initialized) {
  309. result.insert(RRType::CNAME());
  310. result.insert(RRType::NS());
  311. result.insert(RRType::NSEC());
  312. initialized = true;
  313. }
  314. return (result);
  315. }
  316. }
  317. RRsetPtr
  318. DatabaseClient::Finder::findNSECCover(const Name& name) {
  319. try {
  320. // Which one should contain the NSEC record?
  321. const Name coverName(findPreviousName(name));
  322. // Get the record and copy it out
  323. const FoundRRsets found = getRRsets(coverName.toText(), NSEC_TYPES(),
  324. coverName != getOrigin());
  325. const FoundIterator
  326. nci(found.second.find(RRType::NSEC()));
  327. if (nci != found.second.end()) {
  328. return (nci->second);
  329. } else {
  330. // The previous doesn't contain NSEC.
  331. // Badly signed zone or a bug?
  332. // FIXME: Currently, if the zone is not signed, we could get
  333. // here. In that case we can't really throw, but for now, we can't
  334. // recognize it. So we don't throw at all, enable it once
  335. // we have a is_signed flag or something.
  336. #if 0
  337. isc_throw(DataSourceError, "No NSEC in " +
  338. coverName.toText() + ", but it was "
  339. "returned as previous - "
  340. "accessor error? Badly signed zone?");
  341. #endif
  342. }
  343. }
  344. catch (const isc::NotImplemented&) {
  345. // Well, they want DNSSEC, but there is no available.
  346. // So we don't provide anything.
  347. LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
  348. arg(accessor_->getDBName()).arg(name);
  349. }
  350. // We didn't find it, return nothing
  351. return (RRsetPtr());
  352. }
  353. ZoneFinder::FindResult
  354. DatabaseClient::Finder::find(const isc::dns::Name& name,
  355. const isc::dns::RRType& type,
  356. isc::dns::RRsetList*,
  357. const FindOptions options)
  358. {
  359. // This variable is used to determine the difference between
  360. // NXDOMAIN and NXRRSET
  361. bool records_found = false;
  362. bool glue_ok((options & FIND_GLUE_OK) != 0);
  363. const bool dnssec_data((options & FIND_DNSSEC) != 0);
  364. bool get_cover(false);
  365. isc::dns::RRsetPtr result_rrset;
  366. ZoneFinder::Result result_status = SUCCESS;
  367. FoundRRsets found;
  368. logger.debug(DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
  369. .arg(accessor_->getDBName()).arg(name).arg(type);
  370. // In case we are in GLUE_OK mode and start matching wildcards,
  371. // we can't do it under NS, so we store it here to check
  372. isc::dns::RRsetPtr first_ns;
  373. // First, do we have any kind of delegation (NS/DNAME) here?
  374. const Name origin(getOrigin());
  375. const size_t origin_label_count(origin.getLabelCount());
  376. // Number of labels in the last known non-empty domain
  377. size_t last_known(origin_label_count);
  378. const size_t current_label_count(name.getLabelCount());
  379. // This is how many labels we remove to get origin
  380. const size_t remove_labels(current_label_count - origin_label_count);
  381. // Now go trough all superdomains from origin down
  382. for (int i(remove_labels); i > 0; --i) {
  383. Name superdomain(name.split(i));
  384. // Look if there's NS or DNAME (but ignore the NS in origin)
  385. found = getRRsets(superdomain.toText(), DELEGATION_TYPES(),
  386. i != remove_labels);
  387. if (found.first) {
  388. // It contains some RRs, so it exists.
  389. last_known = superdomain.getLabelCount();
  390. const FoundIterator nsi(found.second.find(RRType::NS()));
  391. const FoundIterator dni(found.second.find(RRType::DNAME()));
  392. // In case we are in GLUE_OK mode, we want to store the
  393. // highest encountered NS (but not apex)
  394. if (glue_ok && !first_ns && i != remove_labels &&
  395. nsi != found.second.end()) {
  396. first_ns = nsi->second;
  397. } else if (!glue_ok && i != remove_labels &&
  398. nsi != found.second.end()) {
  399. // Do a NS delegation, but ignore NS in glue_ok mode. Ignore
  400. // delegation in apex
  401. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  402. DATASRC_DATABASE_FOUND_DELEGATION).
  403. arg(accessor_->getDBName()).arg(superdomain);
  404. result_rrset = nsi->second;
  405. result_status = DELEGATION;
  406. // No need to go lower, found
  407. break;
  408. } else if (dni != found.second.end()) {
  409. // Very similar with DNAME
  410. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  411. DATASRC_DATABASE_FOUND_DNAME).
  412. arg(accessor_->getDBName()).arg(superdomain);
  413. result_rrset = dni->second;
  414. result_status = DNAME;
  415. if (result_rrset->getRdataCount() != 1) {
  416. isc_throw(DataSourceError, "DNAME at " << superdomain <<
  417. " has " << result_rrset->getRdataCount() <<
  418. " rdata, 1 expected");
  419. }
  420. break;
  421. }
  422. }
  423. }
  424. if (!result_rrset) { // Only if we didn't find a redirect already
  425. // Try getting the final result and extract it
  426. // It is special if there's a CNAME or NS, DNAME is ignored here
  427. // And we don't consider the NS in origin
  428. WantedTypes final_types(FINAL_TYPES());
  429. final_types.insert(type);
  430. found = getRRsets(name.toText(), final_types, name != origin);
  431. records_found = found.first;
  432. // NS records, CNAME record and Wanted Type records
  433. const FoundIterator nsi(found.second.find(RRType::NS()));
  434. const FoundIterator cni(found.second.find(RRType::CNAME()));
  435. const FoundIterator wti(found.second.find(type));
  436. if (name != origin && !glue_ok && nsi != found.second.end()) {
  437. // There's a delegation at the exact node.
  438. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  439. DATASRC_DATABASE_FOUND_DELEGATION_EXACT).
  440. arg(accessor_->getDBName()).arg(name);
  441. result_status = DELEGATION;
  442. result_rrset = nsi->second;
  443. } else if (type != isc::dns::RRType::CNAME() &&
  444. cni != found.second.end()) {
  445. // A CNAME here
  446. result_status = CNAME;
  447. result_rrset = cni->second;
  448. if (result_rrset->getRdataCount() != 1) {
  449. isc_throw(DataSourceError, "CNAME with " <<
  450. result_rrset->getRdataCount() <<
  451. " rdata at " << name << ", expected 1");
  452. }
  453. } else if (wti != found.second.end()) {
  454. // Just get the answer
  455. result_rrset = wti->second;
  456. } else if (!records_found) {
  457. // Nothing lives here.
  458. // But check if something lives below this
  459. // domain and if so, pretend something is here as well.
  460. if (hasSubdomains(name.toText())) {
  461. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  462. DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
  463. arg(accessor_->getDBName()).arg(name);
  464. records_found = true;
  465. get_cover = dnssec_data;
  466. } else if ((options & NO_WILDCARD) != 0) {
  467. // If wildcard check is disabled, the search will ultimately
  468. // terminate with NXDOMAIN. If DNSSEC is enabled, flag that
  469. // we need to get the NSEC records to prove this.
  470. if (dnssec_data) {
  471. get_cover = true;
  472. }
  473. } else {
  474. // It's not empty non-terminal. So check for wildcards.
  475. // We remove labels one by one and look for the wildcard there.
  476. // Go up to first non-empty domain.
  477. for (size_t i(1); i <= current_label_count - last_known; ++i) {
  478. // Construct the name with *
  479. const Name superdomain(name.split(i));
  480. const string wildcard("*." + superdomain.toText());
  481. const string construct_name(name.toText());
  482. // TODO What do we do about DNAME here?
  483. // The types are the same as with original query
  484. found = getRRsets(wildcard, final_types, true,
  485. &construct_name);
  486. if (found.first) {
  487. if (first_ns) {
  488. // In case we are under NS, we don't
  489. // wildcard-match, but return delegation
  490. result_rrset = first_ns;
  491. result_status = DELEGATION;
  492. records_found = true;
  493. // We pretend to switch to non-glue_ok mode
  494. glue_ok = false;
  495. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  496. DATASRC_DATABASE_WILDCARD_CANCEL_NS).
  497. arg(accessor_->getDBName()).arg(wildcard).
  498. arg(first_ns->getName());
  499. } else if (!hasSubdomains(name.split(i - 1).toText()))
  500. {
  501. // Nothing we added as part of the * can exist
  502. // directly, as we go up only to first existing
  503. // domain, but it could be empty non-terminal. In
  504. // that case, we need to cancel the match.
  505. records_found = true;
  506. const FoundIterator
  507. cni(found.second.find(RRType::CNAME()));
  508. const FoundIterator
  509. nsi(found.second.find(RRType::NS()));
  510. const FoundIterator
  511. nci(found.second.find(RRType::NSEC()));
  512. const FoundIterator wti(found.second.find(type));
  513. if (cni != found.second.end() &&
  514. type != RRType::CNAME()) {
  515. result_rrset = cni->second;
  516. result_status = WILDCARD_CNAME;
  517. } else if (nsi != found.second.end()) {
  518. result_rrset = nsi->second;
  519. result_status = DELEGATION;
  520. } else if (wti != found.second.end()) {
  521. result_rrset = wti->second;
  522. result_status = WILDCARD;
  523. } else {
  524. // NXRRSET case in the wildcard
  525. result_status = WILDCARD_NXRRSET;
  526. if (dnssec_data &&
  527. nci != found.second.end()) {
  528. // User wants a proof the wildcard doesn't
  529. // contain it
  530. //
  531. // However, we need to get the RRset in the
  532. // name of the wildcard, not the constructed
  533. // one, so we walk it again
  534. found = getRRsets(wildcard, NSEC_TYPES(),
  535. true);
  536. result_rrset =
  537. found.second.find(RRType::NSEC())->
  538. second;
  539. }
  540. }
  541. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  542. DATASRC_DATABASE_WILDCARD).
  543. arg(accessor_->getDBName()).arg(wildcard).
  544. arg(name);
  545. } else {
  546. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  547. DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
  548. arg(accessor_->getDBName()).arg(wildcard).
  549. arg(name).arg(superdomain);
  550. }
  551. break;
  552. } else if (hasSubdomains(wildcard)) {
  553. // Empty non-terminal asterisk
  554. records_found = true;
  555. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  556. DATASRC_DATABASE_WILDCARD_EMPTY).
  557. arg(accessor_->getDBName()).arg(wildcard).
  558. arg(name);
  559. if (dnssec_data) {
  560. result_rrset = findNSECCover(Name(wildcard));
  561. if (result_rrset) {
  562. result_status = WILDCARD_NXRRSET;
  563. }
  564. }
  565. break;
  566. }
  567. }
  568. // This is the NXDOMAIN case (nothing found anywhere). If
  569. // they want DNSSEC data, try getting the NSEC record
  570. if (dnssec_data && !records_found) {
  571. get_cover = true;
  572. }
  573. }
  574. } else if (dnssec_data) {
  575. // This is the "usual" NXRRSET case
  576. // So in case they want DNSSEC, provide the NSEC
  577. // (which should be available already here)
  578. result_status = NXRRSET;
  579. const FoundIterator nci(found.second.find(RRType::NSEC()));
  580. if (nci != found.second.end()) {
  581. result_rrset = nci->second;
  582. }
  583. }
  584. }
  585. if (!result_rrset) {
  586. if (result_status == SUCCESS) {
  587. // Should we look for NSEC covering the name?
  588. if (get_cover) {
  589. result_rrset = findNSECCover(name);
  590. if (result_rrset) {
  591. result_status = NXDOMAIN;
  592. }
  593. }
  594. // Something is not here and we didn't decide yet what
  595. if (records_found) {
  596. logger.debug(DBG_TRACE_DETAILED,
  597. DATASRC_DATABASE_FOUND_NXRRSET)
  598. .arg(accessor_->getDBName()).arg(name)
  599. .arg(getClass()).arg(type);
  600. result_status = NXRRSET;
  601. } else {
  602. logger.debug(DBG_TRACE_DETAILED,
  603. DATASRC_DATABASE_FOUND_NXDOMAIN)
  604. .arg(accessor_->getDBName()).arg(name)
  605. .arg(getClass()).arg(type);
  606. result_status = NXDOMAIN;
  607. }
  608. }
  609. } else {
  610. logger.debug(DBG_TRACE_DETAILED,
  611. DATASRC_DATABASE_FOUND_RRSET)
  612. .arg(accessor_->getDBName()).arg(*result_rrset);
  613. }
  614. return (FindResult(result_status, result_rrset));
  615. }
  616. Name
  617. DatabaseClient::Finder::findPreviousName(const Name& name) const {
  618. const string str(accessor_->findPreviousName(zone_id_,
  619. name.reverse().toText()));
  620. try {
  621. return (Name(str));
  622. }
  623. /*
  624. * To avoid having the same code many times, we just catch all the
  625. * exceptions and handle them in a common code below
  626. */
  627. catch (const isc::dns::EmptyLabel&) {}
  628. catch (const isc::dns::TooLongLabel&) {}
  629. catch (const isc::dns::BadLabelType&) {}
  630. catch (const isc::dns::BadEscape&) {}
  631. catch (const isc::dns::TooLongName&) {}
  632. catch (const isc::dns::IncompleteName&) {}
  633. isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
  634. }
  635. Name
  636. DatabaseClient::Finder::getOrigin() const {
  637. return (origin_);
  638. }
  639. isc::dns::RRClass
  640. DatabaseClient::Finder::getClass() const {
  641. // TODO Implement
  642. return isc::dns::RRClass::IN();
  643. }
  644. namespace {
  645. /*
  646. * This needs, beside of converting all data from textual representation, group
  647. * together rdata of the same RRsets. To do this, we hold one row of data ahead
  648. * of iteration. When we get a request to provide data, we create it from this
  649. * data and load a new one. If it is to be put to the same rrset, we add it.
  650. * Otherwise we just return what we have and keep the row as the one ahead
  651. * for next time.
  652. */
  653. class DatabaseIterator : public ZoneIterator {
  654. public:
  655. DatabaseIterator(shared_ptr<DatabaseAccessor> accessor,
  656. const Name& zone_name,
  657. const RRClass& rrclass,
  658. bool adjust_ttl) :
  659. accessor_(accessor),
  660. class_(rrclass),
  661. ready_(true),
  662. adjust_ttl_(adjust_ttl)
  663. {
  664. // Get the zone
  665. const pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
  666. if (!zone.first) {
  667. // No such zone, can't continue
  668. isc_throw(DataSourceError, "Zone " + zone_name.toText() +
  669. " can not be iterated, because it doesn't exist "
  670. "in this data source");
  671. }
  672. // Start a separate transaction.
  673. accessor_->startTransaction();
  674. // Find the SOA of the zone (may or may not succeed). Note that
  675. // this must be done before starting the iteration context.
  676. soa_ = DatabaseClient::Finder(accessor_, zone.second, zone_name).
  677. find(zone_name, RRType::SOA(), NULL).rrset;
  678. // Request the context
  679. context_ = accessor_->getAllRecords(zone.second);
  680. // It must not return NULL, that's a bug of the implementation
  681. if (!context_) {
  682. isc_throw(isc::Unexpected, "Iterator context null at " +
  683. zone_name.toText());
  684. }
  685. // Prepare data for the next time
  686. getData();
  687. }
  688. virtual ~DatabaseIterator() {
  689. if (ready_) {
  690. accessor_->commit();
  691. }
  692. }
  693. virtual ConstRRsetPtr getSOA() const {
  694. return (soa_);
  695. }
  696. virtual isc::dns::ConstRRsetPtr getNextRRset() {
  697. if (!ready_) {
  698. isc_throw(isc::Unexpected, "Iterating past the zone end");
  699. }
  700. if (!data_ready_) {
  701. // At the end of zone
  702. accessor_->commit();
  703. ready_ = false;
  704. LOG_DEBUG(logger, DBG_TRACE_DETAILED,
  705. DATASRC_DATABASE_ITERATE_END);
  706. return (ConstRRsetPtr());
  707. }
  708. const string name_str(name_), rtype_str(rtype_), ttl(ttl_);
  709. const Name name(name_str);
  710. const RRType rtype(rtype_str);
  711. RRsetPtr rrset(new RRset(name, class_, rtype, RRTTL(ttl)));
  712. while (data_ready_ && name_ == name_str && rtype_str == rtype_) {
  713. if (adjust_ttl_) {
  714. if (ttl_ != ttl) {
  715. if (ttl < ttl_) {
  716. ttl_ = ttl;
  717. rrset->setTTL(RRTTL(ttl));
  718. }
  719. LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
  720. arg(name_).arg(class_).arg(rtype_).arg(rrset->getTTL());
  721. }
  722. } else if (ttl_ != ttl) {
  723. break;
  724. }
  725. rrset->addRdata(rdata::createRdata(rtype, class_, rdata_));
  726. getData();
  727. }
  728. LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
  729. arg(rrset->getName()).arg(rrset->getType());
  730. return (rrset);
  731. }
  732. private:
  733. // Load next row of data
  734. void getData() {
  735. string data[DatabaseAccessor::COLUMN_COUNT];
  736. data_ready_ = context_->getNext(data);
  737. name_ = data[DatabaseAccessor::NAME_COLUMN];
  738. rtype_ = data[DatabaseAccessor::TYPE_COLUMN];
  739. ttl_ = data[DatabaseAccessor::TTL_COLUMN];
  740. rdata_ = data[DatabaseAccessor::RDATA_COLUMN];
  741. }
  742. // The dedicated accessor
  743. shared_ptr<DatabaseAccessor> accessor_;
  744. // The context
  745. DatabaseAccessor::IteratorContextPtr context_;
  746. // Class of the zone
  747. const RRClass class_;
  748. // SOA of the zone, if any (it should normally exist)
  749. ConstRRsetPtr soa_;
  750. // Status
  751. bool ready_, data_ready_;
  752. // Data of the next row
  753. string name_, rtype_, rdata_, ttl_;
  754. // Whether to modify differing TTL values, or treat a different TTL as
  755. // a different RRset
  756. bool adjust_ttl_;
  757. };
  758. }
  759. ZoneIteratorPtr
  760. DatabaseClient::getIterator(const isc::dns::Name& name,
  761. bool adjust_ttl) const
  762. {
  763. ZoneIteratorPtr iterator = ZoneIteratorPtr(new DatabaseIterator(
  764. accessor_->clone(), name,
  765. rrclass_, adjust_ttl));
  766. LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
  767. arg(name);
  768. return (iterator);
  769. }
  770. //
  771. // Zone updater using some database system as the underlying data source.
  772. //
  773. class DatabaseUpdater : public ZoneUpdater {
  774. public:
  775. DatabaseUpdater(shared_ptr<DatabaseAccessor> accessor, int zone_id,
  776. const Name& zone_name, const RRClass& zone_class,
  777. bool journaling) :
  778. committed_(false), accessor_(accessor), zone_id_(zone_id),
  779. db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
  780. zone_class_(zone_class), journaling_(journaling),
  781. diff_phase_(NOT_STARTED),
  782. finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
  783. {
  784. logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
  785. .arg(zone_name_).arg(zone_class_).arg(db_name_);
  786. }
  787. virtual ~DatabaseUpdater() {
  788. if (!committed_) {
  789. try {
  790. accessor_->rollback();
  791. logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
  792. .arg(zone_name_).arg(zone_class_).arg(db_name_);
  793. } catch (const DataSourceError& e) {
  794. // We generally expect that rollback always succeeds, and
  795. // it should in fact succeed in a way we execute it. But
  796. // as the public API allows rollback() to fail and
  797. // throw, we should expect it. Obviously we cannot re-throw
  798. // it. The best we can do is to log it as a critical error.
  799. logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
  800. .arg(zone_name_).arg(zone_class_).arg(db_name_)
  801. .arg(e.what());
  802. }
  803. }
  804. logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
  805. .arg(zone_name_).arg(zone_class_).arg(db_name_);
  806. }
  807. virtual ZoneFinder& getFinder() { return (*finder_); }
  808. virtual void addRRset(const RRset& rrset);
  809. virtual void deleteRRset(const RRset& rrset);
  810. virtual void commit();
  811. private:
  812. bool committed_;
  813. shared_ptr<DatabaseAccessor> accessor_;
  814. const int zone_id_;
  815. const string db_name_;
  816. const string zone_name_;
  817. const RRClass zone_class_;
  818. const bool journaling_;
  819. // For the journals
  820. enum DiffPhase {
  821. NOT_STARTED,
  822. DELETE,
  823. ADD
  824. };
  825. DiffPhase diff_phase_;
  826. uint32_t serial_;
  827. boost::scoped_ptr<DatabaseClient::Finder> finder_;
  828. };
  829. void
  830. DatabaseUpdater::addRRset(const RRset& rrset) {
  831. if (committed_) {
  832. isc_throw(DataSourceError, "Add attempt after commit to zone: "
  833. << zone_name_ << "/" << zone_class_);
  834. }
  835. if (rrset.getClass() != zone_class_) {
  836. isc_throw(DataSourceError, "An RRset of a different class is being "
  837. << "added to " << zone_name_ << "/" << zone_class_ << ": "
  838. << rrset.toText());
  839. }
  840. if (rrset.getRRsig()) {
  841. isc_throw(DataSourceError, "An RRset with RRSIG is being added to "
  842. << zone_name_ << "/" << zone_class_ << ": "
  843. << rrset.toText());
  844. }
  845. if (rrset.getType() == RRType::SOA() && diff_phase_ == ADD &&
  846. journaling_) {
  847. isc_throw(isc::BadValue, "Another SOA added inside an add sequence");
  848. }
  849. if (rrset.getType() != RRType::SOA() && diff_phase_ != ADD &&
  850. journaling_) {
  851. isc_throw(isc::BadValue, "Adding sequence not started by SOA");
  852. }
  853. if (rrset.getType() == RRType::SOA() && diff_phase_ != DELETE &&
  854. journaling_) {
  855. isc_throw(isc::BadValue,
  856. "Adding sequence can follow only after delete");
  857. }
  858. RdataIteratorPtr it = rrset.getRdataIterator();
  859. if (it->isLast()) {
  860. isc_throw(DataSourceError, "An empty RRset is being added for "
  861. << rrset.getName() << "/" << zone_class_ << "/"
  862. << rrset.getType());
  863. }
  864. string columns[DatabaseAccessor::ADD_COLUMN_COUNT]; // initialized with ""
  865. columns[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
  866. columns[DatabaseAccessor::ADD_REV_NAME] =
  867. rrset.getName().reverse().toText();
  868. columns[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
  869. columns[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
  870. string journal[DatabaseAccessor::DIFF_PARAM_COUNT];
  871. if (journaling_) {
  872. journal[DatabaseAccessor::DIFF_NAME] =
  873. columns[DatabaseAccessor::ADD_NAME];
  874. journal[DatabaseAccessor::DIFF_TYPE] =
  875. columns[DatabaseAccessor::ADD_TYPE];
  876. journal[DatabaseAccessor::DIFF_TTL] =
  877. columns[DatabaseAccessor::ADD_TTL];
  878. diff_phase_ = ADD;
  879. if (rrset.getType() == RRType::SOA()) {
  880. serial_ =
  881. dynamic_cast<const rdata::generic::SOA&>(it->getCurrent()).
  882. getSerial();
  883. }
  884. }
  885. for (; !it->isLast(); it->next()) {
  886. if (rrset.getType() == RRType::RRSIG()) {
  887. // XXX: the current interface (based on the current sqlite3
  888. // data source schema) requires a separate "sigtype" column,
  889. // even though it won't be used in a newer implementation.
  890. // We should eventually clean up the schema design and simplify
  891. // the interface, but until then we have to conform to the schema.
  892. const generic::RRSIG& rrsig_rdata =
  893. dynamic_cast<const generic::RRSIG&>(it->getCurrent());
  894. columns[DatabaseAccessor::ADD_SIGTYPE] =
  895. rrsig_rdata.typeCovered().toText();
  896. }
  897. columns[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
  898. if (journaling_) {
  899. journal[DatabaseAccessor::DIFF_RDATA] =
  900. columns[DatabaseAccessor::ADD_RDATA];
  901. try {
  902. accessor_->addRecordDiff(zone_id_, serial_,
  903. DatabaseAccessor::DIFF_ADD, journal);
  904. }
  905. // We ignore not implemented
  906. catch (const isc::NotImplemented&) {}
  907. }
  908. accessor_->addRecordToZone(columns);
  909. }
  910. }
  911. void
  912. DatabaseUpdater::deleteRRset(const RRset& rrset) {
  913. if (committed_) {
  914. isc_throw(DataSourceError, "Delete attempt after commit on zone: "
  915. << zone_name_ << "/" << zone_class_);
  916. }
  917. if (rrset.getClass() != zone_class_) {
  918. isc_throw(DataSourceError, "An RRset of a different class is being "
  919. << "deleted from " << zone_name_ << "/" << zone_class_
  920. << ": " << rrset.toText());
  921. }
  922. if (rrset.getRRsig()) {
  923. isc_throw(DataSourceError, "An RRset with RRSIG is being deleted from "
  924. << zone_name_ << "/" << zone_class_ << ": "
  925. << rrset.toText());
  926. }
  927. if (rrset.getType() == RRType::SOA() && diff_phase_ == DELETE &&
  928. journaling_) {
  929. isc_throw(isc::BadValue,
  930. "Another SOA delete inside a delete sequence");
  931. }
  932. if (rrset.getType() != RRType::SOA() && diff_phase_ != DELETE &&
  933. journaling_) {
  934. isc_throw(isc::BadValue, "Delete sequence not started by SOA");
  935. }
  936. RdataIteratorPtr it = rrset.getRdataIterator();
  937. if (it->isLast()) {
  938. isc_throw(DataSourceError, "An empty RRset is being deleted for "
  939. << rrset.getName() << "/" << zone_class_ << "/"
  940. << rrset.getType());
  941. }
  942. string params[DatabaseAccessor::DEL_PARAM_COUNT]; // initialized with ""
  943. params[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
  944. params[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
  945. string journal[DatabaseAccessor::DIFF_PARAM_COUNT];
  946. if (journaling_) {
  947. journal[DatabaseAccessor::DIFF_NAME] =
  948. params[DatabaseAccessor::DEL_NAME];
  949. journal[DatabaseAccessor::DIFF_TYPE] =
  950. params[DatabaseAccessor::DEL_TYPE];
  951. journal[DatabaseAccessor::DIFF_TTL] = rrset.getTTL().toText();
  952. diff_phase_ = DELETE;
  953. if (rrset.getType() == RRType::SOA()) {
  954. serial_ =
  955. dynamic_cast<const rdata::generic::SOA&>(it->getCurrent()).
  956. getSerial();
  957. }
  958. }
  959. for (; !it->isLast(); it->next()) {
  960. params[DatabaseAccessor::DEL_RDATA] = it->getCurrent().toText();
  961. if (journaling_) {
  962. journal[DatabaseAccessor::DIFF_RDATA] =
  963. params[DatabaseAccessor::DEL_RDATA];
  964. try {
  965. accessor_->addRecordDiff(zone_id_, serial_,
  966. DatabaseAccessor::DIFF_DELETE,
  967. journal);
  968. }
  969. // Don't fail if the backend can't store them
  970. catch(const isc::NotImplemented&) {}
  971. }
  972. accessor_->deleteRecordInZone(params);
  973. }
  974. }
  975. void
  976. DatabaseUpdater::commit() {
  977. if (committed_) {
  978. isc_throw(DataSourceError, "Duplicate commit attempt for "
  979. << zone_name_ << "/" << zone_class_ << " on "
  980. << db_name_);
  981. }
  982. if (journaling_ && diff_phase_ == DELETE) {
  983. isc_throw(isc::Unexpected, "Update sequence not complete");
  984. }
  985. accessor_->commit();
  986. committed_ = true; // make sure the destructor won't trigger rollback
  987. // We release the accessor immediately after commit is completed so that
  988. // we don't hold the possible internal resource any longer.
  989. accessor_.reset();
  990. logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
  991. .arg(zone_name_).arg(zone_class_).arg(db_name_);
  992. }
  993. // The updater factory
  994. ZoneUpdaterPtr
  995. DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace,
  996. bool journaling) const
  997. {
  998. if (replace && journaling) {
  999. isc_throw(isc::BadValue, "Can't store journal and replace the whole "
  1000. "zone at the same time");
  1001. }
  1002. shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
  1003. const std::pair<bool, int> zone(update_accessor->startUpdateZone(
  1004. name.toText(), replace));
  1005. if (!zone.first) {
  1006. return (ZoneUpdaterPtr());
  1007. }
  1008. return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
  1009. name, rrclass_, journaling)));
  1010. }
  1011. //
  1012. // Zone journal reader using some database system as the underlying data
  1013. // source.
  1014. //
  1015. class DatabaseJournalReader : public ZoneJournalReader {
  1016. private:
  1017. // A shortcut typedef to keep the code concise.
  1018. typedef DatabaseAccessor Accessor;
  1019. public:
  1020. DatabaseJournalReader(shared_ptr<Accessor> accessor, const Name& zone,
  1021. const RRClass& rrclass, uint32_t begin,
  1022. uint32_t end) :
  1023. accessor_(accessor), rrclass_(rrclass)
  1024. {
  1025. const pair<bool, int> zoneinfo(accessor_->getZone(zone.toText()));
  1026. if (!zoneinfo.first) {
  1027. // No such zone, can't continue
  1028. isc_throw(DataSourceError, "Zone " << zone << "/"
  1029. << rrclass << " doesn't exist in database: " <<
  1030. accessor_->getDBName());
  1031. }
  1032. context_ = accessor_->getDiffs(zoneinfo.second, begin, end);
  1033. }
  1034. virtual ~DatabaseJournalReader() {}
  1035. virtual ConstRRsetPtr getNextDiff() {
  1036. // TBD: check read after completion
  1037. string data[Accessor::COLUMN_COUNT];
  1038. if (!context_->getNext(data)) {
  1039. return (ConstRRsetPtr());
  1040. }
  1041. RRsetPtr rrset(new RRset(Name(data[Accessor::NAME_COLUMN]), rrclass_,
  1042. RRType(data[Accessor::TYPE_COLUMN]),
  1043. RRTTL(data[Accessor::TTL_COLUMN])));
  1044. rrset->addRdata(rdata::createRdata(rrset->getType(), rrclass_,
  1045. data[Accessor::RDATA_COLUMN]));
  1046. return (rrset);
  1047. }
  1048. private:
  1049. shared_ptr<Accessor> accessor_;
  1050. const RRClass rrclass_;
  1051. Accessor::IteratorContextPtr context_;
  1052. };
  1053. // The JournalReader factory
  1054. ZoneJournalReaderPtr
  1055. DatabaseClient::getJournalReader(const isc::dns::Name& zone,
  1056. uint32_t begin_serial,
  1057. uint32_t end_serial) const
  1058. {
  1059. return (ZoneJournalReaderPtr(new DatabaseJournalReader(accessor_, zone,
  1060. rrclass_,
  1061. begin_serial,
  1062. end_serial)));
  1063. }
  1064. }
  1065. }