memory_datasrc.cc 39.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

Michal Vaner's avatar
Michal Vaner committed
15
#include <map>
Michal Vaner's avatar
Michal Vaner committed
16
#include <cassert>
Michal Vaner's avatar
Michal Vaner committed
17
#include <boost/shared_ptr.hpp>
Michal Vaner's avatar
Michal Vaner committed
18
#include <boost/bind.hpp>
19
#include <boost/foreach.hpp>
Michal Vaner's avatar
Michal Vaner committed
20

21
22
#include <exceptions/exceptions.h>

23
#include <dns/name.h>
24
#include <dns/rrclass.h>
chenzhengzhang's avatar
chenzhengzhang committed
25
#include <dns/rrsetlist.h>
Michal Vaner's avatar
Michal Vaner committed
26
#include <dns/masterload.h>
27

28
#include <datasrc/memory_datasrc.h>
Michal Vaner's avatar
Michal Vaner committed
29
#include <datasrc/rbtree.h>
30
#include <datasrc/logger.h>
31
32
#include <datasrc/iterator.h>
#include <datasrc/data_source.h>
33
34
35
#include <datasrc/factory.h>

#include <cc/data.h>
36
37
38

using namespace std;
using namespace isc::dns;
39
using namespace isc::data;
40
41
42
43

namespace isc {
namespace datasrc {

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
namespace {
// Some type aliases
/*
 * Each domain consists of some RRsets. They will be looked up by the
 * RRType.
 *
 * The use of map is questionable with regard to performance - there'll
 * be usually only few RRsets in the domain, so the log n benefit isn't
 * much and a vector/array might be faster due to its simplicity and
 * continuous memory location. But this is unlikely to be a performance
 * critical place and map has better interface for the lookups, so we use
 * that.
 */
typedef map<RRType, ConstRRsetPtr> Domain;
typedef Domain::value_type DomainPair;
typedef boost::shared_ptr<Domain> DomainPtr;
// The tree stores domains
typedef RBTree<Domain> DomainTree;
typedef RBNode<Domain> DomainNode;
}

65
66
// Private data and hidden methods of InMemoryZoneFinder
struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
Michal Vaner's avatar
Michal Vaner committed
67
    // Constructor
68
    InMemoryZoneFinderImpl(const RRClass& zone_class, const Name& origin) :
69
        zone_class_(zone_class), origin_(origin), origin_data_(NULL),
70
        domains_(true)
71
72
73
74
75
76
    {
        // We create the node for origin (it needs to exist anyway in future)
        domains_.insert(origin, &origin_data_);
        DomainPtr origin_domain(new Domain);
        origin_data_->setData(origin_domain);
    }
77
    static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
78
79
80
81
82
83
84

    // Information about the zone
    RRClass zone_class_;
    Name origin_;
    DomainNode* origin_data_;
    string file_name_;

Michal Vaner's avatar
Michal Vaner committed
85
    // The actual zone data
Michal Vaner's avatar
Michal Vaner committed
86
    DomainTree domains_;
Michal Vaner's avatar
Michal Vaner committed
87

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    // Add the necessary magic for any wildcard contained in 'name'
    // (including itself) to be found in the zone.
    //
    // In order for wildcard matching to work correctly in find(),
    // we must ensure that a node for the wildcarding level exists in the
    // backend RBTree.
    // E.g. if the wildcard name is "*.sub.example." then we must ensure
    // that "sub.example." exists and is marked as a wildcard level.
    // Note: the "wildcarding level" is for the parent name of the wildcard
    // name (such as "sub.example.").
    //
    // We also perform the same trick for empty wild card names possibly
    // contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
    void addWildcards(DomainTree& domains, const Name& name) {
        Name wname(name);
        const unsigned int labels(wname.getLabelCount());
        const unsigned int origin_labels(origin_.getLabelCount());
        for (unsigned int l = labels;
             l > origin_labels;
             --l, wname = wname.split(1)) {
            if (wname.isWildcard()) {
109
                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_WILDCARD).
110
                    arg(name);
111
112
                // Ensure a separate level exists for the "wildcarding" name,
                // and mark the node as "wild".
113
114
115
                DomainNode* node;
                DomainTree::Result result(domains.insert(wname.split(1),
                                                         &node));
116
117
                assert(result == DomainTree::SUCCESS ||
                       result == DomainTree::ALREADYEXISTS);
118
                node->setFlag(DOMAINFLAG_WILD);
119

120
121
122
123
                // Ensure a separate level exists for the wildcard name.
                // Note: for 'name' itself we do this later anyway, but the
                // overhead should be marginal because wildcard names should
                // be rare.
124
125
126
127
128
129
130
                result = domains.insert(wname, &node);
                assert(result == DomainTree::SUCCESS ||
                       result == DomainTree::ALREADYEXISTS);
            }
        }
    }

131
132
133
134
135
136
137
    /*
     * Does some checks in context of the data that are already in the zone.
     * Currently checks for forbidden combinations of RRsets in the same
     * domain (CNAME+anything, DNAME+NS).
     *
     * If such condition is found, it throws AddError.
     */
138
139
    void contextCheck(const ConstRRsetPtr& rrset,
                      const DomainPtr& domain) const {
140
141
142
        // Ensure CNAME and other type of RR don't coexist for the same
        // owner name.
        if (rrset->getType() == RRType::CNAME()) {
143
            // TODO: this check will become incorrect when we support DNSSEC
144
145
146
            // (depending on how we support DNSSEC).  We should revisit it
            // at that point.
            if (!domain->empty()) {
147
                LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
148
                    arg(rrset->getName());
149
150
151
152
                isc_throw(AddError, "CNAME can't be added with other data for "
                          << rrset->getName());
            }
        } else if (domain->find(RRType::CNAME()) != domain->end()) {
153
            LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset->getName());
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
            isc_throw(AddError, "CNAME and " << rrset->getType() <<
                      " can't coexist for " << rrset->getName());
        }

        /*
         * Similar with DNAME, but it must not coexist only with NS and only in
         * non-apex domains.
         * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
         */
        if (rrset->getName() != origin_ &&
            // Adding DNAME, NS already there
            ((rrset->getType() == RRType::DNAME() &&
            domain->find(RRType::NS()) != domain->end()) ||
            // Adding NS, DNAME already there
            (rrset->getType() == RRType::NS() &&
            domain->find(RRType::DNAME()) != domain->end())))
        {
171
            LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset->getName());
172
173
174
175
176
            isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
                "domain " << rrset->getName());
        }
    }

177
178
    // Validate rrset before adding it to the zone.  If something is wrong
    // it throws an exception.  It doesn't modify the zone, and provides
JINMEI Tatuya's avatar
JINMEI Tatuya committed
179
    // the strong exception guarantee.
180
    void addValidation(const ConstRRsetPtr rrset) {
Michal Vaner's avatar
Michal Vaner committed
181
182
183
        if (!rrset) {
            isc_throw(NullRRset, "The rrset provided is NULL");
        }
184
185
        // Check for singleton RRs. It should probably handled at a different
        // in future.
186
187
188
189
190
191
        if ((rrset->getType() == RRType::CNAME() ||
            rrset->getType() == RRType::DNAME()) &&
            rrset->getRdataCount() > 1)
        {
            // XXX: this is not only for CNAME or DNAME. We should generalize
            // this code for all other "singleton RR types" (such as SOA) in a
192
            // separate task.
193
            LOG_ERROR(logger, DATASRC_MEM_SINGLETON).arg(rrset->getName()).
194
                arg(rrset->getType());
195
196
197
198
            isc_throw(AddError, "multiple RRs of singleton type for "
                      << rrset->getName());
        }

199
        NameComparisonResult compare(origin_.compare(rrset->getName()));
Michal Vaner's avatar
Michal Vaner committed
200
201
202
        if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
            compare.getRelation() != NameComparisonResult::EQUAL)
        {
203
            LOG_ERROR(logger, DATASRC_MEM_OUT_OF_ZONE).arg(rrset->getName()).
204
                arg(origin_);
205
            isc_throw(OutOfZone, "The name " << rrset->getName() <<
Michal Vaner's avatar
Michal Vaner committed
206
207
                " is not contained in zone " << origin_);
        }
208
209
210
211
212
213
214
215
216

        // Some RR types do not really work well with a wildcard.
        // Even though the protocol specifically doesn't completely ban such
        // usage, we refuse to load a zone containing such RR in order to
        // keep the lookup logic simpler and more predictable.
        // See RFC4592 and (for DNAME) draft-ietf-dnsext-rfc2672bis-dname
        // for more technical background.  Note also that BIND 9 refuses
        // NS at a wildcard, so in that sense we simply provide compatible
        // behavior.
217
        if (rrset->getName().isWildcard()) {
218
            if (rrset->getType() == RRType::NS()) {
219
220
                LOG_ERROR(logger, DATASRC_MEM_WILDCARD_NS).
                    arg(rrset->getName());
221
                isc_throw(AddError, "Invalid NS owner name (wildcard): " <<
222
                          rrset->getName());
223
224
            }
            if (rrset->getType() == RRType::DNAME()) {
225
226
                LOG_ERROR(logger, DATASRC_MEM_WILDCARD_DNAME).
                    arg(rrset->getName());
227
                isc_throw(AddError, "Invalid DNAME owner name (wildcard): " <<
228
                          rrset->getName());
229
230
            }
        }
231
232
233
234
235
236
    }

    /*
     * Implementation of longer methods. We put them here, because the
     * access is without the impl_-> and it will get inlined anyway.
     */
237
    // Implementation of InMemoryZoneFinder::add
238
    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
239
240
241
242
243
        // Sanitize input.  This will cause an exception to be thrown
        // if the input RRset is empty.
        addValidation(rrset);

        // OK, can add the RRset.
244
245
        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
            arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
246
247
248

        // Add wildcards possibly contained in the owner name to the domain
        // tree.
249
250
        // Note: this can throw an exception, breaking strong exception
        // guarantee.  (see also the note for contextCheck() below).
251
        addWildcards(*domains, rrset->getName());
252

Michal Vaner's avatar
Michal Vaner committed
253
254
        // Get the node
        DomainNode* node;
255
256
257
258
        DomainTree::Result result = domains->insert(rrset->getName(), &node);
        // Just check it returns reasonable results
        assert((result == DomainTree::SUCCESS ||
                result == DomainTree::ALREADYEXISTS) && node!= NULL);
Michal Vaner's avatar
Michal Vaner committed
259
260
261
262
263
264
265
266
267
268
269

        // Now get the domain
        DomainPtr domain;
        // It didn't exist yet, create it
        if (node->isEmpty()) {
            domain.reset(new Domain);
            node->setData(domain);
        } else { // Get existing one
            domain = node->getData();
        }

270
        // Checks related to the surrounding data.
271
272
273
274
        // Note: when the check fails and the exception is thrown, it may
        // break strong exception guarantee.  At the moment we prefer
        // code simplicity and don't bother to introduce complicated
        // recovery code.
275
        contextCheck(rrset, domain);
276

Michal Vaner's avatar
Michal Vaner committed
277
278
279
        // Try inserting the rrset there
        if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
            // Ok, we just put it in
280
281
282
283
284

            // If this RRset creates a zone cut at this node, mark the node
            // indicating the need for callback in find().
            if (rrset->getType() == RRType::NS() &&
                rrset->getName() != origin_) {
285
                node->setFlag(DomainNode::FLAG_CALLBACK);
286
287
            // If it is DNAME, we have a callback as well here
            } else if (rrset->getType() == RRType::DNAME()) {
288
                node->setFlag(DomainNode::FLAG_CALLBACK);
289
290
            }

Michal Vaner's avatar
Michal Vaner committed
291
292
293
294
295
296
            return (result::SUCCESS);
        } else {
            // The RRSet of given type was already there
            return (result::EXIST);
        }
    }
Michal Vaner's avatar
Michal Vaner committed
297

298
299
300
301
302
303
304
    /*
     * Same as above, but it checks the return value and if it already exists,
     * it throws.
     */
    void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
            switch (add(set, domains)) {
                case result::EXIST:
305
306
                    LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
                        arg(set->getName()).arg(set->getType());
307
308
309
310
311
312
313
314
315
                    isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
                        set->toText());
                case result::SUCCESS:
                    return;
                default:
                    assert(0);
            }
    }

316
317
318
319
320
321
    // Maintain intermediate data specific to the search context used in
    /// \c find().
    ///
    /// It will be passed to \c zonecutCallback() and record a possible
    /// zone cut node and related RRset (normally NS or DNAME).
    struct FindState {
322
323
324
325
        FindState(FindOptions options) :
            zonecut_node_(NULL),
            dname_node_(NULL),
            options_(options)
326
327
        {}
        const DomainNode* zonecut_node_;
328
        const DomainNode* dname_node_;
329
330
        ConstRRsetPtr rrset_;
        const FindOptions options_;
331
332
    };

333
334
335
336
337
    // A callback called from possible zone cut nodes and nodes with DNAME.
    // This will be passed from the \c find() method to \c RBTree::find().
    static bool cutCallback(const DomainNode& node, FindState* state) {
        // We need to look for DNAME first, there's allowed case where
        // DNAME and NS coexist in the apex. DNAME is the one to notice,
338
339
        // the NS is authoritative, not delegation (corner case explicitly
        // allowed by section 3 of 2672)
340
341
342
        const Domain::const_iterator foundDNAME(node.getData()->find(
            RRType::DNAME()));
        if (foundDNAME != node.getData()->end()) {
343
344
            LOG_DEBUG(logger, DBG_TRACE_DETAILED,
                      DATASRC_MEM_DNAME_ENCOUNTERED);
345
346
            state->dname_node_ = &node;
            state->rrset_ = foundDNAME->second;
347
348
349
350
351
352
            // No more processing below the DNAME (RFC 2672, section 3
            // forbids anything to exist below it, so there's no need
            // to actually search for it). This is strictly speaking
            // a different way than described in 4.1 of that RFC,
            // but because of the assumption in section 3, it has the
            // same behaviour.
353
            return (true);
354
355
        }

356
357
358
359
        // Look for NS
        const Domain::const_iterator foundNS(node.getData()->find(
            RRType::NS()));
        if (foundNS != node.getData()->end()) {
360
361
362
363
364
365
            // We perform callback check only for the highest zone cut in the
            // rare case of nested zone cuts.
            if (state->zonecut_node_ != NULL) {
                return (false);
            }

366
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
367

368
369
370
371
372
373
            // BIND 9 checks if this node is not the origin.  That's probably
            // because it can support multiple versions for dynamic updates
            // and IXFR, and it's possible that the callback is called at
            // the apex and the DNAME doesn't exist for a particular version.
            // It cannot happen for us (at least for now), so we don't do
            // that check.
374
            state->zonecut_node_ = &node;
375
            state->rrset_ = foundNS->second;
376
377
378
379

            // Unless glue is allowed the search stops here, so we return
            // false; otherwise return true to continue the search.
            return ((state->options_ & FIND_GLUE_OK) == 0);
380
381
        }

JINMEI Tatuya's avatar
JINMEI Tatuya committed
382
383
384
        // This case should not happen because we enable callback only
        // when we add an RR searched for above.
        assert(0);
385
386
387
        // This is here to avoid warning (therefore compilation error)
        // in case assert is turned off. Otherwise we could get "Control
        // reached end of non-void function".
388
        return (false);
389
390
    }

391
392
393
394
395
396
397
398
399
400
401
402
    /*
     * Prepares a rrset to be return as a result.
     *
     * If rename is false, it returns the one provided. If it is true, it
     * creates a new rrset with the same data but with provided name.
     * It is designed for wildcard case, where we create the rrsets
     * dynamically.
     */
    static ConstRRsetPtr prepareRRset(const Name& name, const ConstRRsetPtr&
        rrset, bool rename)
    {
        if (rename) {
403
            LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_RENAME).
404
                arg(rrset->getName()).arg(name);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
405
406
407
408
409
410
            /*
             * We lose a signature here. But it would be wrong anyway, because
             * the name changed. This might turn out to be unimportant in
             * future, because wildcards will probably be handled somehow
             * by DNSSEC.
             */
411
412
413
414
415
416
417
418
419
420
421
            RRsetPtr result(new RRset(name, rrset->getClass(),
                rrset->getType(), rrset->getTTL()));
            for (RdataIteratorPtr i(rrset->getRdataIterator()); !i->isLast();
                i->next()) {
                result->addRdata(i->getCurrent());
            }
            return (result);
        } else {
            return (rrset);
        }
    }
chenzhengzhang's avatar
chenzhengzhang committed
422

423
    // Implementation of InMemoryZoneFinder::find
424
425
426
    FindResult find(const Name& name, RRType type,
                    std::vector<ConstRRsetPtr> *target,
                    const FindOptions options) const
427
    {
428
429
        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FIND).arg(name).
            arg(type);
Michal Vaner's avatar
Michal Vaner committed
430
        // Get the node
Michal Vaner's avatar
Michal Vaner committed
431
        DomainNode* node(NULL);
432
        FindState state(options);
433
        RBTreeNodeChain<Domain> node_path;
434
        bool rename(false);
435
        switch (domains_.find(name, &node, node_path, cutCallback, &state)) {
Michal Vaner's avatar
Michal Vaner committed
436
            case DomainTree::PARTIALMATCH:
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
                /*
                 * In fact, we could use a single variable instead of
                 * dname_node_ and zonecut_node_. But then we would need
                 * to distinquish these two cases by something else and
                 * it seemed little more confusing to me when I wrote it.
                 *
                 * Usually at most one of them will be something else than
                 * NULL (it might happen both are NULL, in which case we
                 * consider it NOT FOUND). There's one corner case when
                 * both might be something else than NULL and it is in case
                 * there's a DNAME under a zone cut and we search in
                 * glue OK mode ‒ in that case we don't stop on the domain
                 * with NS and ignore it for the answer, but it gets set
                 * anyway. Then we find the DNAME and we need to act by it,
                 * therefore we first check for DNAME and then for NS. In
452
                 * all other cases it doesn't matter, as at least one of them
453
454
                 * is NULL.
                 */
455
                if (state.dname_node_ != NULL) {
456
                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
457
                        arg(state.rrset_->getName());
458
459
                    // We were traversing a DNAME node (and wanted to go
                    // lower below it), so return the DNAME
460
461
                    return (FindResult(DNAME, prepareRRset(name, state.rrset_,
                        rename)));
462
                }
463
                if (state.zonecut_node_ != NULL) {
464
                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
465
                        arg(state.rrset_->getName());
466
467
                    return (FindResult(DELEGATION, prepareRRset(name,
                        state.rrset_, rename)));
468
                }
469
470
471
472
473
474

                // If the RBTree search stopped at a node for a super domain
                // of the search name, it means the search name exists in
                // the zone but is empty.  Treat it as NXRRSET.
                if (node_path.getLastComparisonResult().getRelation() ==
                    NameComparisonResult::SUPERDOMAIN) {
475
476
                    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
                        arg(node_path.getAbsoluteName()).arg(name);
477
478
479
                    return (FindResult(NXRRSET, ConstRRsetPtr()));
                }

480
481
                /*
                 * No redirection anywhere. Let's try if it is a wildcard.
482
483
484
485
486
                 *
                 * The wildcard is checked after the empty non-terminal domain
                 * case above, because if that one triggers, it means we should
                 * not match according to 4.3.3 of RFC 1034 (the query name
                 * is known to exist).
487
488
                 */
                if (node->getFlag(DOMAINFLAG_WILD)) {
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
                    /* Should we cancel this match?
                     *
                     * If we compare with some node and get a common ancestor,
                     * it might mean we are comparing with a non-wildcard node.
                     * In that case, we check which part is common. If we have
                     * something in common that lives below the node we got
                     * (the one above *), then we should cancel the match
                     * according to section 4.3.3 of RFC 1034 (as the name
                     * between the wildcard domain and the query name is known
                     * to exist).
                     *
                     * Because the way the tree stores relative names, we will
                     * have exactly one common label (the ".") in case we have
                     * nothing common under the node we got and we will get
                     * more common labels otherwise (yes, this relies on the
504
                     * internal RBTree structure, which leaks out through this
505
506
507
508
509
510
511
512
513
                     * little bit).
                     *
                     * If the empty non-terminal node actually exists in the
                     * tree, then this cancellation is not needed, because we
                     * will not get here at all.
                     */
                    if (node_path.getLastComparisonResult().getRelation() ==
                        NameComparisonResult::COMMONANCESTOR && node_path.
                        getLastComparisonResult().getCommonLabels() > 1) {
514
                        LOG_DEBUG(logger, DBG_TRACE_DATA,
515
                                     DATASRC_MEM_WILDCARD_CANCEL).arg(name);
516
517
                        return (FindResult(NXDOMAIN, ConstRRsetPtr()));
                    }
518
519
520
521
522
523
524
                    Name wildcard(Name("*").concatenate(
                        node_path.getAbsoluteName()));
                    DomainTree::Result result(domains_.find(wildcard, &node));
                    /*
                     * Otherwise, why would the DOMAINFLAG_WILD be there if
                     * there was no wildcard under it?
                     */
525
                    assert(result == DomainTree::EXACTMATCH);
526
527
528
529
                    /*
                     * We have the wildcard node now. Jump below the switch,
                     * where handling of the common (exact-match) case is.
                     *
530
                     * However, rename it to the searched name.
531
                     */
532
                    rename = true;
533
534
                    break;
                }
535
536

                // fall through
Michal Vaner's avatar
Michal Vaner committed
537
            case DomainTree::NOTFOUND:
538
                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).
539
                    arg(name);
Michal Vaner's avatar
Michal Vaner committed
540
541
542
543
544
                return (FindResult(NXDOMAIN, ConstRRsetPtr()));
            case DomainTree::EXACTMATCH: // This one is OK, handle it
                break;
            default:
                assert(0);
Michal Vaner's avatar
Michal Vaner committed
545
        }
546
        assert(node != NULL);
547
548
549
550

        // If there is an exact match but the node is empty, it's equivalent
        // to NXRRSET.
        if (node->isEmpty()) {
551
552
            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
                arg(name);
553
554
            return (FindResult(NXRRSET, ConstRRsetPtr()));
        }
Michal Vaner's avatar
Michal Vaner committed
555

556
557
558
        Domain::const_iterator found;

        // If the node callback is enabled, this may be a zone cut.  If it
559
        // has a NS RR, we should return a delegation, but not in the apex.
560
561
562
563
        // There is one exception: the case for DS query, which should always
        // be considered in-zone lookup.
        if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_ &&
            type != RRType::DS()) {
564
565
            found = node->getData()->find(RRType::NS());
            if (found != node->getData()->end()) {
566
567
                LOG_DEBUG(logger, DBG_TRACE_DATA,
                          DATASRC_MEM_EXACT_DELEGATION).arg(name);
568
569
                return (FindResult(DELEGATION, prepareRRset(name,
                    found->second, rename)));
570
571
572
            }
        }

chenzhengzhang's avatar
chenzhengzhang committed
573
        // handle type any query
574
        if (target != NULL && !node->getData()->empty()) {
575
576
            // Empty domain will be handled as NXRRSET by normal processing
            for (found = node->getData()->begin();
577
                 found != node->getData()->end(); ++found)
578
            {
579
                target->push_back(prepareRRset(name, found->second, rename));
chenzhengzhang's avatar
chenzhengzhang committed
580
            }
581
582
            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
                arg(name);
583
            return (FindResult(SUCCESS, ConstRRsetPtr()));
chenzhengzhang's avatar
chenzhengzhang committed
584
585
        }

586
        found = node->getData()->find(type);
Michal Vaner's avatar
Michal Vaner committed
587
588
        if (found != node->getData()->end()) {
            // Good, it is here
589
            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
590
                arg(type);
591
592
            return (FindResult(SUCCESS, prepareRRset(name, found->second,
                rename)));
Michal Vaner's avatar
Michal Vaner committed
593
        } else {
594
            // Next, try CNAME.
595
596
            found = node->getData()->find(RRType::CNAME());
            if (found != node->getData()->end()) {
597
                LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
598
599
                return (FindResult(CNAME, prepareRRset(name, found->second,
                    rename)));
600
            }
Michal Vaner's avatar
Michal Vaner committed
601
        }
602
        // No exact match or CNAME.  Return NXRRSET.
603
604
        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
            arg(name);
605
        return (FindResult(NXRRSET, ConstRRsetPtr()));
Michal Vaner's avatar
Michal Vaner committed
606
    }
607
608
};

609
610
InMemoryZoneFinder::InMemoryZoneFinder(const RRClass& zone_class, const Name& origin) :
    impl_(new InMemoryZoneFinderImpl(zone_class, origin))
611
{
612
    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_CREATE).arg(origin).
613
        arg(zone_class);
614
615
}

616
InMemoryZoneFinder::~InMemoryZoneFinder() {
617
    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_DESTROY).arg(getOrigin()).
618
        arg(getClass());
619
620
621
    delete impl_;
}

622
Name
623
InMemoryZoneFinder::getOrigin() const {
624
625
626
    return (impl_->origin_);
}

627
RRClass
628
InMemoryZoneFinder::getClass() const {
629
630
631
    return (impl_->zone_class_);
}

632
ZoneFinder::FindResult
633
InMemoryZoneFinder::find(const Name& name, const RRType& type,
634
                 const FindOptions options)
chenzhengzhang's avatar
chenzhengzhang committed
635
{
636
637
638
639
640
641
642
643
644
    return (impl_->find(name, type, NULL, options));
}

ZoneFinder::FindResult
InMemoryZoneFinder::findAll(const Name& name,
                            std::vector<ConstRRsetPtr>& target,
                            const FindOptions options)
{
    return (impl_->find(name, RRType::ANY(), &target, options));
chenzhengzhang's avatar
chenzhengzhang committed
645
646
}

647
ZoneFinder::FindNSEC3Result
648
InMemoryZoneFinder::findNSEC3(const Name&, bool) {
649
650
651
652
    isc_throw(NotImplemented, "findNSEC3 is not yet implemented for in memory "
              "data source");
}

Michal Vaner's avatar
Michal Vaner committed
653
result::Result
654
InMemoryZoneFinder::add(const ConstRRsetPtr& rrset) {
655
    return (impl_->add(rrset, &impl_->domains_));
656
657
}

Michal Vaner's avatar
Michal Vaner committed
658
659

void
660
InMemoryZoneFinder::load(const string& filename) {
661
    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
662
        arg(filename);
663
    // Load it into a temporary tree
664
    DomainTree tmp;
665
    masterLoad(filename.c_str(), getOrigin(), getClass(),
666
        boost::bind(&InMemoryZoneFinderImpl::addFromLoad, impl_, _1, &tmp));
667
    // If it went well, put it inside
668
    impl_->file_name_ = filename;
669
670
    tmp.swap(impl_->domains_);
    // And let the old data die with tmp
671
672
}

673
void
674
InMemoryZoneFinder::swap(InMemoryZoneFinder& zone_finder) {
675
    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_SWAP).arg(getOrigin()).
676
677
        arg(zone_finder.getOrigin());
    std::swap(impl_, zone_finder.impl_);
678
679
680
}

const string
681
InMemoryZoneFinder::getFileName() const {
682
683
684
    return (impl_->file_name_);
}

685
686
687
688
689
690
isc::dns::Name
InMemoryZoneFinder::findPreviousName(const isc::dns::Name&) const {
    isc_throw(NotImplemented, "InMemory data source doesn't support DNSSEC "
              "yet, can't find previous name");
}

691
/// Implementation details for \c InMemoryClient hidden from the public
692
693
/// interface.
///
694
/// For now, \c InMemoryClient only contains a \c ZoneTable object, which
695
/// consists of (pointers to) \c InMemoryZoneFinder objects, we may add more
696
/// member variables later for new features.
697
class InMemoryClient::InMemoryClientImpl {
698
public:
699
    InMemoryClientImpl() : zone_count(0) {}
700
    unsigned int zone_count;
701
702
703
    ZoneTable zone_table;
};

704
InMemoryClient::InMemoryClient() : impl_(new InMemoryClientImpl)
705
706
{}

707
InMemoryClient::~InMemoryClient() {
708
709
710
    delete impl_;
}

711
unsigned int
712
InMemoryClient::getZoneCount() const {
713
714
715
    return (impl_->zone_count);
}

716
result::Result
717
718
InMemoryClient::addZone(ZoneFinderPtr zone_finder) {
    if (!zone_finder) {
719
        isc_throw(InvalidParameter,
720
                  "Null pointer is passed to InMemoryClient::addZone()");
721
    }
722

723
    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_ADD_ZONE).
724
        arg(zone_finder->getOrigin()).arg(zone_finder->getClass().toText());
725

726
    const result::Result result = impl_->zone_table.addZone(zone_finder);
727
728
729
730
    if (result == result::SUCCESS) {
        ++impl_->zone_count;
    }
    return (result);
731
732
}

733
734
InMemoryClient::FindResult
InMemoryClient::findZone(const isc::dns::Name& name) const {
735
    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_FIND_ZONE).arg(name);
736
737
    ZoneTable::FindResult result(impl_->zone_table.findZone(name));
    return (FindResult(result.code, result.zone));
738
}
739
740
741
742
743
744
745
746
747

namespace {

class MemoryIterator : public ZoneIterator {
private:
    RBTreeNodeChain<Domain> chain_;
    Domain::const_iterator dom_iterator_;
    const DomainTree& tree_;
    const DomainNode* node_;
748
749
750
    // Only used when separate_rrs_ is true
    RdataIteratorPtr rdata_iterator_;
    bool separate_rrs_;
751
752
    bool ready_;
public:
753
    MemoryIterator(const DomainTree& tree, const Name& origin, bool separate_rrs) :
754
        tree_(tree),
755
        separate_rrs_(separate_rrs),
756
757
758
759
760
761
762
763
764
765
766
767
768
769
        ready_(true)
    {
        // Find the first node (origin) and preserve the node chain for future
        // searches
        DomainTree::Result result(tree_.find<void*>(origin, &node_, chain_,
                                                    NULL, NULL));
        // It can't happen that the origin is not in there
        if (result != DomainTree::EXACTMATCH) {
            isc_throw(Unexpected,
                      "In-memory zone corrupted, missing origin node");
        }
        // Initialize the iterator if there's somewhere to point to
        if (node_ != NULL && node_->getData() != DomainPtr()) {
            dom_iterator_ = node_->getData()->begin();
770
771
            if (separate_rrs_ && dom_iterator_ != node_->getData()->end()) {
                rdata_iterator_ = dom_iterator_->second->getRdataIterator();
772
            }
773
774
        }
    }
Jelte Jansen's avatar
Jelte Jansen committed
775

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
    virtual ConstRRsetPtr getNextRRset() {
        if (!ready_) {
            isc_throw(Unexpected, "Iterating past the zone end");
        }
        /*
         * This cycle finds the first nonempty node with yet unused RRset.
         * If it is NULL, we run out of nodes. If it is empty, it doesn't
         * contain any RRsets. If we are at the end, just get to next one.
         */
        while (node_ != NULL && (node_->getData() == DomainPtr() ||
                                 dom_iterator_ == node_->getData()->end())) {
            node_ = tree_.nextNode(chain_);
            // If there's a node, initialize the iterator and check next time
            // if the map is empty or not
            if (node_ != NULL && node_->getData() != NULL) {
                dom_iterator_ = node_->getData()->begin();
792
793
794
795
                // New RRset, so get a new rdata iterator
                if (separate_rrs_) {
                    rdata_iterator_ = dom_iterator_->second->getRdataIterator();
                }
796
797
798
799
800
            }
        }
        if (node_ == NULL) {
            // That's all, folks
            ready_ = false;
Jelte Jansen's avatar
Jelte Jansen committed
801
            return (ConstRRsetPtr());
802
803
        }

804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
        if (separate_rrs_) {
            // For separate rrs, reconstruct a new RRset with just the
            // 'current' rdata
            RRsetPtr result(new RRset(dom_iterator_->second->getName(),
                                      dom_iterator_->second->getClass(),
                                      dom_iterator_->second->getType(),
                                      dom_iterator_->second->getTTL()));
            result->addRdata(rdata_iterator_->getCurrent());
            rdata_iterator_->next();
            if (rdata_iterator_->isLast()) {
                // all used up, next.
                ++dom_iterator_;
                // New RRset, so get a new rdata iterator, but only if this
                // was not the final RRset in the chain
                if (dom_iterator_ != node_->getData()->end()) {
                    rdata_iterator_ = dom_iterator_->second->getRdataIterator();
                }
            }
            return (result);
        } else {
            // The iterator points to the next yet unused RRset now
            ConstRRsetPtr result(dom_iterator_->second);

            // This one is used, move it to the next time for next call
            ++dom_iterator_;

            return (result);
        }
832
    }
833

834
    virtual ConstRRsetPtr getSOA() const {
835
836
        isc_throw(NotImplemented, "Not imelemented");
    }
837
838
839
840
841
};

} // End of anonymous namespace

ZoneIteratorPtr
842
InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
    ZoneTable::FindResult result(impl_->zone_table.findZone(name));
    if (result.code != result::SUCCESS) {
        isc_throw(DataSourceError, "No such zone: " + name.toText());
    }

    const InMemoryZoneFinder*
        zone(dynamic_cast<const InMemoryZoneFinder*>(result.zone.get()));
    if (zone == NULL) {
        /*
         * TODO: This can happen only during some of the tests and only as
         * a temporary solution. This should be fixed by #1159 and then
         * this cast and check shouldn't be necessary. We don't have
         * test for handling a "can not happen" condition.
         */
        isc_throw(Unexpected, "The zone at " + name.toText() +
                  " is not InMemoryZoneFinder");
    }
860
861
    return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name,
                                               separate_rrs)));
862
863
}

864
ZoneUpdaterPtr
865
InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
866
    isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
867
}
868

869
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
870
871
872
873
874
875
InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
                                 uint32_t) const
{
    isc_throw(isc::NotImplemented, "Journaling isn't supported for "
              "in memory data source");
}
876
877
878
879
880
881
882
883
884
885
886
887

namespace {
// convencience function to add an error message to a list of those
// (TODO: move functions like these to some util lib?)
void
addError(ElementPtr errors, const std::string& error) {
    if (errors != ElementPtr() && errors->getType() == Element::list) {
        errors->add(Element::create(error));
    }
}

/// Check if the given element exists in the map, and if it is a string
888
bool
889
890
891
892
893
894
checkConfigElementString(ConstElementPtr config, const std::string& name,
                         ElementPtr errors)
{
    if (!config->contains(name)) {
        addError(errors,
                 "Config for memory backend does not contain a '"
895
                 +name+
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
                 "' value");
        return false;
    } else if (!config->get(name) ||
               config->get(name)->getType() != Element::string) {
        addError(errors, "value of " + name +
                 " in memory backend config is not a string");
        return false;
    } else {
        return true;
    }
}

bool
checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
    bool result = true;
    if (!config || config->getType() != Element::map) {
        addError(errors, "Elements in memory backend's zone list must be maps");
        result = false;
    } else {
        if (!checkConfigElementString(config, "origin", errors)) {
            result = false;
        }
        if (!checkConfigElementString(config, "file", errors)) {
            result = false;
        }
        // we could add some existence/readabilty/parsability checks here
        // if we want
    }
    return result;
}

bool
checkConfig(ConstElementPtr config, ElementPtr errors) {
    /* Specific configuration is under discussion, right now this accepts
     * the 'old' configuration, see [TODO]
     * So for memory datasource, we get a structure like this:
     * { "type": string ("memory"),
     *   "class": string ("IN"/"CH"/etc),
     *   "zones": list
     * }
     * Zones list is a list of maps:
     * { "origin": string,
     *     "file": string
     * }
     *
     * At this moment we cannot be completely sure of the contents of the
     * structure, so we have to do some more extensive tests than should
     * strictly be necessary (e.g. existence and type of elements)
     */
    bool result = true;

    if (!config || config->getType() != Element::map) {
        addError(errors, "Base config for memory backend must be a map");
        result = false;
    } else {
        if (!checkConfigElementString(config, "type", errors)) {
            result = false;
        } else {
            if (config->get("type")->stringValue() != "memory") {
                addError(errors,
                         "Config for memory backend is not of type \"memory\"");
                result = false;
            }
        }
        if (!checkConfigElementString(config, "class", errors)) {
            result = false;
        } else {
            try {
                RRClass rrc(config->get("class")->stringValue());
            } catch (const isc::Exception& rrce) {
                addError(errors,
                         "Error parsing class config for memory backend: " +
                         std::string(rrce.what()));
                result = false;
            }
        }
        if (!config->contains("zones")) {
            addError(errors, "No 'zones' element in memory backend config");
            result = false;
        } else if (!config->get("zones") ||
                   config->get("zones")->getType() != Element::list) {
            addError(errors, "'zones' element in memory backend config is not a list");
            result = false;
        } else {
            BOOST_FOREACH(ConstElementPtr zone_config,
                          config->get("zones")->listValue()) {
                if (!checkZoneConfig(zone_config, errors)) {
                    result = false;
                }
            }
        }
    }

    return (result);
990
991
992
    return true;
}

993
994
} // end anonymous namespace

995
DataSourceClient *
996
createInstance(isc::data::ConstElementPtr config, std::string& error) {
997
998
    ElementPtr errors(Element::createList());
    if (!checkConfig(config, errors)) {
999
1000
        error = "Configuration error: " + errors->str();
        return (NULL);
1001
    }
1002
1003
1004
1005
1006
    try {
        return (new InMemoryClient());
    } catch (const std::exception& exc) {
        error = std::string("Error creating memory datasource: ") + exc.what();
        return (NULL);
1007
1008
1009
1010
    } catch (...) {
        error = std::string("Error creating memory datasource, "
                            "unknown exception");
        return (NULL);
1011
    }
1012
1013
1014
1015
1016
1017
1018
}

void destroyInstance(DataSourceClient* instance) {
    delete instance;
}


1019
} // end of namespace datasrc
1020
} // end of namespace isc