query_unittest.cc 115 KB
Newer Older
Xie Jiagui's avatar
Xie Jiagui committed
1
// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// 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.

15
#include <boost/bind.hpp>
16
#include <boost/scoped_ptr.hpp>
17
#include <boost/static_assert.hpp>
18

19 20
#include <exceptions/exceptions.h>

21
#include <dns/masterload.h>
22
#include <dns/message.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
23
#include <dns/master_loader.h>
24
#include <dns/name.h>
25
#include <dns/nsec3hash.h>
26
#include <dns/opcode.h>
27
#include <dns/rcode.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
28
#include <dns/rrcollator.h>
29
#include <dns/rrttl.h>
30
#include <dns/rrtype.h>
31
#include <dns/rdataclass.h>
32

Michal Vaner's avatar
Michal Vaner committed
33
#include <datasrc/memory_datasrc.h>
34
#include <datasrc/client_list.h>
35 36 37

#include <auth/query.h>

38
#include <testutils/dnsmessage_test.h>
39

40 41
#include <gtest/gtest.h>

42
#include <cstdlib>
43
#include <fstream>
44 45 46 47
#include <map>
#include <sstream>
#include <vector>

48
using namespace std;
49
using namespace isc::dns;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
50
using namespace isc::dns::rdata;
51 52
using namespace isc::datasrc;
using namespace isc::auth;
53
using namespace isc::testutils;
54

55 56
namespace {

57
// Simple wrapper for a single data source client.
58 59 60 61 62 63 64 65 66
// The list simply delegates all the answers to the single
// client.
class SingletonList : public ClientList {
public:
    SingletonList(DataSourceClient& client) :
        client_(client)
    {}
    virtual FindResult find(const Name& zone, bool exact, bool) const {
        DataSourceClient::FindResult result(client_.findZone(zone));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
67 68 69
        // We don't complicate the tests with real life keepers, but we
        // need to put something to the parameter anyway.
        const boost::shared_ptr<ClientList::FindResult::LifeKeeper> keeper;
70 71
        switch (result.code) {
            case result::SUCCESS:
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
72 73
                return (FindResult(&client_, result.zone_finder, true,
                                   keeper));
74 75
            case result::PARTIALMATCH:
                if (!exact) {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
76 77
                    return (FindResult(&client_, result.zone_finder, false,
                                       keeper));
78 79 80 81 82 83 84 85 86
                }
            default:
                return (FindResult());
        }
    }
private:
    DataSourceClient& client_;
};

JINMEI Tatuya's avatar
JINMEI Tatuya committed
87 88 89 90 91
// These are commonly used data (auto-generated).  There are some exceptional
// data that are only used in a limited scenario, which are defined separately
// below.
#include <auth/tests/example_base_inc.cc>
#include <auth/tests/example_nsec3_inc.cc>
92

JINMEI Tatuya's avatar
JINMEI Tatuya committed
93
// This is used only in one pathological test case.
94 95 96
const char* const zone_ds_txt =
    "example.com. 3600 IN DS 57855 5 1 "
        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
JINMEI Tatuya's avatar
JINMEI Tatuya committed
97

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
98 99
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
100 101
    "www.dname.example.com. 3600 IN CNAME "
    "www.somethinglong.dnametarget.example.com.\n";
102

103 104 105 106 107 108
// NSEC3 for wild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
const char* const nsec3_atwild_txt =
    "ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en\n";

109 110 111 112 113 114
// NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
const char* const nsec3_atcnamewild_txt =
    "k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en\n";

115 116 117 118 119 120 121 122 123 124
// NSEC3 for *.uwild.example.com (will be added on demand not to confuse
// other tests)
const char* const nsec3_wild_txt =
    "b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
// NSEC3 for uwild.example.com. (will be added on demand)
const char* const nsec3_uwild_txt =
    "t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";

Jelte Jansen's avatar
Jelte Jansen committed
125 126
// (Secure) delegation data; Delegation without DS record (and both NSEC
// and NSEC3 denying its existence)
127
// This one will be added on demand
128 129 130 131
const char* const unsigned_delegation_nsec3_txt =
    "q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";

132 133 134 135
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type.  The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
// okay.
136 137 138 139 140 141
string
getCommonRRSIGText(const string& type) {
    return (type +
            string(" 5 3 3600 20000101000000 20000201000000 12345 "
                   "example.com. FAKEFAKEFAKE"));
}
Michal Vaner's avatar
Michal Vaner committed
142

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
void
setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
    *(*it) = rrset;
    ++it;
}

// A helper function that converts a textual form of a single RR into a
// RRsetPtr object.  If it's SOA, origin must be set to its owner name;
// otherwise masterLoad() will reject it.
RRsetPtr
textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
    stringstream ss(text_rrset);
    RRsetPtr rrset;
    vector<RRsetPtr*> rrsets;
    rrsets.push_back(&rrset);
    masterLoad(ss, origin, RRClass::IN(),
160
               boost::bind(setRRset, _1, rrsets.begin()));
161 162 163
    return (rrset);
}

164 165 166 167 168 169
// Setup for faked NSEC3 hash used throughout this test.
class TestNSEC3Hash : public NSEC3Hash {
private:
    typedef map<Name, string> NSEC3HashMap;
    typedef NSEC3HashMap::value_type NSEC3HashPair;
    NSEC3HashMap hash_map_;
170
public:
171
    TestNSEC3Hash() {
172 173
        // (Faked) NSEC3 hash map.  For convenience we use hardcoded built-in
        // map instead of calculating and using actual hash.
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
174 175 176
        // The used hash values are borrowed from RFC5155 examples (they are
        // based on the query name, not that they would correspond directly
        // to the name).
177
        hash_map_[Name("example.com")] = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
178 179
        hash_map_[Name("www.example.com")] =
            "q04jkcevqvmu85r014c7dkba38o0ji5r";
180 181
        hash_map_[Name("nxdomain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
182 183 184 185
        hash_map_[Name("nx.domain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
        hash_map_[Name("domain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
186 187 188 189
        hash_map_[Name("nxdomain2.example.com")] =
            "q00jkcevqvmu85r014c7dkba38o0ji5r";
        hash_map_[Name("nxdomain3.example.com")] =
            "009mhaveqvm6t7vbl5lop2u3t2rp3tom";
190 191
        hash_map_[Name("*.example.com")] =
            "r53bq7cc2uvmubfu5ocmm6pers9tk9en";
192
        hash_map_[Name("unsigned-delegation.example.com")] =
193
            "q81r598950igr1eqvc60aedlq66425b5"; // a bit larger than H(www)
194 195
        hash_map_[Name("*.uwild.example.com")] =
            "b4um86eghhds6nea196smvmlo4ors995";
196 197
        hash_map_[Name("unsigned-delegation-optout.example.com")] =
            "vld46lphhasfapj8og1pglgiasa5o5gt";
198

199 200 201 202 203 204 205
        // For wildcard proofs
        hash_map_[Name("wild.example.com")] =
            "ji6neoaepv8b5o6k4ev33abha8ht9fgc";
        hash_map_[Name("y.wild.example.com")] =
            "0p9mhaveqvm6t7vbl5lop2u3t2rp3ton"; // a bit larger than H(<apex>)
        hash_map_[Name("x.y.wild.example.com")] =
            "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
206 207 208 209
        hash_map_[Name("cnamewild.example.com")] =
            "k8udemvp1j2f7eg6jebps17vp3n8i58h";
        hash_map_[Name("www.cnamewild.example.com")] =
            "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
210

211 212 213 214 215
        // For closest encloser proof for www1.uwild.example.com:
        hash_map_[Name("uwild.example.com")] =
            "t644ebqk9bibcna874givr6joj62mlhv";
        hash_map_[Name("www1.uwild.example.com")] =
            "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
216
    }
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    virtual string calculate(const Name& name) const {
        const NSEC3HashMap::const_iterator found = hash_map_.find(name);
        if (found != hash_map_.end()) {
            return (found->second);
        }
        isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
                  << name);
    }
    virtual bool match(const rdata::generic::NSEC3PARAM&) const {
        return (true);
    }
    virtual bool match(const rdata::generic::NSEC3&) const {
        return (true);
    }
};

class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
public:
    TestNSEC3HashCreator() {}
    virtual isc::dns::NSEC3Hash*
    create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
        return (new TestNSEC3Hash);
    }

    virtual isc::dns::NSEC3Hash*
    create(const isc::dns::rdata::generic::NSEC3&) const {
        return (new TestNSEC3Hash);
    }

    virtual isc::dns::NSEC3Hash*
    create(uint8_t, uint16_t, const uint8_t*, size_t) const {
        return (new TestNSEC3Hash);
    }
};

// This is a mock Zone Finder class for testing.
// It is a derived class of ZoneFinder for the convenient of tests.
// Its find() method emulates the common behavior of protocol compliant
// ZoneFinder classes, but simplifies some minor cases and also supports broken
// behavior.
// For simplicity, most names are assumed to be "in zone"; delegations
// to child zones are identified by the existence of non origin NS records.
// Another special name is "dname.example.com".  Query names under this name
// will result in DNAME.
// This mock zone doesn't handle empty non terminal nodes (if we need to test
// such cases find() should have specialized code for it).
class MockZoneFinder : public ZoneFinder {
public:
    MockZoneFinder() :
        origin_(Name("example.com")),
        bad_signed_delegation_name_("bad-delegation.example.com"),
        dname_name_("dname.example.com"),
        has_SOA_(true),
        has_apex_NS_(true),
        rrclass_(RRClass::IN()),
        include_rrsig_anyway_(false),
        use_nsec3_(false),
        nsec_name_(origin_),
        nsec3_fake_(NULL),
        nsec3_name_(NULL)
    {
        RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
279
        MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
280 281 282 283 284 285 286 287 288 289
                            origin_, rrclass_,
                            MasterLoaderCallbacks::getNullCallbacks(),
                            collator.getCallback());
        loader.load();

        empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
                                                    RRClass::IN(),
                                                    RRType::NSEC(),
                                                    RRTTL(3600)));
    }
290 291
    virtual isc::dns::Name getOrigin() const { return (origin_); }
    virtual isc::dns::RRClass getClass() const { return (rrclass_); }
292 293 294 295 296 297 298 299
    virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
                                      const isc::dns::RRType& type,
                                      const FindOptions options =
                                      FIND_DEFAULT);
    virtual ZoneFinderContextPtr findAll(const isc::dns::Name& name,
                                         std::vector<ConstRRsetPtr>& target,
                                         const FindOptions options =
                                         FIND_DEFAULT);
300

301
    virtual ZoneFinder::FindNSEC3Result
302
    findNSEC3(const Name& name, bool recursive);
303

304 305
    // If false is passed, it makes the zone broken as if it didn't have the
    // SOA.
306
    void setSOAFlag(bool on) { has_SOA_ = on; }
307

308 309 310
    // If false is passed, it makes the zone broken as if it didn't have
    // the apex NS.
    void setApexNSFlag(bool on) { has_apex_NS_ = on; }
311

312 313 314
    // Turn this on if you want it to return RRSIGs regardless of FIND_GLUE_OK
    void setIncludeRRSIGAnyway(bool on) { include_rrsig_anyway_ = on; }

315 316 317 318 319 320
    // Once called, this "faked" result will be returned when NSEC is expected
    // for the specified query name.
    void setNSECResult(const Name& nsec_name, Result code,
                       ConstRRsetPtr rrset)
    {
        nsec_name_ = nsec_name;
321 322 323
        nsec_context_.reset(
            new GenericContext(*this, FIND_DEFAULT, // a fake value
                               ResultContext(code, rrset, RESULT_NSEC_SIGNED)));
324 325
    }

326 327 328 329
    // Once called, the findNSEC3 will return the provided result for the next
    // query. After that, it'll return to operate normally.
    // NULL disables. Does not take ownership of the pointer (it is generally
    // expected to be a local variable in the test function).
330 331 332
    void setNSEC3Result(const FindNSEC3Result* result,
                        const Name* name = NULL)
    {
333
        nsec3_fake_ = result;
334
        nsec3_name_ = name;
335 336
    }

337 338 339 340
    // If true is passed return an empty NSEC3 RRset for some negative
    // answers when DNSSEC is required.
    void setNSEC3Flag(bool on) { use_nsec3_ = on; }

341 342 343 344 345 346 347 348 349 350
    // This method allows tests to insert new record in the middle of the test.
    //
    // \param record_txt textual RR representation of RR (such as soa_txt, etc)
    void addRecord(const string& record_txt) {
        stringstream record_stream;
        record_stream << record_txt;
        masterLoad(record_stream, origin_, rrclass_,
                   boost::bind(&MockZoneFinder::loadRRset, this, _1));
    }

351 352
public:
    // We allow the tests to use these for convenience
353
    ConstRRsetPtr dname_rrset_; // could be used as an arbitrary bogus RRset
354 355
    ConstRRsetPtr empty_nsec_rrset_;

356 357 358 359 360 361 362
protected:
    // A convenient shortcut.  Will also be used by further derived mocks.
    ZoneFinderContextPtr createContext(FindOptions options,
                                       Result code,
                                       isc::dns::ConstRRsetPtr rrset,
                                       FindResultFlags flags = RESULT_DEFAULT)
    {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
363
        ConstRRsetPtr rp = stripRRsigs(rrset, options);
364
        return (ZoneFinderContextPtr(
365 366
                    new GenericContext(*this, options,
                                       ResultContext(code, rp, flags))));
367 368
    }

Jerry's avatar
Jerry committed
369
private:
370 371 372
    typedef map<RRType, ConstRRsetPtr> RRsetStore;
    typedef map<Name, RRsetStore> Domains;
    Domains domains_;
373
    Domains delegations_;
374
    Domains nsec3_domains_;
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

    // This is used to identify delegation to a child zone, and used to
    // find a matching entry in delegations_.  Note that first found entry
    // is returned, so it's not a longest match.  Test data must be set up
    // to ensure the first match is always the longest match.
    struct SubdomainMatch {
        SubdomainMatch(const Name& name) : name_(name) {}
        bool operator()(const pair<Name, RRsetStore>& domain_elem) const {
            return (name_ == domain_elem.first ||
                    name_.compare(domain_elem.first).getRelation() ==
                    NameComparisonResult::SUBDOMAIN);
        }
    private:
        const Name& name_;
    };

391
    void loadRRset(RRsetPtr rrset) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
392 393 394 395 396 397
        // For simplicity we dynamically generate RRSIGs and add them below.
        // The RRSIG RDATA should be consistent with that defined in the
        // zone file.
        if (rrset->getType() == RRType::RRSIG()) {
            return;
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
398 399 400

        // NSEC3PARAM is not used in the mock data source (and it would confuse
        // non-NSEC3 test cases).
401
        if (rrset->getType() == RRType::NSEC3PARAM()) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
402
            return;
403
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
404

405 406 407 408 409 410 411 412 413 414 415
        if (rrset->getType() == RRType::NSEC3()) {
            // NSEC3 should go to the dedicated table
            nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;

            // By nature it should have RRSIG.  (We may want to selectively
            // omit this to test pathological cases).
            rrset->addRRsig(RdataPtr(new generic::RRSIG(
                                         getCommonRRSIGText(rrset->getType().
                                                            toText()))));
            return;
        }
416
        domains_[rrset->getName()][rrset->getType()] = rrset;
417 418

        // Remember delegation (NS/DNAME) related RRsets separately.
419
        if (rrset->getType() == RRType::NS() && rrset->getName() != origin_) {
420
            delegations_[rrset->getName()][rrset->getType()] = rrset;
421
        } else if (rrset->getName() == dname_name_ &&
422
                   rrset->getType() == RRType::DNAME()) {
423
            dname_rrset_ = rrset;
424 425 426 427 428 429 430 431 432 433 434
        }

        // Add some signatures.  For NS, we only have RRSIG for the origin
        // name. For others generate RRSIG unconditionally.  Technically this
        // is wrong because we shouldn't have it for names under a zone
        // cut.  But in our tests that doesn't matter, so we add them
        // just for simplicity.
        // Note that this includes RRSIG for DS with secure delegations.
        // They should have RRSIGs, so that's actually expected data, not just
        // for simplicity.
        if (rrset->getType() != RRType::NS() || rrset->getName() == origin_) {
435 436 437
            rrset->addRRsig(RdataPtr(new generic::RRSIG(
                                         getCommonRRSIGText(rrset->getType().
                                                            toText()))));
438 439 440 441
        }
    }

    const Name origin_;
442
    // Names where we delegate somewhere else
443
    const Name bad_signed_delegation_name_;
444
    const Name dname_name_;
Michal Vaner's avatar
Michal Vaner committed
445
    bool has_SOA_;
Jerry's avatar
Jerry committed
446
    bool has_apex_NS_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
447
    const RRClass rrclass_;
448
    bool include_rrsig_anyway_;
449
    bool use_nsec3_;
450 451
    // The following two will be used for faked NSEC cases
    Name nsec_name_;
452
    ZoneFinderContextPtr nsec_context_;
453 454 455
    // The following two are for faking bad NSEC3 responses
    // Enabled when not NULL
    const FindNSEC3Result* nsec3_fake_;
456
    const Name* nsec3_name_;
457
    TestNSEC3Hash nsec3_hash_;
458 459
};

460 461 462
// A helper function that generates a new RRset based on "wild_rrset",
// replacing its owner name with 'real_name'.
ConstRRsetPtr
463
substituteWild(const AbstractRRset& wild_rrset, const Name& real_name) {
464 465 466 467 468 469 470 471 472 473 474 475 476 477
    RRsetPtr rrset(new RRset(real_name, wild_rrset.getClass(),
                             wild_rrset.getType(), wild_rrset.getTTL()));
    // For simplicity we only consider the case with one RDATA (for now)
    rrset->addRdata(wild_rrset.getRdataIterator()->getCurrent());
    ConstRRsetPtr wild_sig = wild_rrset.getRRsig();
    if (wild_sig) {
        RRsetPtr sig(new RRset(real_name, wild_sig->getClass(),
                               wild_sig->getType(), wild_sig->getTTL()));
        sig->addRdata(wild_sig->getRdataIterator()->getCurrent());
        rrset->addRRsig(sig);
    }
    return (rrset);
}

478
ZoneFinderContextPtr
479 480 481
MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
                        const FindOptions options)
{
482 483
    ZoneFinderContextPtr result(find(name, RRType::ANY(), options));
    if (result->code == NXRRSET) {
484 485 486 487 488 489
        const Domains::const_iterator found_domain = domains_.find(name);
        if (!found_domain->second.empty()) {
            for (RRsetStore::const_iterator found_rrset =
                 found_domain->second.begin();
                 found_rrset != found_domain->second.end(); ++found_rrset) {
                // Insert RRs under the domain name into target
490
                target.push_back(stripRRsigs(found_rrset->second, options));
491
            }
492
            return (ZoneFinderContextPtr(
493 494 495
                        new GenericContext(*this, options,
                                           ResultContext(SUCCESS, RRsetPtr()),
                                           target)));
496 497 498 499 500 501
        }
    }

    return (result);
}

502
ZoneFinder::FindNSEC3Result
503
MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
504
    // Do we have a fake result set? If so, use it.
505 506
    if (nsec3_fake_ != NULL &&
        (nsec3_name_ == NULL || *nsec3_name_ == name)) {
507 508 509 510
        const FindNSEC3Result* result(nsec3_fake_);
        return (*result);
    }

511 512 513 514 515 516
    ConstRRsetPtr covering_proof;
    const int labels = name.getLabelCount();

    // For brevity, we assume several things below: maps should have an
    // expected entry when operator[] is used; maps are not empty.
    for (int i = 0; i < labels; ++i) {
517
        const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
        const Name hname = Name(hlabel + ".example.com");
        // We don't use const_iterator so that we can use operator[] below
        Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);

        // If the given hash is larger than the largest stored hash or
        // the first label doesn't match the target, identify the "previous"
        // hash value and remember it as the candidate next closer proof.
        if (found_domain == nsec3_domains_.end() ||
            found_domain->first.split(0, 1).toText(true) != hlabel) {
            // If the given hash is larger or smaller than everything,
            // the covering proof is the NSEC3 that has the largest hash.
            if (found_domain == nsec3_domains_.end() ||
                found_domain == nsec3_domains_.begin()) {
                covering_proof =
                    nsec3_domains_.rbegin()->second[RRType::NSEC3()];
            } else {
                // Otherwise, H(found_domain-1) < given_hash < H(found_domain)
                // The covering proof is the first one.
                covering_proof = (--found_domain)->second[RRType::NSEC3()];
            }
            if (!recursive) {   // in non recursive mode, we are done.
539 540 541
                return (ZoneFinder::FindNSEC3Result(false,
                                                    name.getLabelCount(),
                                                    covering_proof,
542 543 544 545
                                                    ConstRRsetPtr()));
            }
        } else {                // exact match
            return (ZoneFinder::FindNSEC3Result(
546
                        true, name.getLabelCount() - i,
547 548 549 550 551 552 553
                        found_domain->second[RRType::NSEC3()],
                        covering_proof));
        }
    }
    isc_throw(isc::Unexpected, "findNSEC3() isn't expected to fail");
}

554
ZoneFinderContextPtr
555
MockZoneFinder::find(const Name& name, const RRType& type,
556
                     const FindOptions options)
557
{
558 559 560
    // Emulating a broken zone: mandatory apex RRs are missing if specifically
    // configured so (which are rare cases).
    if (name == origin_ && type == RRType::SOA() && !has_SOA_) {
561
        return (createContext(options, NXDOMAIN, RRsetPtr()));
562
    } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
563
        return (createContext(options, NXDOMAIN, RRsetPtr()));
564 565
    }

566
    // Special case for names on or under a zone cut and under DNAME
567
    Domains::iterator it;
568
    if ((options & FIND_GLUE_OK) == 0 &&
569 570 571 572
        (it = find_if(delegations_.begin(), delegations_.end(),
                      SubdomainMatch(name))) != delegations_.end()) {
        ConstRRsetPtr delegation_ns = it->second[RRType::NS()];
        assert(delegation_ns); // should be ensured by how we construct it
573 574 575 576 577

        // DS query for the delegated domain (i.e. an exact match) will be
        // handled just like an in-zone case below.  Others result in
        // DELEGATION.
        if (type != RRType::DS() || it->first != name) {
578
            return (createContext(options, DELEGATION, delegation_ns));
579
        }
580
    } else if (name.compare(dname_name_).getRelation() ==
581
               NameComparisonResult::SUBDOMAIN) {
582
        return (createContext(options, DNAME, dname_rrset_));
583 584 585 586 587 588 589 590 591 592
    }

    // normal cases.  names are searched for only per exact-match basis
    // for simplicity.
    const Domains::const_iterator found_domain = domains_.find(name);
    if (found_domain != domains_.end()) {
        // First, try exact match.
        RRsetStore::const_iterator found_rrset =
            found_domain->second.find(type);
        if (found_rrset != found_domain->second.end()) {
593 594
            ConstRRsetPtr rrset = ZoneFinder::stripRRsigs(found_rrset->second,
                                                          options);
595
            return (createContext(options, SUCCESS, rrset));
596 597 598 599 600
        }

        // Otherwise, if this domain name has CNAME, return it.
        found_rrset = found_domain->second.find(RRType::CNAME());
        if (found_rrset != found_domain->second.end()) {
601
            return (createContext(options, CNAME, found_rrset->second));
602 603
        }

604 605 606 607
        // Otherwise it's NXRRSET case...
        // ...but a special pathological case first:
        if (found_domain->first == bad_signed_delegation_name_ &&
            type == RRType::DS()) {
608
            return (createContext(options, NXDOMAIN, RRsetPtr()));
609 610
        }
        // normal cases follow.
611
        if ((options & FIND_DNSSEC) != 0) {
612
            if (use_nsec3_) {
613 614
                return (createContext(options, NXRRSET, RRsetPtr(),
                                      RESULT_NSEC3_SIGNED));
615
            }
616 617
            found_rrset = found_domain->second.find(RRType::NSEC());
            if (found_rrset != found_domain->second.end()) {
618 619
                return (createContext(options, NXRRSET, found_rrset->second,
                                      RESULT_NSEC_SIGNED));
620 621
            }
        }
622 623 624
        // If no NSEC is found or DNSSEC isn't specified, behave as if the
        // zone is unsigned.
        return (createContext(options, NXRRSET, RRsetPtr()));
625 626
    }

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
    // query name isn't found in our domains.
    // We first check if the query name is an empty non terminal name
    // of the zone by naive linear search.
    Domains::const_iterator domain;
    for (domain = domains_.begin(); domain != domains_.end(); ++domain) {
        if (name.compare((*domain).first).getRelation() ==
            NameComparisonResult::SUPERDOMAIN) {
            break;
        }
    }
    if (domain != domains_.end()) {
        // The query name is in an empty non terminal node followed by 'domain'
        // (for simplicity we ignore the pathological case of 'domain' is
        // the origin of the zone)
        --domain;               // reset domain to the "previous name"
        if ((options & FIND_DNSSEC) != 0) {
643
            if (use_nsec3_) {
644 645
                return (createContext(options, NXRRSET, RRsetPtr(),
                                      RESULT_NSEC3_SIGNED));
646
            }
647 648 649
            RRsetStore::const_iterator found_rrset =
                (*domain).second.find(RRType::NSEC());
            if (found_rrset != (*domain).second.end()) {
650 651
                return (createContext(options, NXRRSET, found_rrset->second,
                                      RESULT_NSEC_SIGNED));
652 653
            }
        }
654
        return (createContext(options, NXRRSET, RRsetPtr()));
655 656
    }

657 658 659 660
    // Another possibility is wildcard.  For simplicity we only check
    // hardcoded specific cases, ignoring other details such as canceling
    // due to the existence of closer name.
    if ((options & NO_WILDCARD) == 0) {
661 662
        const Name wild_suffix(name == Name("x.y.wild.example.com") ?
                               Name("wild.example.com") : name.split(1));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
663
        // Unit Tests use those domains for Wildcard test.
664 665 666 667
        if (name.equals(Name("www.wild.example.com")) ||
            name.equals(Name("x.y.wild.example.com")) ||
            name.equals(Name("www1.uwild.example.com")) ||
            name.equals(Name("a.t.example.com"))) {
668 669 670
            if (name.compare(wild_suffix).getRelation() ==
                NameComparisonResult::SUBDOMAIN) {
                domain = domains_.find(Name("*").concatenate(wild_suffix));
671 672
                // Matched the QNAME
                if (domain != domains_.end()) {
673 674 675 676
                    RRsetStore::const_iterator found_rrset =
                        domain->second.find(type);
                    // Matched the QTYPE
                    if(found_rrset != domain->second.end()) {
677 678 679 680 681 682
                        return (createContext(options,SUCCESS, substituteWild(
                                                  *found_rrset->second, name),
                                              RESULT_WILDCARD |
                                              (use_nsec3_ ?
                                               RESULT_NSEC3_SIGNED :
                                               RESULT_NSEC_SIGNED)));
683
                    } else {
684 685
                        // No matched QTYPE, this case is for NXRRSET with
                        // WILDCARD
686
                        if (use_nsec3_) {
687 688 689
                            return (createContext(options, NXRRSET, RRsetPtr(),
                                                  RESULT_WILDCARD |
                                                  RESULT_NSEC3_SIGNED));
690
                        }
691 692
                        const Name new_name =
                            Name("*").concatenate(wild_suffix);
693 694
                        found_rrset = domain->second.find(RRType::NSEC());
                        assert(found_rrset != domain->second.end());
695 696 697 698 699
                        return (createContext(options, NXRRSET, substituteWild(
                                                  *found_rrset->second,
                                                  new_name),
                                              RESULT_WILDCARD |
                                              RESULT_NSEC_SIGNED));
700 701
                    }
                } else {
702
                    // This is empty non terminal name case on wildcard.
703 704
                    const Name empty_name = Name("*").concatenate(wild_suffix);
                    if (use_nsec3_) {
705 706 707
                        return (createContext(options, NXRRSET, RRsetPtr(),
                                              RESULT_WILDCARD |
                                              RESULT_NSEC3_SIGNED));
708
                    }
709
                    for (Domains::reverse_iterator it = domains_.rbegin();
710 711 712
                         it != domains_.rend();
                         ++it) {
                        RRsetStore::const_iterator nsec_it;
713
                        if ((*it).first < empty_name &&
714 715
                            (nsec_it = (*it).second.find(RRType::NSEC()))
                            != (*it).second.end()) {
716 717 718 719
                            return (createContext(options, NXRRSET,
                                                  (*nsec_it).second,
                                                  RESULT_WILDCARD |
                                                  RESULT_NSEC_SIGNED));
720
                        }
721
                    }
722
                }
723 724
                return (createContext(options, NXRRSET, RRsetPtr(),
                                      RESULT_WILDCARD));
725 726
             }
        }
727 728 729 730 731 732 733 734
        const Name cnamewild_suffix("cnamewild.example.com");
        if (name.compare(cnamewild_suffix).getRelation() ==
            NameComparisonResult::SUBDOMAIN) {
            domain = domains_.find(Name("*").concatenate(cnamewild_suffix));
            assert(domain != domains_.end());
            RRsetStore::const_iterator found_rrset =
                domain->second.find(RRType::CNAME());
            assert(found_rrset != domain->second.end());
735 736 737 738 739
            return (createContext(options, CNAME,
                                  substituteWild(*found_rrset->second, name),
                                  RESULT_WILDCARD |
                                  (use_nsec3_ ? RESULT_NSEC3_SIGNED :
                                   RESULT_NSEC_SIGNED)));
740
        }
741 742
    }

743
    // This is an NXDOMAIN case.
744 745 746 747 748 749 750
    // If we need DNSSEC proof, find the "previous name" that has an NSEC RR
    // and return NXDOMAIN with the found NSEC.  Otherwise, just return the
    // NXDOMAIN code and NULL.  If DNSSEC proof is requested but no NSEC is
    // found, we return NULL, too.  (For simplicity under the test conditions
    // we don't care about pathological cases such as the name is "smaller"
    // than the origin)
    if ((options & FIND_DNSSEC) != 0) {
751
        if (use_nsec3_) {
752 753
            return (createContext(options, NXDOMAIN, RRsetPtr(),
                                  RESULT_NSEC3_SIGNED));
754 755
        }

756
        // Emulate a broken DataSourceClient for some special names.
757 758
        if (nsec_context_ && nsec_name_ == name) {
            return (nsec_context_);
759 760 761
        }

        // Normal case
762 763 764
        // XXX: some older g++ complains about operator!= if we use
        // const_reverse_iterator
        for (Domains::reverse_iterator it = domains_.rbegin();
765 766 767 768 769 770
             it != domains_.rend();
             ++it) {
            RRsetStore::const_iterator nsec_it;
            if ((*it).first < name &&
                (nsec_it = (*it).second.find(RRType::NSEC()))
                != (*it).second.end()) {
771 772
                return (createContext(options, NXDOMAIN, (*nsec_it).second,
                                      RESULT_NSEC_SIGNED));
773 774 775
            }
        }
    }
776
    return (createContext(options,NXDOMAIN, RRsetPtr()));
777 778
}

779
enum DataSrcType {
780
    MOCK,
781 782
    INMEMORY,
    SQLITE3
783 784 785 786
};

boost::shared_ptr<ClientList>
createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
787
    boost::shared_ptr<ConfigurableClientList> list;
788 789 790
    switch (type) {
    case MOCK:
        return (boost::shared_ptr<ClientList>(new SingletonList(client)));
791
    case INMEMORY:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
792
        list.reset(new ConfigurableClientList(RRClass::IN()));
793 794 795 796
        list->configure(isc::data::Element::fromJSON(
                            "[{\"type\": \"MasterFiles\","
                            "  \"cache-enable\": true, "
                            "  \"params\": {\"example.com\": \"" +
797
                            string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
798 799
                            "\"}}]"), true);
        return (list);
800
    case SQLITE3:
801 802 803 804 805 806
        // The copy should succeed; if it failed we should notice it in
        // test cases.
        std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
                    "/example-base.sqlite3 "
                    TEST_OWN_DATA_BUILDDIR
                    "/example-base.sqlite3.copied");
807 808 809 810 811 812
        list.reset(new ConfigurableClientList(RRClass::IN()));
        list->configure(isc::data::Element::fromJSON(
                            "[{\"type\": \"sqlite3\","
                            "  \"cache-enable\": false, "
                            "  \"cache-zones\": [], "
                            "  \"params\": {\"database_file\": \"" +
813
                            string(TEST_OWN_DATA_BUILDDIR
814
                                   "/example-base.sqlite3.copied") +
815 816
                            "\"}}]"), true);
         return (list);
817 818 819
    default:
        isc_throw(isc::Unexpected,
                  "Unexpected data source type, should be a bug in test code");
820 821 822 823
    }
}

class QueryTest : public ::testing::TestWithParam<DataSrcType> {
824 825 826 827
protected:
    QueryTest() :
        qname(Name("www.example.com")), qclass(RRClass::IN()),
        qtype(RRType::A()), response(Message::RENDER),
828 829 830 831 832 833 834
        qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
        ns_addrs_and_sig_txt(string(ns_addrs_txt) +
                             "glue.delegation.example.com. 3600 IN RRSIG " +
                             getCommonRRSIGText("A") + "\n" +
                             "glue.delegation.example.com. 3600 IN RRSIG " +
                             getCommonRRSIGText("AAAA") + "\n" +
                             "noglue.example.com. 3600 IN RRSIG " +
835 836 837
                             getCommonRRSIGText("A")),
        base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
        nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
838 839
        common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
        rrsets_added_(false)
840
    {
841
        // Set up the faked hash calculator.
842 843
        setNSEC3HashCreator(&nsec3hash_creator_);

844
        response.setRcode(Rcode::NOERROR());
845 846
        response.setOpcode(Opcode::QUERY());
        // create and add a matching zone.
847
        mock_finder = new MockZoneFinder();
848
        memory_client.addZone(ZoneFinderPtr(mock_finder));
849
    }
850

851
    virtual void SetUp() {
852 853 854 855 856 857 858 859 860
        // clear the commonly included zone file.
        ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
                                 "/example-common-inc-template.zone "
                                 TEST_OWN_DATA_BUILDDIR
                                 "/example-common-inc.zone"));

        // We create data source clients here, not in the constructor, so this
        // doesn't happen for derived test class.  This also ensures the
        // data source clients are configured after setting NSEC3 hash in case
861 862 863 864 865
        // there's dependency.
        list_ = createDataSrcClientList(GetParam(), memory_client);
    }

    virtual ~QueryTest() {
866 867 868 869
        // Make sure we reset the hash creator to the default
        setNSEC3HashCreator(NULL);
    }

870
    void enableNSEC3(const vector<string>& rrsets_to_add) {
871 872 873 874
        boost::shared_ptr<ConfigurableClientList> new_list;
        switch (GetParam()) {
        case MOCK:
            mock_finder->setNSEC3Flag(true);
875
            addRRsets(rrsets_to_add, *list_, "");
876 877
            break;
        case INMEMORY:
878
            addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
879 880
            break;
        case SQLITE3:
881
            ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
882
                                     "/example-nsec3.sqlite3 "
883
                                     TEST_OWN_DATA_BUILDDIR
884 885 886 887 888 889 890
                                     "/example-nsec3.sqlite3.copied"));
            new_list.reset(new ConfigurableClientList(RRClass::IN()));
            new_list->configure(isc::data::Element::fromJSON(
                                    "[{\"type\": \"sqlite3\","
                                    "  \"cache-enable\": false, "
                                    "  \"cache-zones\": [], "
                                    "  \"params\": {\"database_file\": \"" +
891
                                    string(TEST_OWN_DATA_BUILDDIR
892 893
                                           "/example-nsec3.sqlite3.copied") +
                                    "\"}}]"), true);
894
            addRRsets(rrsets_to_add, *new_list, "");
895 896 897 898
            list_ = new_list;
            break;
        }
    }
899

900
    // A helper to add some RRsets to the test zone in the middle of a test
901 902
    // case.  The detailed behavior is quite different depending on the
    // data source type, and not all parameters are used in all cases.
903 904 905 906 907
    //
    // Note: due to limitation of its implementation, this method doesn't
    // work correctly for in-memory if called more than once.  This condition
    // is explicitly checked so any accidental violation would be noted as a
    // test failure.
908 909 910 911 912 913
    void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
                   const string& zone_file)
    {
        boost::shared_ptr<ConfigurableClientList> new_list;
        ofstream ofs;

914 915 916 917 918 919 920 921 922 923 924
        switch (GetParam()) {
        case MOCK:
            // directly add them to the mock data source; ignore the passed
            // list.
            for (vector<string>::const_iterator it = rrsets_to_add.begin();
                 it != rrsets_to_add.end();
                 ++it) {
                mock_finder->addRecord(*it);
            }
            break;
        case INMEMORY:
925 926 927
            ASSERT_FALSE(rrsets_added_);
            rrsets_added_ = true;

928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946
            // dump the RRsets to be added to the placeholder of commonly
            // included zone file (removing any existing contents) and do
            // full reconfiguration.
            ofs.open(common_zone_file.c_str(), ios_base::trunc);
            for (vector<string>::const_iterator it = rrsets_to_add.begin();
                 it != rrsets_to_add.end();
                 ++it) {
                ofs << *it << "\n";
                ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
            }
            ofs.close();

            new_list.reset(new ConfigurableClientList(RRClass::IN()));
            new_list->configure(isc::data::Element::fromJSON(
                                    "[{\"type\": \"MasterFiles\","
                                    "  \"cache-enable\": true, "
                                    "  \"params\": {\"example.com\": \"" +
                                    zone_file + "\"}}]"), true);
            list_ = new_list;
947 948
            break;
        case SQLITE3:
949 950
            const Name origin("example.com");
            ZoneUpdaterPtr updater =
951
                list.find(origin, true, false).dsrc_client_->
952 953 954 955 956 957 958 959 960
                getUpdater(origin, false);
            for (vector<string>::const_iterator it = rrsets_to_add.begin();
                 it != rrsets_to_add.end();
                 ++it) {
                ConstRRsetPtr rrset = textToRRset(*it);
                updater->addRRset(*rrset);
                updater->addRRset(*createRRSIG(rrset));
            }
            updater->commit();
961 962 963 964
            break;
        }
    }

965 966 967 968 969 970 971 972 973 974