Branch data Line data Source code
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 : :
15 : : #include <string>
16 : : #include <utility>
17 : : #include <vector>
18 : :
19 : : #include <datasrc/database.h>
20 : : #include <datasrc/data_source.h>
21 : : #include <datasrc/iterator.h>
22 : :
23 : : #include <exceptions/exceptions.h>
24 : : #include <dns/name.h>
25 : : #include <dns/rrclass.h>
26 : : #include <dns/rrttl.h>
27 : : #include <dns/rrset.h>
28 : : #include <dns/rdata.h>
29 : : #include <dns/rdataclass.h>
30 : : #include <dns/nsec3hash.h>
31 : :
32 : : #include <datasrc/data_source.h>
33 : : #include <datasrc/logger.h>
34 : :
35 : : #include <boost/foreach.hpp>
36 : : #include <boost/scoped_ptr.hpp>
37 : :
38 : : using namespace isc::dns;
39 : : using namespace std;
40 : : using namespace isc::dns::rdata;
41 : : using namespace boost;
42 : :
43 : : namespace isc {
44 : : namespace datasrc {
45 : :
46 : 247 : DatabaseClient::DatabaseClient(RRClass rrclass,
47 : : boost::shared_ptr<DatabaseAccessor>
48 : : accessor) :
49 : 495 : rrclass_(rrclass), accessor_(accessor)
50 : : {
51 [ + + ]: 247 : if (!accessor_) {
52 [ + - ][ + - ]: 2 : isc_throw(isc::InvalidParameter,
53 : : "No database provided to DatabaseClient");
54 : : }
55 : 246 : }
56 : :
57 : : DataSourceClient::FindResult
58 : 383 : DatabaseClient::findZone(const Name& name) const {
59 [ + - ]: 383 : std::pair<bool, int> zone(accessor_->getZone(name.toText()));
60 : : // Try exact first
61 [ + + ]: 383 : if (zone.first) {
62 : : return (FindResult(result::SUCCESS,
63 : : ZoneFinderPtr(new Finder(accessor_,
64 [ + - ][ + - ]: 278 : zone.second, name))));
[ + - ]
65 : : }
66 : : // Then super domains
67 : : // Start from 1, as 0 is covered above
68 [ + + ]: 280 : for (size_t i(1); i < name.getLabelCount(); ++i) {
69 : 482 : isc::dns::Name superdomain(name.split(i));
70 [ + - ][ + - ]: 241 : zone = accessor_->getZone(superdomain.toText());
71 [ + + ]: 241 : if (zone.first) {
72 : : return (FindResult(result::PARTIALMATCH,
73 : : ZoneFinderPtr(new Finder(accessor_,
74 : : zone.second,
75 [ + - ][ + - ]: 66 : superdomain))));
[ + - ]
76 : : }
77 : : }
78 : : // No, really nothing
79 : : return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
80 : : }
81 : :
82 : 528 : DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
83 : : int zone_id, const isc::dns::Name& origin) :
84 : : accessor_(accessor),
85 : : zone_id_(zone_id),
86 [ + - ]: 528 : origin_(origin)
87 : 528 : { }
88 : :
89 : : namespace {
90 : : // Adds the given Rdata to the given RRset
91 : : // If the rrset is an empty pointer, a new one is
92 : : // created with the given name, class, type and ttl
93 : : // The type is checked if the rrset exists, but the
94 : : // name is not.
95 : : //
96 : : // Then adds the given rdata to the set
97 : : //
98 : : // Raises a DataSourceError if the type does not
99 : : // match, or if the given rdata string does not
100 : : // parse correctly for the given type and class
101 : : //
102 : : // The DatabaseAccessor is passed to print the
103 : : // database name in the log message if the TTL is
104 : : // modified
105 : 2331 : void addOrCreate(isc::dns::RRsetPtr& rrset,
106 : : const isc::dns::Name& name,
107 : : const isc::dns::RRClass& cls,
108 : : const isc::dns::RRType& type,
109 : : const isc::dns::RRTTL& ttl,
110 : : const std::string& rdata_str,
111 : : const DatabaseAccessor& db
112 : : )
113 : : {
114 [ + + ]: 2331 : if (!rrset) {
115 [ + - ]: 1453 : rrset.reset(new isc::dns::RRset(name, cls, type, ttl));
116 : : } else {
117 : : // This is a check to make sure find() is not messing things up
118 [ - + ]: 878 : assert(type == rrset->getType());
119 [ + + ]: 878 : if (ttl != rrset->getTTL()) {
120 [ + + ]: 58 : if (ttl < rrset->getTTL()) {
121 : 4 : rrset->setTTL(ttl);
122 : : }
123 : 58 : logger.warn(DATASRC_DATABASE_FIND_TTL_MISMATCH)
124 [ + - ][ + - ]: 58 : .arg(db.getDBName()).arg(name).arg(cls)
[ + - ]
125 [ + - ][ + - ]: 116 : .arg(type).arg(rrset->getTTL());
126 : : }
127 : : }
128 : : try {
129 [ + + ][ + - ]: 4658 : rrset->addRdata(isc::dns::rdata::createRdata(type, cls, rdata_str));
130 [ - + ]: 8 : } catch (const isc::dns::rdata::InvalidRdataText& ivrt) {
131 : : // at this point, rrset may have been initialised for no reason,
132 : : // and won't be used. But the caller would drop the shared_ptr
133 : : // on such an error anyway, so we don't care.
134 [ - + ][ - + ]: 8 : isc_throw(DataSourceError,
[ - + ][ - + ]
[ - + ][ - + ]
[ - + ][ - + ]
135 : : "bad rdata in database for " << name << " "
136 : : << type << ": " << ivrt.what());
137 : : }
138 : 2327 : }
139 : :
140 : : // This class keeps a short-lived store of RRSIG records encountered
141 : : // during a call to find(). If the backend happens to return signatures
142 : : // before the actual data, we might not know which signatures we will need
143 : : // So if they may be relevant, we store the in this class.
144 : : //
145 : : // (If this class seems useful in other places, we might want to move
146 : : // it to util. That would also provide an opportunity to add unit tests)
147 : 4342 : class RRsigStore {
148 : : public:
149 : : // Adds the given signature Rdata to the store
150 : : // The signature rdata MUST be of the RRSIG rdata type
151 : : // (the caller must make sure of this).
152 : : // NOTE: if we move this class to a public namespace,
153 : : // we should add a type_covered argument, so as not
154 : : // to have to do this cast here.
155 : : void addSig(isc::dns::rdata::RdataPtr sig_rdata) {
156 : : const isc::dns::RRType& type_covered =
157 : : static_cast<isc::dns::rdata::generic::RRSIG*>(
158 [ + - ]: 2110 : sig_rdata.get())->typeCovered();
159 : 4220 : sigs[type_covered].push_back(sig_rdata);
160 : : }
161 : :
162 : : // If the store contains signatures for the type of the given
163 : : // rrset, they are appended to it.
164 : : void appendSignatures(isc::dns::RRsetPtr& rrset) const {
165 : : std::map<isc::dns::RRType,
166 : : std::vector<isc::dns::rdata::RdataPtr> >::const_iterator
167 [ + - ]: 1431 : found = sigs.find(rrset->getType());
168 [ + + ]: 1431 : if (found != sigs.end()) {
169 [ + + ][ + - ]: 3636 : BOOST_FOREACH(isc::dns::rdata::RdataPtr sig, found->second) {
[ + - ][ + + ]
[ + + ]
170 [ + - ]: 728 : rrset->addRRsig(sig);
171 : : }
172 : : }
173 : : }
174 : :
175 : : private:
176 : : std::map<isc::dns::RRType, std::vector<isc::dns::rdata::RdataPtr> > sigs;
177 : : };
178 : : }
179 : :
180 : : DatabaseClient::Finder::FoundRRsets
181 : 2171 : DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
182 : : bool check_ns, const string* construct_name,
183 : : bool any,
184 : : DatabaseAccessor::IteratorContextPtr context)
185 : : {
186 : : RRsigStore sig_store;
187 : 2171 : bool records_found = false;
188 : : std::map<RRType, RRsetPtr> result;
189 : :
190 : : // Request the context in case we didn't get one
191 [ + + ]: 2171 : if (!context) {
192 [ + + ][ + - ]: 2155 : context = accessor_->getRecords(name, zone_id_);
193 : : }
194 : : // It must not return NULL, that's a bug of the implementation
195 [ + - ]: 2167 : if (!context) {
196 [ # # ][ # # ]: 0 : isc_throw(isc::Unexpected, "Iterator context null at " + name);
[ # # ]
197 : : }
198 : :
199 [ + + ]: 23837 : std::string columns[DatabaseAccessor::COLUMN_COUNT];
200 [ + + ]: 2167 : if (construct_name == NULL) {
201 : 1899 : construct_name = &name;
202 : : }
203 : :
204 [ + - ]: 4334 : const Name construct_name_object(*construct_name);
205 : :
206 : : bool seen_cname(false);
207 : : bool seen_ds(false);
208 : : bool seen_other(false);
209 : : bool seen_ns(false);
210 : :
211 [ + + ][ + + ]: 9353 : while (context->getNext(columns)) {
212 : : // The domain is not empty
213 : 7198 : records_found = true;
214 : :
215 : : try {
216 [ + + ]: 7198 : const RRType cur_type(columns[DatabaseAccessor::TYPE_COLUMN]);
217 : :
218 [ + + ]: 7196 : if (cur_type == RRType::RRSIG()) {
219 : : // If we get signatures before we get the actual data, we
220 : : // can't know which ones to keep and which to drop...
221 : : // So we keep a separate store of any signature that may be
222 : : // relevant and add them to the final RRset when we are
223 : : // done.
224 : : // A possible optimization here is to not store them for
225 : : // types we are certain we don't need
226 : 2112 : sig_store.addSig(rdata::createRdata(cur_type, getClass(),
227 [ + - ][ + + ]: 2112 : columns[DatabaseAccessor::RDATA_COLUMN]));
228 : : }
229 : :
230 [ + + ][ + + ]: 7194 : if (types.find(cur_type) != types.end() || any) {
[ + + ]
231 : : // This type is requested, so put it into result
232 [ + + ]: 2335 : const RRTTL cur_ttl(columns[DatabaseAccessor::TTL_COLUMN]);
233 : : // Ths sigtype column was an optimization for finding the
234 : : // relevant RRSIG RRs for a lookup. Currently this column is
235 : : // not used in this revised datasource implementation. We
236 : : // should either start using it again, or remove it from use
237 : : // completely (i.e. also remove it from the schema and the
238 : : // backend implementation).
239 : : // Note that because we don't use it now, we also won't notice
240 : : // it if the value is wrong (i.e. if the sigtype column
241 : : // contains an rrtype that is different from the actual value
242 : : // of the 'type covered' field in the RRSIG Rdata).
243 : : //cur_sigtype(columns[SIGTYPE_COLUMN]);
244 [ + - ]: 2331 : addOrCreate(result[cur_type], construct_name_object,
245 : 2331 : getClass(), cur_type, cur_ttl,
246 : : columns[DatabaseAccessor::RDATA_COLUMN],
247 [ + - ][ + + ]: 4662 : *accessor_);
248 : : }
249 : :
250 [ + + ]: 7186 : if (cur_type == RRType::CNAME()) {
251 : : seen_cname = true;
252 [ + + ]: 7162 : } else if (cur_type == RRType::NS()) {
253 : : seen_ns = true;
254 [ + + ]: 5453 : } else if (cur_type == RRType::DS()) {
255 : : seen_ds = true;
256 [ + + + + : 12051 : } else if (cur_type != RRType::RRSIG() &&
+ + ][ + + ]
257 : 3317 : cur_type != RRType::NSEC3() &&
258 : 3307 : cur_type != RRType::NSEC()) {
259 : : // NSEC and RRSIG can coexist with anything, otherwise
260 : : // we've seen something that can't live together with potential
261 : : // CNAME or NS
262 : : //
263 : : // NSEC3 lives in separate namespace from everything, therefore
264 : : // we just ignore it here for these checks as well.
265 : 9329 : seen_other = true;
266 : : }
267 : 4 : } catch (const InvalidRRType&) {
268 [ - + ][ - + ]: 4 : isc_throw(DataSourceError, "Invalid RRType in database for " <<
[ - + ][ - + ]
[ - + ][ - + ]
269 : : name << ": " << columns[DatabaseAccessor::
270 : : TYPE_COLUMN]);
271 : 8 : } catch (const InvalidRRTTL&) {
272 [ - + ][ - + ]: 8 : isc_throw(DataSourceError, "Invalid TTL in database for " <<
[ - + ][ - + ]
[ - + ][ - + ]
273 : : name << ": " << columns[DatabaseAccessor::
274 : : TTL_COLUMN]);
275 [ + + + + ]: 14 : } catch (const rdata::InvalidRdataText&) {
276 [ - + ][ - + ]: 4 : isc_throw(DataSourceError, "Invalid rdata in database for " <<
[ - + ][ - + ]
[ - + ][ - + ]
277 : : name << ": " << columns[DatabaseAccessor::
278 : : RDATA_COLUMN]);
279 : : }
280 : : }
281 [ + + ][ + + ]: 2152 : if (seen_cname && (seen_other || seen_ns || seen_ds)) {
[ + - ][ - + ]
282 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, "CNAME shares domain " << name <<
[ + - ][ + - ]
[ + - ]
283 : : " with something else");
284 : : }
285 [ + + ][ + + ]: 2148 : if (check_ns && seen_ns && seen_other) {
[ + + ]
286 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, "NS shares domain " << name <<
[ + - ][ + - ]
[ + - ]
287 : : " with something else");
288 : : }
289 : : // Add signatures to all found RRsets
290 [ + + ]: 3575 : for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
291 : 3575 : i != result.end(); ++ i) {
292 : 1431 : sig_store.appendSignatures(i->second);
293 : : }
294 [ + + ][ + + ]: 2144 : if (records_found && any) {
295 [ + - ][ + - ]: 17 : result[RRType::ANY()] = RRsetPtr();
296 : : // These will be sitting on the other RRsets.
297 : 17 : result.erase(RRType::RRSIG());
298 : : }
299 [ + + ][ + + ]: 15146 : return (FoundRRsets(records_found, result));
300 : : }
301 : :
302 : : bool
303 : 487 : DatabaseClient::Finder::hasSubdomains(const std::string& name) {
304 : : // Request the context
305 : : DatabaseAccessor::IteratorContextPtr
306 : 487 : context(accessor_->getRecords(name, zone_id_, true));
307 : : // It must not return NULL, that's a bug of the implementation
308 [ + - ]: 487 : if (!context) {
309 [ # # ][ # # ]: 0 : isc_throw(isc::Unexpected, "Iterator context null at " + name);
[ # # ]
310 : : }
311 : :
312 [ + + ]: 5357 : std::string columns[DatabaseAccessor::COLUMN_COUNT];
313 [ + - ][ + + ]: 3409 : return (context->getNext(columns));
[ # # ]
314 : : }
315 : :
316 : : // Some manipulation with RRType sets
317 : : namespace {
318 : :
319 : : // Bunch of functions to construct specific sets of RRTypes we will
320 : : // ask from it.
321 : : typedef std::set<RRType> WantedTypes;
322 : :
323 : : const WantedTypes&
324 : 16 : NSEC3_TYPES() {
325 : : static bool initialized(false);
326 [ + + ][ + - ]: 16 : static WantedTypes result;
327 : :
328 [ + + ]: 16 : if (!initialized) {
329 : 1 : result.insert(RRType::NSEC3());
330 : 1 : initialized = true;
331 : : }
332 : 16 : return (result);
333 : : }
334 : :
335 : : const WantedTypes&
336 : 115 : NSEC3PARAM_TYPES() {
337 : : static bool initialized(false);
338 [ + + ][ + - ]: 115 : static WantedTypes result;
339 : :
340 [ + + ]: 115 : if (!initialized) {
341 : 1 : result.insert(RRType::NSEC3PARAM());
342 : 1 : initialized = true;
343 : : }
344 : 115 : return (result);
345 : : }
346 : :
347 : : const WantedTypes&
348 : 101 : NSEC_TYPES() {
349 : : static bool initialized(false);
350 [ + + ][ + - ]: 101 : static WantedTypes result;
351 : :
352 [ + + ]: 101 : if (!initialized) {
353 : 1 : result.insert(RRType::NSEC());
354 : 1 : initialized = true;
355 : : }
356 : 101 : return (result);
357 : : }
358 : :
359 : : const WantedTypes&
360 : 984 : DELEGATION_TYPES() {
361 : : static bool initialized(false);
362 [ + + ][ + - ]: 984 : static WantedTypes result;
363 : :
364 [ + + ]: 984 : if (!initialized) {
365 : 4 : result.insert(RRType::DNAME());
366 : 4 : result.insert(RRType::NS());
367 : 4 : initialized = true;
368 : : }
369 : 984 : return (result);
370 : : }
371 : :
372 : : const WantedTypes&
373 : 873 : FINAL_TYPES() {
374 : : static bool initialized(false);
375 [ + + ][ + - ]: 873 : static WantedTypes result;
376 : :
377 [ + + ]: 873 : if (!initialized) {
378 : 7 : result.insert(RRType::CNAME());
379 : 7 : result.insert(RRType::NS());
380 : 7 : result.insert(RRType::NSEC());
381 : 7 : initialized = true;
382 : : }
383 : 873 : return (result);
384 : : }
385 : : }
386 : :
387 : : ZoneFinderContextPtr
388 : 64 : DatabaseClient::Finder::findAll(const isc::dns::Name& name,
389 : : std::vector<isc::dns::ConstRRsetPtr>& target,
390 : : const FindOptions options)
391 : : {
392 : : return (ZoneFinderContextPtr(new Context(*this, options,
393 : 64 : findInternal(name, RRType::ANY(),
394 : : &target, options),
395 [ + - ][ + - ]: 172 : target)));
396 : : }
397 : :
398 : : ZoneFinderContextPtr
399 : 689 : DatabaseClient::Finder::find(const isc::dns::Name& name,
400 : : const isc::dns::RRType& type,
401 : : const FindOptions options)
402 : : {
403 [ + + ]: 689 : if (type == RRType::ANY()) {
404 [ + - ]: 4 : isc_throw(isc::Unexpected, "Use findAll to answer ANY");
405 : : }
406 : : return (ZoneFinderContextPtr(new Context(*this, options,
407 : : findInternal(name, type, NULL,
408 [ + - ][ + - ]: 1973 : options))));
409 : : }
410 : :
411 : : DatabaseClient::Finder::DelegationSearchResult
412 : 728 : DatabaseClient::Finder::findDelegationPoint(const isc::dns::Name& name,
413 : : const FindOptions options)
414 : : {
415 : : // Result of search
416 : : isc::dns::ConstRRsetPtr result_rrset;
417 : 728 : ZoneFinder::Result result_status = SUCCESS;
418 : :
419 : : // Are we searching for glue?
420 : 728 : const bool glue_ok = ((options & FIND_GLUE_OK) != 0);
421 : :
422 : : // This next declaration is an optimisation. When we search the database
423 : : // for glue records, we generally ignore delegations. (This allows for
424 : : // the case where e.g. the delegation to zone example.com refers to
425 : : // nameservers within the zone, e.g. ns1.example.com. When conducting the
426 : : // search for ns1.example.com, we have to search past the NS records at
427 : : // example.com.)
428 : : //
429 : : // The one case where this is forbidden is when we search past the zone
430 : : // cut but the match we find for the glue is a wildcard match. In that
431 : : // case, we return the delegation instead (see RFC 1034, section 4.3.3).
432 : : // To save a new search, we record the location of the delegation cut when
433 : : // we encounter it here.
434 : : isc::dns::ConstRRsetPtr first_ns;
435 : :
436 : : // We want to search from the apex down. We are given the full domain
437 : : // name so we have to do some manipulation to ensure that when we start
438 : : // checking superdomains, we start from the the domain name of the zone
439 : : // (e.g. if the name is b.a.example.com. and we are in the example.com.
440 : : // zone, we check example.com., a.example.com. and b.a.example.com. We
441 : : // don't need to check com. or .).
442 : : //
443 : : // Set the number of labels in the origin (i.e. apex of the zone) and in
444 : : // the last known non-empty domain (which, at this point, is the origin).
445 [ + - ]: 728 : const size_t origin_label_count = getOrigin().getLabelCount();
446 : 728 : size_t last_known = origin_label_count;
447 : :
448 : : // Set how many labels we remove to get origin: this is the number of
449 : : // labels we have to process in our search.
450 : 728 : const size_t remove_labels = name.getLabelCount() - origin_label_count;
451 : :
452 : : // Go through all superdomains from the origin down searching for nodes
453 : : // that indicate a delegation (.e. NS or DNAME).
454 [ + + ]: 1671 : for (int i = remove_labels; i > 0; --i) {
455 [ + - ]: 1968 : const Name superdomain(name.split(i));
456 : :
457 : : // Note if this is the origin. (We don't count NS records at the origin
458 : : // as a delegation so this controls whether NS RRs are included in
459 : : // the results of some searches.)
460 : 984 : const bool not_origin = (i != remove_labels);
461 : :
462 : : // Look if there's NS or DNAME at this point of the tree, but ignore
463 : : // the NS RRs at the apex of the zone.
464 : 984 : const FoundRRsets found = getRRsets(superdomain.toText(),
465 [ + - ][ + - ]: 1968 : DELEGATION_TYPES(), not_origin);
[ + - ][ + - ]
[ + - ]
466 [ + + ]: 984 : if (found.first) {
467 : : // This node contains either NS or DNAME RRs so it does exist.
468 : 640 : const FoundIterator nsi(found.second.find(RRType::NS()));
469 : 640 : const FoundIterator dni(found.second.find(RRType::DNAME()));
470 : :
471 : : // An optimisation. We know that there is an exact match for
472 : : // something at this point in the tree so remember it. If we have
473 : : // to do a wildcard search, as we search upwards through the tree
474 : : // we don't need to pass this point, which is an exact match for
475 : : // the domain name.
476 : 640 : last_known = superdomain.getLabelCount();
477 : :
478 [ + + ][ + - ]: 640 : if (glue_ok && !first_ns && not_origin &&
[ + + ][ + + ]
[ + + ]
479 : 26 : nsi != found.second.end()) {
480 : : // If we are searching for glue ("glue OK" mode), store the
481 : : // highest NS record that we find that is not the apex. This
482 : : // is another optimisation for later, where we need the
483 : : // information if the domain we are looking for matches through
484 : : // a wildcard.
485 [ + - ]: 20 : first_ns = nsi->second;
486 : :
487 [ + + ][ + + ]: 620 : } else if (!glue_ok && not_origin && nsi != found.second.end()) {
[ + + ][ + + ]
488 : : // Not searching for glue and we have found an NS RRset that is
489 : : // not at the apex. We have found a delegation - return that
490 : : // fact, there is no need to search further down the tree.
491 [ + - ][ + - ]: 48 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
492 : : DATASRC_DATABASE_FOUND_DELEGATION).
493 [ + - ][ + - ]: 24 : arg(accessor_->getDBName()).arg(superdomain);
[ + - ][ + - ]
494 [ + - ]: 24 : result_rrset = nsi->second;
495 : : result_status = DELEGATION;
496 : : break;
497 : :
498 [ + + ]: 596 : } else if (dni != found.second.end()) {
499 : : // We have found a DNAME so again stop searching down the tree
500 : : // and return the information.
501 [ + - ][ + - ]: 34 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
502 : : DATASRC_DATABASE_FOUND_DNAME).
503 [ + - ][ + - ]: 17 : arg(accessor_->getDBName()).arg(superdomain);
[ + - ][ + - ]
504 [ + - ]: 17 : result_rrset = dni->second;
505 : 17 : result_status = DNAME;
506 [ + - ][ + + ]: 17 : if (result_rrset->getRdataCount() != 1) {
507 [ + - ][ + - ]: 6 : isc_throw(DataSourceError, "DNAME at " << superdomain <<
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
508 : : " has " << result_rrset->getRdataCount() <<
509 : : " rdata, 1 expected");
510 : : }
511 : : break;
512 : : }
513 : : }
514 : : }
515 : : return (DelegationSearchResult(result_status, result_rrset, first_ns,
516 : 726 : last_known));
517 : : }
518 : :
519 : : // This method is called when we have not found an exact match and when we
520 : : // know that the name is not an empty non-terminal. So the only way that
521 : : // the name can match something in the zone is through a wildcard match.
522 : : //
523 : : // During an earlier stage in the search for this name, we made a record of
524 : : // the lowest superdomain for which we know an RR exists. (Note the "we
525 : : // know" qualification - there may be lower superdomains (ones with more
526 : : // labels) that hold an RR, but as we weren't searching for them, we don't
527 : : // know about them.)
528 : : //
529 : : // In the search for a wildcard match (which starts at the given domain
530 : : // name and goes up the tree to successive superdomains), this is the level
531 : : // at which we can stop - there can't be a wildcard at or beyond that
532 : : // point.
533 : : //
534 : : // At each level that can stop the search, we should consider several cases:
535 : : //
536 : : // - If we found a wildcard match for a glue record below a
537 : : // delegation point, we don't return the match,
538 : : // instead we return the delegation. (Note that if we didn't
539 : : // a wildcard match at all, we would return NXDOMAIN, not the
540 : : // the delegation.)
541 : : //
542 : : // - If we found a wildcard match and we are sure that the match
543 : : // is not an empty non-terminal, return the result taking into account CNAME,
544 : : // on a zone cut, and NXRRSET.
545 : : // (E.g. searching for a match
546 : : // for c.b.a.example.com, we found that b.a.example.com did
547 : : // not exist but that *.a.example.com. did. Checking
548 : : // b.a.example.com revealed no subdomains, so we can use the
549 : : // wilcard match we found.)
550 : : //
551 : : // - If we found a more specified match, the wildcard search
552 : : // is canceled, resulting in NXDOMAIN. (E.g. searching for a match
553 : : // for c.b.a.example.com, we found that b.a.example.com did
554 : : // not exist but that *.a.example.com. did. Checking
555 : : // b.a.example.com found subdomains. So b.example.com is
556 : : // an empty non-terminal and so should not be returned in
557 : : // the wildcard matching process. In other words,
558 : : // b.example.com does exist in the DNS space, it just doesn't
559 : : // have any RRs associated with it.)
560 : : //
561 : : // - If we found a match, but it is an empty non-terminal asterisk (E.g.#
562 : : // subdomain.*.example.com. is present, but there is nothing at
563 : : // *.example.com.), return an NXRRSET indication;
564 : : // the wildcard exists in the DNS space, there's just nothing
565 : : // associated with it. If DNSSEC data is required, return the
566 : : // covering NSEC record.
567 : : //
568 : : // If none of the above applies in any level, the search fails with NXDOMAIN.
569 : : ZoneFinder::ResultContext
570 : 186 : DatabaseClient::Finder::findWildcardMatch(
571 : : const Name& name, const RRType& type, const FindOptions options,
572 : : const DelegationSearchResult& dresult, vector<ConstRRsetPtr>* target,
573 : : FindDNSSECContext& dnssec_ctx)
574 : : {
575 : : // Note that during the search we are going to search not only for the
576 : : // requested type, but also for types that indicate a delegation -
577 : : // NS and DNAME.
578 : 372 : WantedTypes final_types(FINAL_TYPES());
579 [ + - ]: 186 : final_types.insert(type);
580 : :
581 : 186 : const size_t remove_labels = name.getLabelCount() - dresult.last_known;
582 [ + + ]: 368 : for (size_t i = 1; i <= remove_labels; ++i) {
583 : :
584 : : // Strip off the left-more label(s) in the name and replace with a "*".
585 [ + - ]: 536 : const Name superdomain(name.split(i));
586 [ + - ][ + - ]: 536 : const string wildcard("*." + superdomain.toText());
587 [ + - ]: 536 : const string construct_name(name.toText());
588 : :
589 : : // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
590 : : // RFC 4592 section 4.4).
591 : : // Search for a match. The types are the same as with original query.
592 : : FoundRRsets found = getRRsets(wildcard, final_types, true,
593 [ + - ][ + - ]: 536 : &construct_name, type == RRType::ANY());
[ + - ]
594 [ + + ]: 268 : if (found.first) {
595 : : // Found something - but what?
596 : :
597 [ + + ]: 52 : if (dresult.first_ns) {
598 : : // About to use first_ns. The only way this can be set is if
599 : : // we are searching for glue, so do a sanity check.
600 [ - + ]: 2 : if ((options & FIND_GLUE_OK) == 0) {
601 [ # # ][ # # ]: 0 : isc_throw(Unexpected, "Inconsistent conditions during "
[ # # ][ # # ]
[ # # ]
602 : : "cancel of wilcard search for " <<
603 : : name.toText() << ": find_ns non-null when not "
604 : : "processing glue request");
605 : : }
606 : :
607 : : // Wildcard match for a glue below a delegation point
608 [ + - ][ + - ]: 4 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
609 : : DATASRC_DATABASE_WILDCARD_CANCEL_NS).
610 [ + - ][ + - ]: 2 : arg(accessor_->getDBName()).arg(wildcard).
[ + - ][ + - ]
611 [ + - ][ + - ]: 4 : arg(dresult.first_ns->getName());
612 : 2 : return (ResultContext(DELEGATION, dresult.first_ns));
613 [ + - ][ + - ]: 50 : } else if (!hasSubdomains(name.split(i - 1).toText())) {
[ + - ][ + + ]
614 : : // The wildcard match is the best one, find the final result
615 : : // at it. Note that wildcard should never be the zone origin.
616 : : return (findOnNameResult(name, type, options, false, found,
617 [ + - ]: 48 : &wildcard, target, dnssec_ctx));
618 : : } else {
619 : :
620 : : // more specified match found, cancel wildcard match
621 [ + - ][ + - ]: 4 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
622 : : DATASRC_DATABASE_WILDCARD_CANCEL_SUB).
623 [ + - ][ + - ]: 2 : arg(accessor_->getDBName()).arg(wildcard).
[ + - ][ + - ]
624 [ + - ][ + - ]: 2 : arg(name).arg(superdomain);
625 : : return (ResultContext(NXDOMAIN, ConstRRsetPtr()));
626 : : }
627 : :
628 [ + - ][ + + ]: 216 : } else if (hasSubdomains(wildcard)) {
629 : : // an empty non-terminal asterisk
630 [ + - ][ + - ]: 68 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
631 : : DATASRC_DATABASE_WILDCARD_EMPTY).
632 [ + - ][ + - ]: 34 : arg(accessor_->getDBName()).arg(wildcard).arg(name);
[ + - ][ + - ]
[ + - ]
633 : : const FindResultFlags flags = (RESULT_WILDCARD |
634 [ + - ]: 34 : dnssec_ctx.getResultFlags());
635 : : return (ResultContext(NXRRSET,
636 : 34 : dnssec_ctx.getDNSSECRRset(Name(wildcard),
637 [ + - ][ + - ]: 34 : true), flags));
638 : : }
639 : : }
640 : :
641 : : // Nothing found at any level.
642 : : return (ResultContext(NXDOMAIN, ConstRRsetPtr()));
643 : : }
644 : :
645 : : ZoneFinder::ResultContext
646 : 485 : DatabaseClient::Finder::logAndCreateResult(
647 : : const Name& name, const string* wildname, const RRType& type,
648 : : ZoneFinder::Result code, ConstRRsetPtr rrset,
649 : : const isc::log::MessageID& log_id, FindResultFlags flags) const
650 : : {
651 [ + + ]: 485 : if (rrset) {
652 [ + + ]: 406 : if (wildname == NULL) {
653 [ + - ]: 772 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
654 [ + - ][ + - ]: 386 : arg(accessor_->getDBName()).arg(name).arg(type).
[ + - ]
655 [ + - ][ + - ]: 772 : arg(getClass()).arg(*rrset);
656 : : } else {
657 [ + - ]: 40 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
658 [ + - ][ + - ]: 20 : arg(accessor_->getDBName()).arg(name).arg(type).
[ + - ]
659 [ + - ][ + - ]: 40 : arg(getClass()).arg(*wildname).arg(*rrset);
[ + - ]
660 : : }
661 : : } else {
662 [ + + ]: 79 : if (wildname == NULL) {
663 [ + - ]: 110 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
664 [ + - ][ + - ]: 55 : arg(accessor_->getDBName()).arg(name).arg(type).
[ + - ]
665 [ + - ]: 110 : arg(getClass());
666 : : } else {
667 [ + - ]: 48 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, log_id).
668 [ + - ][ + - ]: 24 : arg(accessor_->getDBName()).arg(name).arg(type).
[ + - ]
669 [ + - ][ + - ]: 48 : arg(getClass()).arg(*wildname);
670 : : }
671 : : }
672 : 485 : return (ResultContext(code, rrset, flags));
673 : : }
674 : :
675 : 660 : DatabaseClient::Finder::FindDNSSECContext::FindDNSSECContext(
676 : : DatabaseClient::Finder& finder,
677 : : const FindOptions options) :
678 : : finder_(finder),
679 : : need_dnssec_((options & FIND_DNSSEC) != 0),
680 : : is_nsec3_(false),
681 : : is_nsec_(false),
682 : 660 : probed_(false)
683 : 660 : {}
684 : :
685 : : void
686 : 273 : DatabaseClient::Finder::FindDNSSECContext::probe() {
687 [ + - ]: 273 : if (!probed_) {
688 : 273 : probed_ = true;
689 [ + + ]: 273 : if (need_dnssec_) {
690 : : // If an NSEC3PARAM RR exists at the zone apex, it's quite likely
691 : : // that the zone is signed with NSEC3. (If not the zone is more
692 : : // or less broken, but it's caller's responsibility how to handle
693 : : // such cases).
694 [ + - ]: 214 : const string origin = finder_.getOrigin().toText();
695 : : const FoundRRsets nsec3_found =
696 [ + - ][ + - ]: 214 : finder_.getRRsets(origin, NSEC3PARAM_TYPES(), false);
[ + - ]
697 : : const FoundIterator nfi=
698 : 107 : nsec3_found.second.find(RRType::NSEC3PARAM());
699 : 214 : is_nsec3_ = (nfi != nsec3_found.second.end());
700 : :
701 : : // Likewise for NSEC, depending on the apex has an NSEC RR.
702 : : // If we know the zone is NSEC3-signed, however, we don't bother
703 : : // to check that. This is aligned with the transition guideline
704 : : // described in Section 10.4 of RFC 5155.
705 [ + + ]: 107 : if (!is_nsec3_) {
706 : : const FoundRRsets nsec_found =
707 [ + - ]: 134 : finder_.getRRsets(origin, NSEC_TYPES(), false);
[ + - + - ]
708 : : const FoundIterator nfi =
709 : 67 : nsec_found.second.find(RRType::NSEC());
710 : 134 : is_nsec_ = (nfi != nsec_found.second.end());
711 : : }
712 : : }
713 : : }
714 : 273 : }
715 : :
716 : : bool
717 : 283 : DatabaseClient::Finder::FindDNSSECContext::isNSEC3() {
718 [ + + ]: 283 : if (!probed_) {
719 : 219 : probe();
720 : : }
721 : 283 : return (is_nsec3_);
722 : : }
723 : :
724 : : bool
725 : 482 : DatabaseClient::Finder::FindDNSSECContext::isNSEC() {
726 [ + + ]: 482 : if (!probed_) {
727 : 54 : probe();
728 : : }
729 : 482 : return (is_nsec_);
730 : : }
731 : :
732 : : isc::dns::ConstRRsetPtr
733 : 54 : DatabaseClient::Finder::FindDNSSECContext::getDNSSECRRset(
734 : : const FoundRRsets& found_set)
735 : : {
736 [ + + ]: 54 : if (!isNSEC()) {
737 : : return (ConstRRsetPtr());
738 : : }
739 : :
740 : 4 : const FoundIterator nci = found_set.second.find(RRType::NSEC());
741 [ + - ]: 4 : if (nci != found_set.second.end()) {
742 : 4 : return (nci->second);
743 : : } else {
744 : : return (ConstRRsetPtr());
745 : : }
746 : : }
747 : :
748 : : isc::dns::ConstRRsetPtr
749 : 189 : DatabaseClient::Finder::FindDNSSECContext::getDNSSECRRset(const Name &name,
750 : : bool covering)
751 : : {
752 [ + + ]: 189 : if (!isNSEC()) {
753 : : return (ConstRRsetPtr());
754 : : }
755 : :
756 : : try {
757 : : const Name& nsec_name =
758 [ + + ][ + + ]: 70 : covering ? finder_.findPreviousName(name) : name;
[ + - ]
759 [ + - ]: 68 : const bool need_nscheck = (nsec_name != finder_.getOrigin());
760 : 34 : const FoundRRsets found = finder_.getRRsets(nsec_name.toText(),
761 [ + - ]: 34 : NSEC_TYPES(),
762 [ + - ][ + - ]: 68 : need_nscheck);
[ + - ][ # # ]
763 : 34 : const FoundIterator nci = found.second.find(RRType::NSEC());
764 [ + - ]: 34 : if (nci != found.second.end()) {
765 : 34 : return (nci->second);
766 : : }
767 [ - + ]: 4 : } catch (const isc::NotImplemented&) {
768 : : // This happens when the underlying database accessor doesn't support
769 : : // findPreviousName() (it probably doesn't support DNSSEC at all) but
770 : : // there is somehow an NSEC RR at the zone apex. We log the fact but
771 : : // otherwise let the caller decide what to do (so, for example, a
772 : : // higher level query processing won't completely fail but can return
773 : : // anything it can get).
774 [ - + ][ + - ]: 4 : LOG_INFO(logger, DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED).
[ - + ]
775 [ - + ][ - + ]: 2 : arg(finder_.accessor_->getDBName()).arg(name);
[ - + ][ - + ]
776 : : }
777 : : return (ConstRRsetPtr());
778 : : }
779 : :
780 : : ZoneFinder::FindResultFlags
781 : 283 : DatabaseClient::Finder::FindDNSSECContext::getResultFlags() {
782 [ + + ]: 283 : if (isNSEC3()) {
783 : : return (RESULT_NSEC3_SIGNED);
784 [ + + ]: 239 : } else if (isNSEC()) {
785 : : return (RESULT_NSEC_SIGNED);
786 : : }
787 : 283 : return (RESULT_DEFAULT);
788 : : }
789 : :
790 : : ZoneFinder::ResultContext
791 : 487 : DatabaseClient::Finder::findOnNameResult(const Name& name,
792 : : const RRType& type,
793 : : const FindOptions options,
794 : : const bool is_origin,
795 : : const FoundRRsets& found,
796 : : const string* wildname,
797 : : std::vector<isc::dns::ConstRRsetPtr>*
798 : : target, FindDNSSECContext& dnssec_ctx)
799 : : {
800 : 487 : const bool wild = (wildname != NULL);
801 : : // For wildcard case with DNSSEC required, the caller would need to
802 : : // know whether it's NSEC or NSEC3 signed. getResultFlags returns
803 : : // appropriate flag based on the query context and zone status.
804 : : const FindResultFlags flags =
805 [ + + ]: 487 : wild ? (RESULT_WILDCARD | dnssec_ctx.getResultFlags()) : RESULT_DEFAULT;
806 : :
807 : : // Get iterators for the different types of records we are interested in -
808 : : // CNAME, NS and Wanted types.
809 : 487 : const FoundIterator nsi(found.second.find(RRType::NS()));
810 : 487 : const FoundIterator cni(found.second.find(RRType::CNAME()));
811 : 487 : const FoundIterator wti(found.second.find(type));
812 : :
813 [ + + ][ + + ]: 487 : if (!is_origin && (options & FIND_GLUE_OK) == 0 &&
[ + + ][ + + ]
814 : 626 : nsi != found.second.end()) {
815 : : // A NS RRset was found at the domain we were searching for. As it is
816 : : // not at the origin of the zone, it is a delegation and indicates that
817 : : // this zone is not authoritative for the data. Just return the
818 : : // delegation information.
819 : : return (logAndCreateResult(name, wildname, type, DELEGATION,
820 : : nsi->second,
821 : : wild ? DATASRC_DATABASE_WILDCARD_NS :
822 : : DATASRC_DATABASE_FOUND_DELEGATION_EXACT,
823 [ + + ][ + - ]: 12 : flags));
824 : :
825 [ + + ][ + + ]: 481 : } else if (type != RRType::CNAME() && cni != found.second.end()) {
[ + + ]
826 : : // We are not searching for a CNAME but nevertheless we have found one
827 : : // at the name we are searching so we return it. (The caller may
828 : : // want to continue the lookup by replacing the query name with the
829 : : // canonical name and the original RR type.) First though, do a sanity
830 : : // check to ensure that there is only one RR in the CNAME RRset.
831 [ + + ]: 16 : if (cni->second->getRdataCount() != 1) {
832 [ + - ][ + - ]: 6 : isc_throw(DataSourceError, "CNAME with " <<
[ + - ][ + - ]
[ + - ][ + - ]
833 : : cni->second->getRdataCount() << " rdata at " << name <<
834 : : ", expected 1");
835 : : }
836 : : return (logAndCreateResult(name, wildname, type, CNAME, cni->second,
837 : : wild ? DATASRC_DATABASE_WILDCARD_CNAME :
838 : : DATASRC_DATABASE_FOUND_CNAME,
839 [ + + ][ + - ]: 28 : flags));
840 [ + + ]: 465 : } else if (wti != found.second.end()) {
841 : 393 : bool any(type == RRType::ANY());
842 : : isc::log::MessageID lid(wild ? DATASRC_DATABASE_WILDCARD_MATCH :
843 [ + + ]: 393 : DATASRC_DATABASE_FOUND_RRSET);
844 [ + + ]: 393 : if (any) {
845 : : // An ANY query, copy everything to the target instead of returning
846 : : // directly.
847 [ + + ]: 61 : for (FoundIterator it(found.second.begin());
848 : 122 : it != found.second.end(); ++it) {
849 [ + + ]: 46 : if (it->second) {
850 : : // Skip over the empty ANY
851 : 31 : target->push_back(it->second);
852 : : }
853 : : }
854 : : lid = wild ? DATASRC_DATABASE_WILDCARD_ANY :
855 [ + + ]: 15 : DATASRC_DATABASE_FOUND_ANY;
856 : : }
857 : : // Found an RR matching the query, so return it. (Note that this
858 : : // includes the case where we were explicitly querying for a CNAME and
859 : : // found it. It also includes the case where we were querying for an
860 : : // NS RRset and found it at the apex of the zone.)
861 : : return (logAndCreateResult(name, wildname, type, SUCCESS,
862 [ + - ]: 393 : wti->second, lid, flags));
863 : : }
864 : :
865 : : // If we get here, we have found something at the requested name but not
866 : : // one of the RR types we were interested in. This is the NXRRSET case so
867 : : // return the appropriate status. If DNSSEC information was requested,
868 : : // provide the NSEC records. If it's for wildcard, we need to get the
869 : : // NSEC records in the name of the wildcard, not the substituted one,
870 : : // so we need to search the tree again.
871 : : const ConstRRsetPtr dnssec_rrset =
872 : 18 : wild ? dnssec_ctx.getDNSSECRRset(Name(*wildname), false) :
873 [ + + ][ + - ]: 72 : dnssec_ctx.getDNSSECRRset(found);
[ + - ][ + - ]
[ + + ][ # # ]
874 [ + + ]: 72 : if (dnssec_rrset) {
875 : : // This log message covers both normal and wildcard cases, so we pass
876 : : // NULL for 'wildname'.
877 : : return (logAndCreateResult(name, NULL, type, NXRRSET, dnssec_rrset,
878 : : DATASRC_DATABASE_FOUND_NXRRSET_NSEC,
879 [ + - ]: 8 : flags | RESULT_NSEC_SIGNED));
880 : : }
881 : : return (logAndCreateResult(name, wildname, type, NXRRSET, dnssec_rrset,
882 : : wild ? DATASRC_DATABASE_WILDCARD_NXRRSET :
883 : : DATASRC_DATABASE_FOUND_NXRRSET,
884 [ + - ][ + + ]: 128 : flags | dnssec_ctx.getResultFlags()));
[ + - ]
885 : : }
886 : :
887 : : ZoneFinder::ResultContext
888 : 221 : DatabaseClient::Finder::findNoNameResult(const Name& name, const RRType& type,
889 : : FindOptions options,
890 : : const DelegationSearchResult& dresult,
891 : : std::vector<isc::dns::ConstRRsetPtr>*
892 : : target, FindDNSSECContext& dnssec_ctx)
893 : : {
894 : : // On entry to this method, we know that the database doesn't have any
895 : : // entry for this name. Before returning NXDOMAIN, we need to check
896 : : // for special cases.
897 : :
898 [ + - ][ + + ]: 221 : if (hasSubdomains(name.toText())) {
899 : : // Does the domain have a subdomain (i.e. it is an empty non-terminal)?
900 : : // If so, return NXRRSET instead of NXDOMAIN (as although the name does
901 : : // not exist in the database, it does exist in the DNS tree).
902 [ + - ]: 54 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
903 : : DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL).
904 [ + - ][ + - ]: 27 : arg(accessor_->getDBName()).arg(name);
905 : : return (ResultContext(NXRRSET, dnssec_ctx.getDNSSECRRset(name, true),
906 : 27 : dnssec_ctx.getResultFlags()));
907 [ + + ]: 194 : } else if ((options & NO_WILDCARD) == 0) {
908 : : // It's not an empty non-terminal and wildcard matching is not
909 : : // disabled, so check for wildcards. If there is a wildcard match
910 : : // (i.e. all results except NXDOMAIN) return it; otherwise fall
911 : : // through to the NXDOMAIN case below.
912 : : const ResultContext wcontext =
913 : : findWildcardMatch(name, type, options, dresult, target,
914 : 186 : dnssec_ctx);
915 [ + + ]: 186 : if (wcontext.code != NXDOMAIN) {
916 : : return (wcontext);
917 : : }
918 : : }
919 : :
920 : : // All avenues to find a match are now exhausted, return NXDOMAIN (plus
921 : : // NSEC records if requested).
922 [ + - ]: 220 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_NO_MATCH).
923 [ + - ][ + - ]: 110 : arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
[ + - ][ + - ]
924 : : return (ResultContext(NXDOMAIN, dnssec_ctx.getDNSSECRRset(name, true),
925 : 110 : dnssec_ctx.getResultFlags()));
926 : : }
927 : :
928 : : ZoneFinder::ResultContext
929 : 751 : DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
930 : : std::vector<ConstRRsetPtr>* target,
931 : : const FindOptions options)
932 : : {
933 [ + - ]: 1502 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_FIND_RECORDS)
934 [ + - ][ + - ]: 751 : .arg(accessor_->getDBName()).arg(name).arg(type).arg(getClass());
[ + - ][ + - ]
935 : :
936 : : // find() variants generally expect 'name' to be included in the zone.
937 : : // Otherwise the search algorithm below won't work correctly, so we
938 : : // reject the unexpected case first.
939 : : const NameComparisonResult::NameRelation reln =
940 [ + - ]: 751 : name.compare(getOrigin()).getRelation();
941 [ + + ]: 751 : if (reln != NameComparisonResult::SUBDOMAIN &&
942 : : reln != NameComparisonResult::EQUAL) {
943 [ + - ][ + - ]: 46 : isc_throw(OutOfZone, name.toText() << " not in " << getOrigin());
[ + - ][ + - ]
[ + - ]
944 : : }
945 : :
946 : : // First, go through all superdomains from the origin down, searching for
947 : : // nodes that indicate a delegation (i.e. NS or DNAME, ignoring NS records
948 : : // at the apex). If one is found, the search stops there.
949 : : //
950 : : // (In fact there could be RRs in the database corresponding to subdomains
951 : : // of the delegation. The reason we do the search for the delegations
952 : : // first is because the delegation means that another zone is authoritative
953 : : // for the data and so should be consulted to retrieve it. RRs below
954 : : // this delegation point can be found in a search for glue but not
955 : : // otherwise; in the latter case they are said to be occluded by the
956 : : // presence of the delegation.)
957 : 1454 : const DelegationSearchResult dresult = findDelegationPoint(name, options);
958 [ + + ]: 726 : if (dresult.rrset) {
959 : : // In this case no special flags are needed.
960 : 39 : return (ResultContext(dresult.code, dresult.rrset));
961 : : }
962 : :
963 : : // If there is no delegation, look for the exact match to the request
964 : : // name/type/class. However, there are special cases:
965 : : // - Requested name has a singleton CNAME record associated with it
966 : : // - Requested name is a delegation point (NS only but not at the zone
967 : : // apex - DNAME is ignored here as it redirects DNS names subordinate to
968 : : // the owner name - the owner name itself is not redirected.)
969 [ + - ]: 1374 : const bool is_origin = (name == getOrigin());
970 [ + - ][ + - ]: 1374 : WantedTypes final_types(FINAL_TYPES());
971 [ + - ]: 687 : final_types.insert(type);
972 : 687 : const FoundRRsets found = getRRsets(name.toText(), final_types,
973 : 687 : !is_origin, NULL,
974 [ + - ][ + + ]: 1347 : type == RRType::ANY());
[ + - ]
975 [ + - ]: 660 : FindDNSSECContext dnssec_ctx(*this, options);
976 [ + + ]: 660 : if (found.first) {
977 : : // Something found at the domain name. Look into it further to get
978 : : // the final result.
979 : : return (findOnNameResult(name, type, options, is_origin, found, NULL,
980 [ + + ]: 439 : target, dnssec_ctx));
981 : : } else {
982 : : // Did not find anything at all at the domain name, so check for
983 : : // subdomains or wildcards.
984 : : return (findNoNameResult(name, type, options, dresult, target,
985 [ + - ]: 221 : dnssec_ctx));
986 : : }
987 : : }
988 : :
989 : : // The behaviour is inspired by the one in the in-memory implementation.
990 : : ZoneFinder::FindNSEC3Result
991 : 10 : DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
992 [ + - ][ + - ]: 30 : LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3).arg(name).
993 [ + + ][ + - ]: 20 : arg(recursive ? "recursive" : "non-recursive");
994 : :
995 : : // First, validate the input
996 [ + - ]: 10 : const NameComparisonResult cmp_result(name.compare(getOrigin()));
997 [ + + ][ + + ]: 10 : if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
[ + + ]
998 : 7 : cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
999 [ + - ][ + - ]: 4 : isc_throw(OutOfZone, "findNSEC3 attempt for out-of-zone name: " <<
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
1000 : : name << ", zone: " << getOrigin() << "/" << getClass());
1001 : : }
1002 : :
1003 : : // Now, we need to get the NSEC3 params from the apex and create the hash
1004 : : // creator for it.
1005 : 8 : const FoundRRsets nsec3param(getRRsets(getOrigin().toText(),
1006 [ + - ][ + - ]: 16 : NSEC3PARAM_TYPES(), false));
[ + - ][ + - ]
1007 : 8 : const FoundIterator param(nsec3param.second.find(RRType::NSEC3PARAM()));
1008 [ + - ][ + + ]: 8 : if (!nsec3param.first || param == nsec3param.second.end()) {
[ + + ]
1009 : : // No NSEC3 params? :-(
1010 [ + - ][ + - ]: 2 : isc_throw(DataSourceError, "findNSEC3 attempt for non NSEC3 signed " <<
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
1011 : : "zone: " << getOrigin() << "/" << getClass());
1012 : : }
1013 : : // This takes the RRset received from the find method, takes the first RR
1014 : : // in it, casts it to NSEC3PARAM (as it should be that one) and then creates
1015 : : // the hash calculator class from it.
1016 : : const scoped_ptr<NSEC3Hash> calculator(NSEC3Hash::create(
1017 : : dynamic_cast<const generic::NSEC3PARAM&>(
1018 [ + - ][ + - ]: 7 : param->second->getRdataIterator()->getCurrent())));
[ + - ][ + - ]
1019 : :
1020 : : // Few shortcut variables
1021 [ + - ]: 7 : const unsigned olabels(getOrigin().getLabelCount());
1022 : 7 : const unsigned qlabels(name.getLabelCount());
1023 [ + - ][ + - ]: 14 : const string otext(getOrigin().toText());
1024 : :
1025 : : // This will be set to the one covering the query name
1026 : : ConstRRsetPtr covering_proof;
1027 : :
1028 : : // We keep stripping the leftmost label until we find something.
1029 : : // In case it is recursive, we'll exit the loop at the first iteration.
1030 [ + - ]: 10 : for (unsigned labels(qlabels); labels >= olabels; -- labels) {
1031 : : const string hash(calculator->calculate(labels == qlabels ? name :
1032 : : name.split(qlabels - labels,
1033 [ + + ][ + - ]: 20 : labels)));
[ + - ][ + - ]
1034 : : // Get the exact match for the name.
1035 [ + - ][ + - ]: 20 : LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3_TRYHASH).
[ + - ]
1036 [ + - ][ + - ]: 10 : arg(name).arg(labels).arg(hash);
[ + - ][ + - ]
1037 : :
1038 : : DatabaseAccessor::IteratorContextPtr
1039 [ + - ]: 10 : context(accessor_->getNSEC3Records(hash, zone_id_));
1040 : :
1041 [ - + ]: 10 : if (!context) {
1042 [ # # ][ # # ]: 0 : isc_throw(Unexpected, "Iterator context null for hash " + hash);
[ # # ]
1043 : : }
1044 : :
1045 [ + - ]: 20 : const FoundRRsets nsec3(getRRsets(hash + "." + otext, NSEC3_TYPES(),
1046 [ + - ][ + - ]: 20 : false, NULL, false, context));
[ + - + - ]
[ + - ]
1047 : :
1048 [ + + ]: 10 : if (nsec3.first) {
1049 : : // We found an exact match against the current label.
1050 : 4 : const FoundIterator it(nsec3.second.find(RRType::NSEC3()));
1051 [ - + ]: 4 : if (it == nsec3.second.end()) {
1052 [ # # ][ # # ]: 0 : isc_throw(DataSourceError, "Hash " + hash +
[ # # ][ # # ]
[ # # ]
1053 : : "exists, but no NSEC3 there");
1054 : : }
1055 : :
1056 [ + - ][ + - ]: 8 : LOG_DEBUG(logger, DBG_TRACE_BASIC,
[ + - ]
1057 [ + - ][ + - ]: 4 : DATASRC_DATABASE_FINDNSEC3_MATCH).arg(name).arg(labels).
[ + - ]
1058 [ + - ]: 4 : arg(*it->second);
1059 : : // Yes, we win
1060 : 8 : return (FindNSEC3Result(true, labels, it->second, covering_proof));
1061 : : } else {
1062 : : // There's no exact match. We try a previous one. We must find it
1063 : : // (if the zone is properly signed).
1064 : 6 : const string prevHash(accessor_->findPreviousNSEC3Hash(zone_id_,
1065 [ + - ]: 12 : hash));
1066 [ + - ][ + - ]: 12 : LOG_DEBUG(logger, DBG_TRACE_BASIC,
[ + - ]
1067 [ + - ][ + - ]: 6 : DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV).arg(name).
1068 [ + - ][ + - ]: 6 : arg(labels).arg(prevHash);
1069 [ + - ][ + - ]: 6 : context = accessor_->getNSEC3Records(prevHash, zone_id_);
1070 : 6 : const FoundRRsets prev_nsec3(getRRsets(prevHash + "." + otext,
1071 [ + - ]: 6 : NSEC3_TYPES(), false, NULL,
1072 [ + - ][ + - ]: 12 : false, context));
[ + - ][ + - ]
[ + - ]
1073 : :
1074 [ - + ]: 6 : if (!prev_nsec3.first) {
1075 [ # # ][ # # ]: 0 : isc_throw(DataSourceError, "Hash " + prevHash + " returned "
[ # # ][ # # ]
[ # # ]
1076 : : "from findPreviousNSEC3Hash, but it is empty");
1077 : : }
1078 : : const FoundIterator
1079 : 6 : prev_it(prev_nsec3.second.find(RRType::NSEC3()));
1080 [ - + ]: 6 : if (prev_it == prev_nsec3.second.end()) {
1081 [ # # ][ # # ]: 0 : isc_throw(DataSourceError, "The previous hash " + prevHash +
[ # # ][ # # ]
[ # # ]
1082 : : "exists, but does not contain the NSEC3");
1083 : : }
1084 : :
1085 [ + - ]: 6 : covering_proof = prev_it->second;
1086 : : // In case it is recursive, we try to get an exact match a level
1087 : : // up. If it is not recursive, the caller is ok with a covering
1088 : : // one, so we just return it.
1089 [ + + ]: 6 : if (!recursive) {
1090 [ + - ][ + - ]: 6 : LOG_DEBUG(logger, DBG_TRACE_BASIC,
[ + - ]
1091 [ + - ][ + - ]: 3 : DATASRC_DATABASE_FINDNSEC3_COVER).arg(name).
1092 [ + - ][ + - ]: 6 : arg(labels).arg(*covering_proof);
1093 : : return (FindNSEC3Result(false, labels, covering_proof,
1094 : 3 : ConstRRsetPtr()));
1095 : : }
1096 : : }
1097 : : }
1098 : :
1099 : : // The zone must contain at least the apex and that one should match
1100 : : // exactly. If that doesn't happen, we have a problem.
1101 [ # # ][ # # ]: 0 : isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely a "
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
1102 : : "broken NSEC3 zone: " << otext << "/" << getClass());
1103 : : }
1104 : :
1105 : : Name
1106 : 43 : DatabaseClient::Finder::findPreviousName(const Name& name) const {
1107 : 43 : const string str(accessor_->findPreviousName(zone_id_,
1108 [ + - ][ + + ]: 81 : name.reverse().toText()));
1109 : : try {
1110 [ + + ]: 75 : return (Name(str));
1111 : : }
1112 [ - + ]: 2 : catch (const isc::dns::NameParserException&) {
1113 [ - + ][ - + ]: 2 : isc_throw(DataSourceError, "Bad name " + str + " from findPreviousName");
[ - + ][ - + ]
[ - + ]
1114 : : }
1115 : : }
1116 : :
1117 : : Name
1118 : 2421 : DatabaseClient::Finder::getOrigin() const {
1119 : 2421 : return (origin_);
1120 : : }
1121 : :
1122 : : isc::dns::RRClass
1123 : 6059 : DatabaseClient::Finder::getClass() const {
1124 : : // TODO Implement
1125 : 6059 : return isc::dns::RRClass::IN();
1126 : : }
1127 : :
1128 : : namespace {
1129 : :
1130 : : /// This needs, beside of converting all data from textual representation, group
1131 : : /// together rdata of the same RRsets. To do this, we hold one row of data ahead
1132 : : /// of iteration. When we get a request to provide data, we create it from this
1133 : : /// data and load a new one. If it is to be put to the same rrset, we add it.
1134 : : /// Otherwise we just return what we have and keep the row as the one ahead
1135 : : /// for next time.
1136 : : class DatabaseIterator : public ZoneIterator {
1137 : : public:
1138 : 39 : DatabaseIterator(boost::shared_ptr<DatabaseAccessor> accessor,
1139 : : const Name& zone_name,
1140 : : const RRClass& rrclass,
1141 : : bool separate_rrs) :
1142 : : accessor_(accessor),
1143 : : class_(rrclass),
1144 : : ready_(true),
1145 : 6 : separate_rrs_(separate_rrs)
1146 : : {
1147 : : // Get the zone
1148 [ + - ][ + - ]: 39 : const pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
1149 [ + + ]: 39 : if (!zone.first) {
1150 : : // No such zone, can't continue
1151 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, "Zone " + zone_name.toText() +
[ + - ][ + - ]
[ + - ][ + - ]
1152 : : " can not be iterated, because it doesn't exist "
1153 : : "in this data source");
1154 : : }
1155 : :
1156 : : // Start a separate transaction.
1157 [ + - ]: 35 : accessor_->startTransaction();
1158 : :
1159 : : // Find the SOA of the zone (may or may not succeed). Note that
1160 : : // this must be done before starting the iteration context.
1161 [ + - ]: 35 : soa_ = DatabaseClient::Finder(accessor_, zone.second, zone_name).
1162 [ + - ][ + + ]: 104 : find(zone_name, RRType::SOA())->rrset;
[ + - ]
1163 : :
1164 : : // Request the context
1165 [ + - ][ + - ]: 34 : context_ = accessor_->getAllRecords(zone.second);
1166 : : // It must not return NULL, that's a bug of the implementation
1167 [ + + ]: 34 : if (!context_) {
1168 [ + - ][ + - ]: 2 : isc_throw(isc::Unexpected, "Iterator context null at " +
[ + - ][ + - ]
1169 : : zone_name.toText());
1170 : : }
1171 : :
1172 : : // Prepare data for the next time
1173 [ + - ]: 33 : getData();
1174 : 33 : }
1175 : :
1176 : 33 : virtual ~DatabaseIterator() {
1177 [ + + ]: 33 : if (ready_) {
1178 [ + - ]: 15 : accessor_->commit();
1179 : : }
1180 : 66 : }
1181 : :
1182 : 12 : virtual ConstRRsetPtr getSOA() const {
1183 : 12 : return (soa_);
1184 : : }
1185 : :
1186 : 380 : virtual isc::dns::ConstRRsetPtr getNextRRset() {
1187 [ + + ]: 380 : if (!ready_) {
1188 [ + - ]: 6 : isc_throw(isc::Unexpected, "Iterating past the zone end");
1189 : : }
1190 [ + + ]: 377 : if (!data_ready_) {
1191 : : // At the end of zone
1192 : 18 : accessor_->commit();
1193 : 18 : ready_ = false;
1194 [ + - ]: 18 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_END);
1195 : : return (ConstRRsetPtr());
1196 : : }
1197 : 359 : const RRType rtype(rtype_txt_);
1198 : 359 : RRsetPtr rrset(new RRset(Name(name_txt_), class_, rtype,
1199 [ + - ][ + - ]: 359 : RRTTL(ttl_txt_)));
[ + - ][ + - ]
1200 : : // Remember the first RDATA of the RRset for comparison:
1201 : 359 : const ConstRdataPtr rdata_base = rdata_;
1202 : : while (true) {
1203 : : // Extend the RRset with the new RDATA.
1204 [ + - ]: 808 : rrset->addRdata(rdata_);
1205 : :
1206 : : // Retrieve the next record from the database. If we reach the
1207 : : // end of the zone, done; if we were requested to separate all RRs,
1208 : : // just remember this record and return the single RR.
1209 [ + - ]: 404 : getData();
1210 [ + + ][ + + ]: 404 : if (separate_rrs_ || !data_ready_) {
1211 : : break;
1212 : : }
1213 : :
1214 : : // Check if the next record belongs to the same RRset. If not,
1215 : : // we are done. The next RDATA has been stored in rdata_, which
1216 : : // is used within this loop (if it belongs to the same RRset) or
1217 : : // in the next call.
1218 [ + - ][ + - ]: 963 : if (Name(name_txt_) != rrset->getName() ||
[ + + ][ + + ]
[ + + ][ # # ]
1219 [ + - ][ + + ]: 631 : !isSameType(rtype, rdata_base, RRType(rtype_txt_), rdata_)) {
[ + + ][ # # ]
[ # # ]
1220 : : break;
1221 : : }
1222 : :
1223 : : // Adjust TTL if necessary
1224 [ + - ]: 45 : const RRTTL next_ttl(ttl_txt_);
1225 [ + - ][ + + ]: 45 : if (next_ttl != rrset->getTTL()) {
1226 [ + - ][ + + ]: 9 : if (next_ttl < rrset->getTTL()) {
1227 [ + - ]: 2 : rrset->setTTL(next_ttl);
1228 : : }
1229 [ + - ][ - + ]: 18 : LOG_WARN(logger, DATASRC_DATABASE_ITERATE_TTL_MISMATCH).
[ + - ]
1230 [ + - ][ + - ]: 9 : arg(name_txt_).arg(class_).arg(rtype).arg(rrset->getTTL());
[ + - ][ + - ]
[ + - ][ + - ]
1231 : : }
1232 : : }
1233 [ + - ][ + - ]: 718 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE_NEXT).
[ + - ]
1234 [ + - ][ + - ]: 359 : arg(rrset->getName()).arg(rrset->getType());
[ + - ][ + - ]
[ + - ]
1235 : : return (rrset);
1236 : : }
1237 : :
1238 : : private:
1239 : : // Check two RDATA types are equivalent. Basically it's a trivial
1240 : : // comparison, but if both are of RRSIG, we should also compare the types
1241 : : // covered.
1242 : : static bool isSameType(RRType type1, ConstRdataPtr rdata1,
1243 : : RRType type2, ConstRdataPtr rdata2)
1244 : : {
1245 [ + + ]: 186 : if (type1 != type2) {
1246 : : return (false);
1247 : : }
1248 [ + + ]: 86 : if (type1 == RRType::RRSIG()) {
1249 [ + - ][ + - ]: 43 : return (dynamic_cast<const generic::RRSIG&>(*rdata1).typeCovered()
1250 : 43 : == dynamic_cast<const generic::RRSIG&>(*rdata2).
1251 [ + - ][ + - ]: 43 : typeCovered());
1252 : : }
1253 : : return (true);
1254 : : }
1255 : :
1256 : : // Load next row of data
1257 : 437 : void getData() {
1258 [ + + ]: 4807 : string data[DatabaseAccessor::COLUMN_COUNT];
1259 [ + - ]: 437 : data_ready_ = context_->getNext(data);
1260 [ + + ]: 437 : if (data_ready_) {
1261 : 416 : name_txt_ = data[DatabaseAccessor::NAME_COLUMN];
1262 : 416 : rtype_txt_ = data[DatabaseAccessor::TYPE_COLUMN];
1263 : 416 : ttl_txt_ = data[DatabaseAccessor::TTL_COLUMN];
1264 : : rdata_ = rdata::createRdata(RRType(rtype_txt_), class_,
1265 [ + - ][ + - ]: 416 : data[DatabaseAccessor::RDATA_COLUMN]);
1266 [ + + ][ # # ]: 2622 : }
1267 : 437 : }
1268 : :
1269 : : // The dedicated accessor
1270 : : boost::shared_ptr<DatabaseAccessor> accessor_;
1271 : : // The context
1272 : : DatabaseAccessor::IteratorContextPtr context_;
1273 : : // Class of the zone
1274 : : const RRClass class_;
1275 : : // SOA of the zone, if any (it should normally exist)
1276 : : ConstRRsetPtr soa_;
1277 : : // Status
1278 : : bool ready_, data_ready_;
1279 : : // Data of the next row
1280 : : string name_txt_, rtype_txt_, ttl_txt_;
1281 : : // RDATA of the next row
1282 : : ConstRdataPtr rdata_;
1283 : : // Whether to modify differing TTL values, or treat a different TTL as
1284 : : // a different RRset
1285 : : const bool separate_rrs_;
1286 : : };
1287 : :
1288 : : }
1289 : :
1290 : : ZoneIteratorPtr
1291 : 39 : DatabaseClient::getIterator(const isc::dns::Name& name,
1292 : : bool separate_rrs) const
1293 : : {
1294 : : ZoneIteratorPtr iterator = ZoneIteratorPtr(new DatabaseIterator(
1295 : 39 : accessor_->clone(), name,
1296 [ + - ][ + + ]: 39 : rrclass_, separate_rrs));
1297 [ + - ][ + - ]: 66 : LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ITERATE).
[ + - ]
1298 [ + - ][ + - ]: 33 : arg(name);
1299 : :
1300 : 33 : return (iterator);
1301 : : }
1302 : :
1303 : : //
1304 : : // Zone updater using some database system as the underlying data source.
1305 : : //
1306 : : class DatabaseUpdater : public ZoneUpdater {
1307 : : public:
1308 : 149 : DatabaseUpdater(boost::shared_ptr<DatabaseAccessor> accessor, int zone_id,
1309 : : const Name& zone_name, const RRClass& zone_class,
1310 : : bool journaling) :
1311 : : committed_(false), accessor_(accessor), zone_id_(zone_id),
1312 [ + - ]: 149 : db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
1313 : : zone_class_(zone_class), journaling_(journaling),
1314 : : diff_phase_(NOT_STARTED), serial_(0),
1315 [ + - ][ + - ]: 298 : finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
[ + - ][ + - ]
1316 : : {
1317 [ + - ]: 149 : logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
1318 [ + - ][ + - ]: 149 : .arg(zone_name_).arg(zone_class_).arg(db_name_);
[ + - ][ + - ]
1319 : 149 : }
1320 : :
1321 : 149 : virtual ~DatabaseUpdater() {
1322 [ + + ]: 149 : if (!committed_) {
1323 : : try {
1324 [ + + ]: 69 : accessor_->rollback();
1325 [ + - ]: 68 : logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
1326 [ + - ][ + - ]: 68 : .arg(zone_name_).arg(zone_class_).arg(db_name_);
[ + - ][ + - ]
1327 [ - + ][ + - ]: 2 : } catch (const DataSourceError& e) {
1328 : : // We generally expect that rollback always succeeds, and
1329 : : // it should in fact succeed in a way we execute it. But
1330 : : // as the public API allows rollback() to fail and
1331 : : // throw, we should expect it. Obviously we cannot re-throw
1332 : : // it. The best we can do is to log it as a critical error.
1333 [ - + ]: 1 : logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
1334 [ - + ][ - + ]: 1 : .arg(zone_name_).arg(zone_class_).arg(db_name_)
[ - + ][ - + ]
1335 [ - + ]: 2 : .arg(e.what());
1336 : : }
1337 : : }
1338 : :
1339 [ + - ]: 149 : logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
1340 [ + - ][ + - ]: 149 : .arg(zone_name_).arg(zone_class_).arg(db_name_);
[ + - ][ + - ]
1341 : 298 : }
1342 : :
1343 : 79 : virtual ZoneFinder& getFinder() { return (*finder_); }
1344 : :
1345 : : virtual void addRRset(const AbstractRRset& rrset);
1346 : : virtual void deleteRRset(const AbstractRRset& rrset);
1347 : : virtual void commit();
1348 : :
1349 : : private:
1350 : : // A short cut typedef only for making the code shorter.
1351 : : typedef DatabaseAccessor Accessor;
1352 : :
1353 : : bool committed_;
1354 : : boost::shared_ptr<DatabaseAccessor> accessor_;
1355 : : const int zone_id_;
1356 : : const string db_name_;
1357 : : const string zone_name_;
1358 : : const RRClass zone_class_;
1359 : : const bool journaling_;
1360 : : // For the journals
1361 : : enum DiffPhase {
1362 : : NOT_STARTED,
1363 : : DELETE,
1364 : : ADD
1365 : : };
1366 : : DiffPhase diff_phase_;
1367 : : Serial serial_;
1368 : : boost::scoped_ptr<DatabaseClient::Finder> finder_;
1369 : :
1370 : : // This is a set of validation checks commonly used for addRRset() and
1371 : : // deleteRRset to minimize duplicate code logic and to make the main
1372 : : // code concise.
1373 : : void validateAddOrDelete(const char* const op_str,
1374 : : const AbstractRRset& rrset,
1375 : : DiffPhase prev_phase,
1376 : : DiffPhase current_phase) const;
1377 : : };
1378 : :
1379 : : void
1380 : 1808 : DatabaseUpdater::validateAddOrDelete(const char* const op_str,
1381 : : const AbstractRRset& rrset,
1382 : : DiffPhase prev_phase,
1383 : : DiffPhase current_phase) const
1384 : : {
1385 [ + + ]: 1808 : if (committed_) {
1386 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, op_str << " attempt after commit to zone: "
[ + - ][ + - ]
[ + - ][ + - ]
1387 : : << zone_name_ << "/" << zone_class_);
1388 : : }
1389 [ + + ]: 1804 : if (rrset.getRdataCount() == 0) {
1390 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, op_str << " attempt with an empty RRset: "
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
1391 : : << rrset.getName() << "/" << zone_class_ << "/"
1392 : : << rrset.getType());
1393 : : }
1394 [ + + ]: 1800 : if (rrset.getClass() != zone_class_) {
1395 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, op_str << " attempt for a different class "
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
1396 : : << zone_name_ << "/" << zone_class_ << ": "
1397 : : << rrset.toText());
1398 : : }
1399 [ + + ]: 3592 : if (rrset.getRRsig()) {
1400 [ + - ][ + - ]: 12 : isc_throw(DataSourceError, op_str << " attempt for RRset with RRSIG "
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
1401 : : << zone_name_ << "/" << zone_class_ << ": "
1402 : : << rrset.toText());
1403 : : }
1404 [ + + ]: 1790 : if (journaling_) {
1405 : 1253 : const RRType rrtype(rrset.getType());
1406 [ + + ][ + + ]: 1253 : if (rrtype == RRType::SOA() && diff_phase_ != prev_phase) {
[ + + ]
1407 [ + - ][ + - ]: 12 : isc_throw(isc::BadValue, op_str << " attempt in an invalid "
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
1408 : : << "diff phase: " << diff_phase_ << ", rrset: " <<
1409 : : rrset.toText());
1410 : : }
1411 [ + + ][ + + ]: 1247 : if (rrtype != RRType::SOA() && diff_phase_ != current_phase) {
[ + + ]
1412 [ + - ][ + - ]: 8 : isc_throw(isc::BadValue, "diff state change by non SOA: "
[ + - ]
1413 : : << rrset.toText());
1414 : : }
1415 : : }
1416 : 1780 : }
1417 : :
1418 : : // This is a helper class used in adding/deleting RRsets to/from a database.
1419 : : // The purpose of this class is to provide conversion interface from various
1420 : : // parameters of the RRset to corresponding textual representations that the
1421 : : // underlying database interface expects. The necessary parameters and how
1422 : : // to convert them depend on several things, such as whether it's NSEC3 related
1423 : : // or not, or whether journaling is requested. In order to avoid unnecessary
1424 : : // conversion, this class also performs the conversion in a lazy manner.
1425 : : // Also, in order to avoid redundant conversion when the conversion is
1426 : : // requested for the same parameter multiple times, it remembers the
1427 : : // conversion result first time, and reuses it for subsequent requests
1428 : : // (this implicitly assumes copying std::string objects is not very expensive;
1429 : : // this is often the case in some common implementations that have
1430 : : // copy-on-write semantics for the string class).
1431 : 1780 : class RRParameterConverter {
1432 : : public:
1433 : 1780 : RRParameterConverter(const AbstractRRset& rrset) : rrset_(rrset)
1434 : : {}
1435 : 3154 : const string& getName() {
1436 [ + + ]: 3154 : if (name_.empty()) {
1437 : 3532 : name_ = rrset_.getName().toText();
1438 : : }
1439 : 3154 : return (name_);
1440 : : }
1441 : 18 : const string& getNSEC3Name() {
1442 [ + - ]: 18 : if (nsec3_name_.empty()) {
1443 [ + - ]: 36 : nsec3_name_ = rrset_.getName().split(0, 1).toText(true);
1444 : : }
1445 : 18 : return (nsec3_name_);
1446 : : }
1447 : : const string& getRevName() {
1448 [ + + ]: 1260 : if (revname_.empty()) {
1449 [ + - ][ + - ]: 2224 : revname_ = rrset_.getName().reverse().toText();
[ + - ]
1450 : : }
1451 : : return (revname_);
1452 : : }
1453 : 2515 : const string& getTTL() {
1454 [ + + ]: 2515 : if (ttl_.empty()) {
1455 : 3500 : ttl_ = rrset_.getTTL().toText();
1456 : : }
1457 : 2515 : return (ttl_);
1458 : : }
1459 : 3172 : const string& getType() {
1460 [ + + ]: 3172 : if (type_.empty()) {
1461 : 3560 : type_ = rrset_.getType().toText();
1462 : : }
1463 : 3172 : return (type_);
1464 : : }
1465 : :
1466 : : private:
1467 : : string name_;
1468 : : string nsec3_name_;
1469 : : string revname_;
1470 : : string ttl_;
1471 : : string type_;
1472 : : const AbstractRRset& rrset_;
1473 : : };
1474 : :
1475 : : namespace {
1476 : : // A shared shortcut to detect if the given type of RDATA is NSEC3 or
1477 : : // RRSIG covering NSEC3. RRSIG for NSEC3 should go to the (conceptual)
1478 : : // separate namespace, so we need to check the covered type.
1479 : : // Note: in principle the type covered should be the same for
1480 : : // all RDATA, but the RRset interface doesn't ensure that condition.
1481 : : // So we explicitly check that for every RDATA below.
1482 : : bool
1483 : 1932 : isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
1484 [ + + ]: 1932 : if (rrtype == RRType::NSEC3()) {
1485 : : return (true);
1486 : : }
1487 [ + + + + ]: 3870 : if (rrtype == RRType::RRSIG() &&
[ + + ]
1488 [ + - ]: 1950 : dynamic_cast<const generic::RRSIG&>(rdata).typeCovered() ==
1489 : 36 : RRType::NSEC3())
1490 : : {
1491 : : return (true);
1492 : : }
1493 : 1932 : return (false);
1494 : : }
1495 : : }
1496 : :
1497 : : void
1498 : 1139 : DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
1499 : 1139 : validateAddOrDelete("add", rrset, DELETE, ADD);
1500 : :
1501 : : // It's guaranteed rrset has at least one RDATA at this point.
1502 : 1125 : RdataIteratorPtr it = rrset.getRdataIterator();
1503 [ + + ]: 1125 : if (journaling_) {
1504 : 618 : diff_phase_ = ADD;
1505 [ + - ][ + + ]: 618 : if (rrset.getType() == RRType::SOA()) {
1506 [ + - ]: 613 : serial_ = dynamic_cast<const generic::SOA&>(it->getCurrent()).
1507 [ + - ][ + - ]: 613 : getSerial();
1508 : : }
1509 : : }
1510 : :
1511 : 1125 : RRParameterConverter cvtr(rrset);
1512 [ + - ][ + - ]: 2397 : for (; !it->isLast(); it->next()) {
[ + + ]
1513 [ + - ]: 1273 : const Rdata& rdata = it->getCurrent();
1514 [ + - ][ + - ]: 1273 : const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
1515 : :
1516 : 1273 : string sigtype;
1517 [ + - ][ + + ]: 1273 : if (rrset.getType() == RRType::RRSIG()) {
1518 : : // XXX: the current interface (based on the current sqlite3
1519 : : // data source schema) requires a separate "sigtype" column,
1520 : : // even though it won't be used in a newer implementation.
1521 : : // We should eventually clean up the schema design and simplify
1522 : : // the interface, but until then we have to conform to the schema.
1523 : 34 : sigtype = dynamic_cast<const generic::RRSIG&>(rdata).
1524 [ + - ][ + - ]: 68 : typeCovered().toText();
[ + - ]
1525 : : }
1526 [ + - ]: 2546 : const string& rdata_txt = rdata.toText();
1527 [ + + ]: 1273 : if (journaling_) {
1528 : : const string journal[Accessor::DIFF_PARAM_COUNT] =
1529 [ + - ][ + - ]: 3091 : { cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ # # ]
1530 : 618 : accessor_->addRecordDiff(zone_id_, serial_.getValue(),
1531 [ + + ][ + + ]: 3090 : Accessor::DIFF_ADD, journal);
[ + + ]
1532 : : }
1533 [ + + ]: 1272 : if (nsec3_type) {
1534 : : const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
1535 [ + - ][ + - ]: 36 : { cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(),
[ + - ]
1536 [ + - ][ + - ]: 84 : rdata_txt };
[ + - ][ + - ]
[ # # ]
1537 [ - + ][ + + ]: 60 : accessor_->addNSEC3RecordToZone(nsec3_columns);
[ # # ]
1538 : : } else {
1539 : : const string columns[Accessor::ADD_COLUMN_COUNT] =
1540 [ + - ][ + - ]: 2520 : { cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
1541 [ + - ][ + - ]: 11340 : cvtr.getType(), sigtype, rdata_txt };
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ # # ]
1542 [ - + ][ + + ]: 8820 : accessor_->addRecordToZone(columns);
[ # # ]
1543 : : }
1544 : : }
1545 : 1124 : }
1546 : :
1547 : : void
1548 : 669 : DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
1549 : : // If this is the first operation, pretend we are starting a new delete
1550 : : // sequence after adds. This will simplify the validation below.
1551 [ + + ]: 669 : if (diff_phase_ == NOT_STARTED) {
1552 : 61 : diff_phase_ = ADD;
1553 : : }
1554 : :
1555 : 669 : validateAddOrDelete("delete", rrset, ADD, DELETE);
1556 : :
1557 : 655 : RdataIteratorPtr it = rrset.getRdataIterator();
1558 [ + + ]: 655 : if (journaling_) {
1559 : 625 : diff_phase_ = DELETE;
1560 [ + - ][ + + ]: 625 : if (rrset.getType() == RRType::SOA()) {
1561 : : serial_ =
1562 [ + - ]: 620 : dynamic_cast<const generic::SOA&>(it->getCurrent()).
1563 [ + - ][ + - ]: 620 : getSerial();
1564 : : }
1565 : : }
1566 : :
1567 : 655 : RRParameterConverter cvtr(rrset);
1568 [ + - ][ + - ]: 1312 : for (; !it->isLast(); it->next()) {
[ + + ]
1569 [ + - ]: 659 : const Rdata& rdata = it->getCurrent();
1570 [ + - ][ + - ]: 659 : const bool nsec3_type = isNSEC3KindType(rrset.getType(), rdata);
1571 [ + - ][ + - ]: 1318 : const string& rdata_txt = it->getCurrent().toText();
1572 : :
1573 [ + + ]: 659 : if (journaling_) {
1574 : : const string journal[Accessor::DIFF_PARAM_COUNT] =
1575 [ + - ][ + - ]: 3127 : { cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ # # ]
1576 : 625 : accessor_->addRecordDiff(zone_id_, serial_.getValue(),
1577 [ + + ][ + + ]: 3125 : Accessor::DIFF_DELETE, journal);
[ + + ]
1578 : : }
1579 : : const string params[Accessor::DEL_PARAM_COUNT] =
1580 : : { nsec3_type ? cvtr.getNSEC3Name() : cvtr.getName(),
1581 [ + + ][ + - ]: 2628 : cvtr.getType(), rdata_txt };
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ # # ]
1582 [ + + ]: 657 : if (nsec3_type) {
1583 [ - + ]: 6 : accessor_->deleteNSEC3RecordInZone(params);
1584 : : } else {
1585 [ - + ]: 657 : accessor_->deleteRecordInZone(params);
1586 : : }
1587 [ + + ][ # # ]: 2628 : }
1588 : 653 : }
1589 : :
1590 : : void
1591 : 87 : DatabaseUpdater::commit() {
1592 [ + + ]: 87 : if (committed_) {
1593 [ + - ][ + - ]: 8 : isc_throw(DataSourceError, "Duplicate commit attempt for "
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ]
1594 : : << zone_name_ << "/" << zone_class_ << " on "
1595 : : << db_name_);
1596 : : }
1597 [ + + ][ + + ]: 83 : if (journaling_ && diff_phase_ == DELETE) {
1598 [ + - ]: 4 : isc_throw(isc::BadValue, "Update sequence not complete");
1599 : : }
1600 : 81 : accessor_->commit();
1601 : 80 : committed_ = true; // make sure the destructor won't trigger rollback
1602 : :
1603 : : // We release the accessor immediately after commit is completed so that
1604 : : // we don't hold the possible internal resource any longer.
1605 : 80 : accessor_.reset();
1606 : :
1607 : 80 : logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
1608 [ + - ][ + - ]: 80 : .arg(zone_name_).arg(zone_class_).arg(db_name_);
[ + - ]
1609 : 80 : }
1610 : :
1611 : : // The updater factory
1612 : : ZoneUpdaterPtr
1613 : 153 : DatabaseClient::getUpdater(const isc::dns::Name& name, bool replace,
1614 : : bool journaling) const
1615 : : {
1616 [ + + ][ + + ]: 153 : if (replace && journaling) {
1617 [ + - ]: 6 : isc_throw(isc::BadValue, "Can't store journal and replace the whole "
1618 : : "zone at the same time");
1619 : : }
1620 : 150 : boost::shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
1621 : 150 : const std::pair<bool, int> zone(update_accessor->startUpdateZone(
1622 [ + - ][ + - ]: 150 : name.toText(), replace));
1623 [ + + ]: 150 : if (!zone.first) {
1624 : : return (ZoneUpdaterPtr());
1625 : : }
1626 : :
1627 : : return (ZoneUpdaterPtr(new DatabaseUpdater(update_accessor, zone.second,
1628 [ + - ][ + - ]: 149 : name, rrclass_, journaling)));
1629 : : }
1630 : :
1631 : : //
1632 : : // Zone journal reader using some database system as the underlying data
1633 : : // source.
1634 : : //
1635 : : class DatabaseJournalReader : public ZoneJournalReader {
1636 : : private:
1637 : : // A shortcut typedef to keep the code concise.
1638 : : typedef DatabaseAccessor Accessor;
1639 : : public:
1640 : : DatabaseJournalReader(boost::shared_ptr<Accessor> accessor, const Name& zone,
1641 : : int zone_id, const RRClass& rrclass, uint32_t begin,
1642 : : uint32_t end) :
1643 : : accessor_(accessor), zone_(zone), rrclass_(rrclass),
1644 [ + - ]: 23 : begin_(begin), end_(end), finished_(false)
1645 : : {
1646 [ + + ][ + - ]: 19 : context_ = accessor_->getDiffs(zone_id, begin, end);
1647 : : }
1648 : 45 : virtual ~DatabaseJournalReader() {}
1649 : 838 : virtual ConstRRsetPtr getNextDiff() {
1650 [ + + ]: 838 : if (finished_) {
1651 [ + - ][ + - ]: 6 : isc_throw(InvalidOperation,
[ + - ]
1652 : : "Diff read attempt past the end of sequence on "
1653 : : << accessor_->getDBName());
1654 : : }
1655 : :
1656 [ + + ]: 10020 : string data[Accessor::COLUMN_COUNT];
1657 [ + - ][ + + ]: 835 : if (!context_->getNext(data)) {
1658 : 10 : finished_ = true;
1659 [ + - ][ + - ]: 20 : LOG_DEBUG(logger, DBG_TRACE_BASIC,
[ + - ]
1660 : : DATASRC_DATABASE_JOURNALREADER_END).
1661 [ + - ][ + - ]: 10 : arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
[ + - ][ + - ]
[ + - ]
1662 [ + - ][ + - ]: 10 : arg(begin_).arg(end_);
1663 : : return (ConstRRsetPtr());
1664 : : }
1665 : :
1666 : : try {
1667 : 824 : RRsetPtr rrset(new RRset(Name(data[Accessor::NAME_COLUMN]),
1668 : : rrclass_,
1669 : : RRType(data[Accessor::TYPE_COLUMN]),
1670 [ + + ][ + + ]: 825 : RRTTL(data[Accessor::TTL_COLUMN])));
[ + + ][ + - ]
[ + - ][ + - ]
1671 [ + - ]: 822 : rrset->addRdata(rdata::createRdata(rrset->getType(), rrclass_,
1672 [ + + ][ + - ]: 1643 : data[Accessor::RDATA_COLUMN]));
1673 [ + - ][ + - ]: 1642 : LOG_DEBUG(logger, DBG_TRACE_DETAILED,
[ + - ]
1674 : : DATASRC_DATABASE_JOURNALREADER_NEXT).
1675 [ + - ][ + - ]: 821 : arg(rrset->getName()).arg(rrset->getType()).
[ + - ][ + - ]
[ + - ]
1676 [ + - ][ + - ]: 1642 : arg(zone_).arg(rrclass_).arg(accessor_->getDBName());
[ + - ][ + - ]
1677 : : return (rrset);
1678 [ - + ]: 8 : } catch (const Exception& ex) {
1679 [ - + ][ + - ]: 8 : LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADR_BADDATA).
[ - + ]
1680 [ - + ][ - + ]: 4 : arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
[ - + ][ - + ]
[ - + ]
1681 [ - + ][ - + ]: 8 : arg(begin_).arg(end_).arg(ex.what());
[ - + ]
1682 [ - + ][ - + ]: 8 : isc_throw(DataSourceError, "Failed to create RRset from diff on "
[ - + ][ - + ]
[ - + ]
1683 : : << accessor_->getDBName());
1684 [ + + ][ + + ]: 5010 : }
1685 : : }
1686 : :
1687 : : private:
1688 : : boost::shared_ptr<Accessor> accessor_;
1689 : : const Name zone_;
1690 : : const RRClass rrclass_;
1691 : : Accessor::IteratorContextPtr context_;
1692 : : const uint32_t begin_;
1693 : : const uint32_t end_;
1694 : : bool finished_;
1695 : : };
1696 : :
1697 : : // The JournalReader factory
1698 : : pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
1699 : 22 : DatabaseClient::getJournalReader(const isc::dns::Name& zone,
1700 : : uint32_t begin_serial,
1701 : : uint32_t end_serial) const
1702 : : {
1703 : 22 : boost::shared_ptr<DatabaseAccessor> jnl_accessor(accessor_->clone());
1704 [ + - ][ + - ]: 22 : const pair<bool, int> zoneinfo(jnl_accessor->getZone(zone.toText()));
1705 [ + + ]: 22 : if (!zoneinfo.first) {
1706 : : return (pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>(
1707 : : ZoneJournalReader::NO_SUCH_ZONE,
1708 : : ZoneJournalReaderPtr()));
1709 : : }
1710 : :
1711 : : try {
1712 : : const pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> ret(
1713 : : ZoneJournalReader::SUCCESS,
1714 : : ZoneJournalReaderPtr(new DatabaseJournalReader(jnl_accessor,
1715 : : zone,
1716 : : zoneinfo.second,
1717 : : rrclass_,
1718 : : begin_serial,
1719 [ + - ]: 23 : end_serial)));
1720 [ + - ][ + - ]: 30 : LOG_DEBUG(logger, DBG_TRACE_BASIC,
[ + - ]
1721 [ + - ][ + - ]: 15 : DATASRC_DATABASE_JOURNALREADER_START).arg(zone).arg(rrclass_).
[ + - ]
1722 [ + - ][ + - ]: 30 : arg(jnl_accessor->getDBName()).arg(begin_serial).arg(end_serial);
[ + - ][ + - ]
1723 : : return (ret);
1724 [ - + ][ + - ]: 8 : } catch (const NoSuchSerial&) {
1725 : : return (pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>(
1726 : : ZoneJournalReader::NO_SUCH_VERSION,
1727 : : ZoneJournalReaderPtr()));
1728 : : }
1729 : : }
1730 : : }
1731 : 1871 : }
|