query_unittest.cc 81.1 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 <sstream>
16
17
#include <vector>
#include <map>
18

19
#include <boost/bind.hpp>
20
#include <boost/scoped_ptr.hpp>
21

22
23
#include <exceptions/exceptions.h>

24
#include <dns/masterload.h>
25
26
#include <dns/message.h>
#include <dns/name.h>
27
#include <dns/opcode.h>
28
#include <dns/rcode.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
35
36

#include <auth/query.h>

37
#include <testutils/dnsmessage_test.h>
38

39
40
#include <gtest/gtest.h>

41
using namespace std;
42
using namespace isc::dns;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
43
using namespace isc::dns::rdata;
44
45
using namespace isc::datasrc;
using namespace isc::auth;
46
using namespace isc::testutils;
47

48
49
namespace {

50
51
52
53
54
// This is the content of the mock zone (see below).
// It's a sequence of textual RRs that is supposed to be parsed by
// dns::masterLoad().  Some of the RRs are also used as the expected
// data in specific tests, in which case they are referenced via specific
// local variables (such as soa_txt).
55
56
57
58
59
60
61
62
63
64
//
// For readability consistency, all strings are placed in a separate line,
// even if they are very short and can reasonably fit in a single line with
// the corresponding variable.  For example, we write
// const char* const foo_txt =
//  "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
// instead of
// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
const char* const soa_txt =
    "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
65
66
67
68
const char* const zone_ns_txt =
    "example.com. 3600 IN NS glue.delegation.example.com.\n"
    "example.com. 3600 IN NS noglue.example.com.\n"
    "example.com. 3600 IN NS example.net.\n";
69
70
71
const char* const zone_ds_txt =
    "example.com. 3600 IN DS 57855 5 1 "
        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
72
73
74
75
76
const char* const ns_addrs_txt =
    "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
    "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
    "noglue.example.com. 3600 IN A 192.0.2.53\n";
const char* const delegation_txt =
chenzhengzhang's avatar
chenzhengzhang committed
77
78
79
80
    "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
    "delegation.example.com. 3600 IN NS noglue.example.com.\n"
    "delegation.example.com. 3600 IN NS cname.example.com.\n"
    "delegation.example.com. 3600 IN NS example.org.\n";
81
82
83
84
// Borrowed from the RFC4035
const char* const delegation_ds_txt =
    "delegation.example.com. 3600 IN DS 57855 5 1 "
        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
85
86
87
88
const char* const mx_txt =
    "mx.example.com. 3600 IN MX 10 www.example.com.\n"
    "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
    "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
89
90
const char* const www_a_txt =
    "www.example.com. 3600 IN A 192.0.2.80\n";
91
92
const char* const cname_txt =
    "cname.example.com. 3600 IN CNAME www.example.com.\n";
93
94
95
96
97
const char* const cname_nxdom_txt =
    "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
// CNAME Leading out of zone
const char* const cname_out_txt =
    "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
98
// The DNAME to do tests against
99
const char* const dname_txt =
100
101
    "dname.example.com. 3600 IN DNAME "
    "somethinglong.dnametarget.example.com.\n";
102
// Some data at the dname node (allowed by RFC 2672)
103
104
const char* const dname_a_txt =
    "dname.example.com. 3600 IN A 192.0.2.5\n";
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
105
106
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
107
108
    "www.dname.example.com. 3600 IN CNAME "
    "www.somethinglong.dnametarget.example.com.\n";
109
110
111
112
113
// The rest of data won't be referenced from the test cases.
const char* const other_zone_rrs =
    "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
    "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
    "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
114
// Wildcards
115
116
const char* const wild_txt =
    "*.wild.example.com. 3600 IN A 192.0.2.7\n";
117
118
119
120
const char* const nsec_wild_txt =
    "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
const char* const cnamewild_txt =
    "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
121
122
123
const char* const nsec_cnamewild_txt =
    "*.cnamewild.example.com. 3600 IN NSEC "
    "delegation.example.com. CNAME NSEC RRSIG\n";
124
125
// Wildcard_nxrrset
const char* const wild_txt_nxrrset =
126
    "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
127
const char* const nsec_wild_txt_nxrrset =
128
    "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
129
const char* const wild_txt_next =
130
    "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
131
const char* const nsec_wild_txt_next =
132
    "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
133
// Wildcard empty
134
135
const char* const empty_txt =
    "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
136
const char* const nsec_empty_txt =
137
    "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
138
139
const char* const empty_prev_txt =
    "t.example.com. 3600 IN A 192.0.2.15\n";
140
141
const char* const nsec_empty_prev_txt =
    "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
142
143
144
145
146
147
148
149
150
// Used in NXDOMAIN proof test.  We are going to test some unusual case where
// the best possible wildcard is below the "next domain" of the NSEC RR that
// proves the NXDOMAIN, i.e.,
// mx.example.com. (exist)
// (.no.example.com. (qname, NXDOMAIN)
// ).no.example.com. (exist)
// *.no.example.com. (best possible wildcard, not exist)
const char* const no_txt =
    ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
151
152
153
// NSEC records.
const char* const nsec_apex_txt =
    "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
154
155
156
const char* const nsec_mx_txt =
    "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
const char* const nsec_no_txt =
157
158
159
160
161
162
163
164
165
166
167
168
    ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
// We'll also test the case where a single NSEC proves both NXDOMAIN and the
// non existence of wildcard.  The following records will be used for that
// test.
// ).no.example.com. (exist, whose NSEC proves everything)
// *.no.example.com. (best possible wildcard, not exist)
// nx.no.example.com. (NXDOMAIN)
// nz.no.example.com. (exist)
const char* const nz_txt =
    "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
const char* const nsec_nz_txt =
    "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
169
const char* const nsec_nxdomain_txt =
170
    "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
171

172
173
174
175
// NSEC for the normal NXRRSET case
const char* const nsec_www_txt =
    "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";

176
// Authoritative data without NSEC
177
178
const char* const nonsec_a_txt =
    "nonsec.example.com. 3600 IN A 192.0.2.0\n";
179

180
181
182
183
184
185
186
187
// NSEC3 RRs.  You may also need to add mapping to MockZoneFinder::hash_map_.
const char* const nsec3_apex_txt =
    "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
const char* const nsec3_www_txt =
    "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
    "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";

188
// (Secure) delegation data; Delegation with DS record
189
190
191
192
193
194
const char* const signed_delegation_txt =
    "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
const char* const signed_delegation_ds_txt =
    "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
    "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";

195
196
// (Secure) delegation data; Delegation without DS record (and NSEC denying
// its existence.
197
198
199
200
201
202
const char* const unsigned_delegation_txt =
    "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
const char* const unsigned_delegation_nsec_txt =
    "unsigned-delegation.example.com. 3600 IN NSEC "
    "*.uwild.example.com. NS RRSIG NSEC\n";

203
204
// (Secure) delegation data; Delegation where the DS lookup will raise an
// exception.
205
206
207
208
const char* const bad_delegation_txt =
    "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";


209
210
211
212
// 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.
213
214
215
216
217
218
string
getCommonRRSIGText(const string& type) {
    return (type +
            string(" 5 3 3600 20000101000000 20000201000000 12345 "
                   "example.com. FAKEFAKEFAKE"));
}
Michal Vaner's avatar
Michal Vaner committed
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// 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(),
               boost::bind(setRRset, _1, rrsets.begin()));
    return (rrset);
}

241
// This is a mock Zone Finder class for testing.
242
// It is a derived class of ZoneFinder for the convenient of tests.
243
// Its find() method emulates the common behavior of protocol compliant
244
// ZoneFinder classes, but simplifies some minor cases and also supports broken
245
// behavior.
246
247
// For simplicity, most names are assumed to be "in zone"; delegations
// to child zones are identified by the existence of non origin NS records.
248
249
250
251
// 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).
252
class MockZoneFinder : public ZoneFinder {
253
public:
254
    MockZoneFinder() :
Michal Vaner's avatar
Michal Vaner committed
255
        origin_(Name("example.com")),
256
        bad_signed_delegation_name_("bad-delegation.example.com"),
257
        dname_name_("dname.example.com"),
258
259
        has_SOA_(true),
        has_apex_NS_(true),
260
        rrclass_(RRClass::IN()),
261
        include_rrsig_anyway_(false),
262
        use_nsec3_(false),
263
        nsec_name_(origin_)
264
    {
265
        stringstream zone_stream;
266
        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
267
268
            delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
            cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
269
            dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
270
            nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
271
            nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
272
            wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
273
            wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
274
            nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
275
            empty_prev_txt << nsec_empty_prev_txt <<
276
277
278
279
            nsec3_apex_txt << nsec3_www_txt <<
            signed_delegation_txt << signed_delegation_ds_txt <<
            unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
            bad_delegation_txt;
280

281
        masterLoad(zone_stream, origin_, rrclass_,
282
                   boost::bind(&MockZoneFinder::loadRRset, this, _1));
283
284
285
286
287

        empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
                                                    RRClass::IN(),
                                                    RRType::NSEC(),
                                                    RRTTL(3600)));
288
289
290
291
292
293
294

        // (Faked) NSEC3 hash map.  For convenience we use hardcoded built-in
        // map instead of calculating and using actual hash.
        // The used hash values are borrowed from RFC5155 examples.
        hash_map_[Name("example.com")] = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
        hash_map_[Name("nxdomain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
295
296
297
298
        hash_map_[Name("nx.domain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
        hash_map_[Name("domain.example.com")] =
            "v644ebqk9bibcna874givr6joj62mlhv";
299
300
301
302
        hash_map_[Name("nxdomain2.example.com")] =
            "q00jkcevqvmu85r014c7dkba38o0ji5r";
        hash_map_[Name("nxdomain3.example.com")] =
            "009mhaveqvm6t7vbl5lop2u3t2rp3tom";
303
    }
304
305
    virtual isc::dns::Name getOrigin() const { return (origin_); }
    virtual isc::dns::RRClass getClass() const { return (rrclass_); }
306
307
    virtual FindResult find(const isc::dns::Name& name,
                            const isc::dns::RRType& type,
308
                            const FindOptions options = FIND_DEFAULT);
309
310
311
    virtual FindResult findAll(const isc::dns::Name& name,
                               std::vector<ConstRRsetPtr>& target,
                               const FindOptions options = FIND_DEFAULT);
312

313
    virtual ZoneFinder::FindNSEC3Result
314
    findNSEC3(const Name& name, bool recursive);
315

316
317
    // If false is passed, it makes the zone broken as if it didn't have the
    // SOA.
318
    void setSOAFlag(bool on) { has_SOA_ = on; }
319

320
321
322
    // 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; }
323

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

327
328
329
330
331
332
333
334
335
    // 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;
        nsec_result_.reset(new ZoneFinder::FindResult(code, rrset));
    }

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

340
    virtual Name findPreviousName(const Name&) const {
341
342
343
        isc_throw(isc::NotImplemented, "Mock doesn't support previous name");
    }

344
345
346
347
348
349
350
351
352
353
    // 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));
    }

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

Jerry's avatar
Jerry committed
359
private:
360
361
362
    typedef map<RRType, ConstRRsetPtr> RRsetStore;
    typedef map<Name, RRsetStore> Domains;
    Domains domains_;
363
    Domains delegations_;
364
    Domains nsec3_domains_;
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380

    // 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_;
    };

381
    void loadRRset(RRsetPtr rrset) {
382
383
384
385
386
387
388
389
390
391
392
        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;
        }
393
        domains_[rrset->getName()][rrset->getType()] = rrset;
394
395

        // Remember delegation (NS/DNAME) related RRsets separately.
396
        if (rrset->getType() == RRType::NS() && rrset->getName() != origin_) {
397
            delegations_[rrset->getName()][rrset->getType()] = rrset;
398
        } else if (rrset->getName() == dname_name_ &&
399
                   rrset->getType() == RRType::DNAME()) {
400
            dname_rrset_ = rrset;
401
402
403
404
405
406
407
408
409
410
411
        }

        // 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_) {
412
413
414
            rrset->addRRsig(RdataPtr(new generic::RRSIG(
                                         getCommonRRSIGText(rrset->getType().
                                                            toText()))));
415
416
417
418
        }
    }

    const Name origin_;
419
    // Names where we delegate somewhere else
420
    const Name bad_signed_delegation_name_;
421
    const Name dname_name_;
Michal Vaner's avatar
Michal Vaner committed
422
    bool has_SOA_;
Jerry's avatar
Jerry committed
423
    bool has_apex_NS_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
424
    const RRClass rrclass_;
425
    bool include_rrsig_anyway_;
426
    bool use_nsec3_;
427
428
429
    // The following two will be used for faked NSEC cases
    Name nsec_name_;
    boost::scoped_ptr<ZoneFinder::FindResult> nsec_result_;
430
    map<Name, string> hash_map_;
431
432
};

433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
// A helper function that generates a new RRset based on "wild_rrset",
// replacing its owner name with 'real_name'.
ConstRRsetPtr
substituteWild(const RRset& wild_rrset, const Name& real_name) {
    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);
}

451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
ZoneFinder::FindResult
MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
                        const FindOptions options)
{
    ZoneFinder::FindResult result(find(name, RRType::ANY(), options));
    if (result.code == NXRRSET) {
        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
                target.push_back(found_rrset->second);
            }
            return (FindResult(SUCCESS, RRsetPtr()));
        }
    }

    return (result);
}

472
ZoneFinder::FindNSEC3Result
473
MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    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) {
        const string hlabel = hash_map_[name.split(i, labels - i)];
        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.
502
503
504
                return (ZoneFinder::FindNSEC3Result(false,
                                                    name.getLabelCount(),
                                                    covering_proof,
505
506
507
508
                                                    ConstRRsetPtr()));
            }
        } else {                // exact match
            return (ZoneFinder::FindNSEC3Result(
509
                        true, name.getLabelCount() - i,
510
511
512
513
514
515
516
                        found_domain->second[RRType::NSEC3()],
                        covering_proof));
        }
    }
    isc_throw(isc::Unexpected, "findNSEC3() isn't expected to fail");
}

517
518
ZoneFinder::FindResult
MockZoneFinder::find(const Name& name, const RRType& type,
519
                     const FindOptions options)
520
{
521
522
523
    // 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_) {
524
        return (FindResult(NXDOMAIN, RRsetPtr()));
525
526
527
528
    } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
        return (FindResult(NXDOMAIN, RRsetPtr()));
    }

529
    // Special case for names on or under a zone cut and under DNAME
530
    Domains::iterator it;
531
    if ((options & FIND_GLUE_OK) == 0 &&
532
533
534
535
        (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
536
537
538
539
540

        // 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) {
541
            return (FindResult(DELEGATION, delegation_ns));
542
        }
543
544
545
    } else if (name.compare(dname_name_).getRelation() ==
               NameComparisonResult::SUBDOMAIN) {
        return (FindResult(DNAME, dname_rrset_));
546
547
548
549
550
551
552
553
554
555
    }

    // 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()) {
556
557
558
            ConstRRsetPtr rrset;
            // Strip whatever signature there is in case DNSSEC is not required
            // Just to make sure the Query asks for it when it is needed
559
            if ((options & ZoneFinder::FIND_DNSSEC) != 0 ||
560
                include_rrsig_anyway_ ||
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
                !found_rrset->second->getRRsig()) {
                rrset = found_rrset->second;
            } else {
                RRsetPtr noconst(new RRset(found_rrset->second->getName(),
                                           found_rrset->second->getClass(),
                                           found_rrset->second->getType(),
                                           found_rrset->second->getTTL()));
                for (RdataIteratorPtr
                     i(found_rrset->second->getRdataIterator());
                     !i->isLast(); i->next()) {
                    noconst->addRdata(i->getCurrent());
                }
                rrset = noconst;
            }
            return (FindResult(SUCCESS, rrset));
576
577
578
579
580
581
582
583
        }

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

584
585
586
587
588
589
590
        // Otherwise it's NXRRSET case...
        // ...but a special pathological case first:
        if (found_domain->first == bad_signed_delegation_name_ &&
            type == RRType::DS()) {
            return (FindResult(NXDOMAIN, RRsetPtr()));
        }
        // normal cases follow.
591
        if ((options & FIND_DNSSEC) != 0) {
592
            if (use_nsec3_) {
593
                return (FindResult(NXRRSET, RRsetPtr(), RESULT_NSEC3_SIGNED));
594
            }
595
596
            found_rrset = found_domain->second.find(RRType::NSEC());
            if (found_rrset != found_domain->second.end()) {
597
598
                return (FindResult(NXRRSET, found_rrset->second,
                                   RESULT_NSEC_SIGNED));
599
600
            }
        }
601
        return (FindResult(NXRRSET, RRsetPtr(), RESULT_NSEC_SIGNED));
602
603
    }

604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    // 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) {
620
            if (use_nsec3_) {
621
                return (FindResult(NXRRSET, RRsetPtr(), RESULT_NSEC3_SIGNED));
622
            }
623
624
625
            RRsetStore::const_iterator found_rrset =
                (*domain).second.find(RRType::NSEC());
            if (found_rrset != (*domain).second.end()) {
626
627
                return (FindResult(NXRRSET, found_rrset->second,
                                   RESULT_NSEC_SIGNED));
628
629
630
631
632
            }
        }
        return (FindResult(NXRRSET, RRsetPtr()));
    }

633
634
635
636
    // 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) {
637
        const Name wild_suffix(name.split(1));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
638
        // Unit Tests use those domains for Wildcard test.
639
        if (name.equals(Name("www.wild.example.com"))||
640
641
           name.equals(Name("www1.uwild.example.com"))||
           name.equals(Name("a.t.example.com"))) {
642
643
644
            if (name.compare(wild_suffix).getRelation() ==
                NameComparisonResult::SUBDOMAIN) {
                domain = domains_.find(Name("*").concatenate(wild_suffix));
645
646
                // Matched the QNAME
                if (domain != domains_.end()) {
647
648
649
650
                    RRsetStore::const_iterator found_rrset =
                        domain->second.find(type);
                    // Matched the QTYPE
                    if(found_rrset != domain->second.end()) {
651
                        return (FindResult(SUCCESS,
652
                                           substituteWild(
653
654
655
656
657
                                               *found_rrset->second, name),
                                           RESULT_WILDCARD |
                                           (use_nsec3_ ?
                                            RESULT_NSEC3_SIGNED :
                                            RESULT_NSEC_SIGNED)));
658
659
                    } else {
                        // No matched QTYPE, this case is for WILDCARD_NXRRSET
660
                        if (use_nsec3_) {
661
662
663
                            return (FindResult(NXRRSET, RRsetPtr(),
                                               RESULT_WILDCARD |
                                               RESULT_NSEC3_SIGNED));
664
                        }
665
666
                        const Name new_name =
                            Name("*").concatenate(wild_suffix);
667
668
                        found_rrset = domain->second.find(RRType::NSEC());
                        assert(found_rrset != domain->second.end());
669
                        return (FindResult(NXRRSET,
670
                                           substituteWild(
671
                                               *found_rrset->second,
672
673
674
                                               new_name),
                                           RESULT_WILDCARD |
                                           RESULT_NSEC_SIGNED));
675
676
                    }
                } else {
677
                    // This is empty non terminal name case on wildcard.
678
679
                    const Name empty_name = Name("*").concatenate(wild_suffix);
                    if (use_nsec3_) {
680
681
682
                        return (FindResult(NXRRSET, RRsetPtr(),
                                           RESULT_WILDCARD |
                                           RESULT_NSEC3_SIGNED));
683
                    }
684
                    for (Domains::reverse_iterator it = domains_.rbegin();
685
686
687
                         it != domains_.rend();
                         ++it) {
                        RRsetStore::const_iterator nsec_it;
688
                        if ((*it).first < empty_name &&
689
690
                            (nsec_it = (*it).second.find(RRType::NSEC()))
                            != (*it).second.end()) {
691
692
693
                            return (FindResult(NXRRSET, (*nsec_it).second,
                                               RESULT_WILDCARD |
                                               RESULT_NSEC_SIGNED));
694
                        }
695
                    }
696
                }
697
                return (FindResult(NXRRSET, RRsetPtr(), RESULT_WILDCARD));
698
699
             }
        }
700
701
702
703
704
705
706
707
        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());
708
709
710
711
712
            return (FindResult(CNAME,
                               substituteWild(*found_rrset->second, name),
                               RESULT_WILDCARD |
                               (use_nsec3_ ? RESULT_NSEC3_SIGNED :
                                RESULT_NSEC_SIGNED)));
713
        }
714
715
    }

716
    // This is an NXDOMAIN case.
717
718
719
720
721
722
723
    // 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) {
724
        if (use_nsec3_) {
725
            return (FindResult(NXDOMAIN, RRsetPtr(), RESULT_NSEC3_SIGNED));
726
727
        }

728
729
730
731
732
733
        // Emulate a broken DataSourceClient for some special names.
        if (nsec_result_ && nsec_name_ == name) {
            return (*nsec_result_);
        }

        // Normal case
734
735
736
        // XXX: some older g++ complains about operator!= if we use
        // const_reverse_iterator
        for (Domains::reverse_iterator it = domains_.rbegin();
737
738
739
740
741
742
             it != domains_.rend();
             ++it) {
            RRsetStore::const_iterator nsec_it;
            if ((*it).first < name &&
                (nsec_it = (*it).second.find(RRType::NSEC()))
                != (*it).second.end()) {
743
744
                return (FindResult(NXDOMAIN, (*nsec_it).second,
                                   RESULT_NSEC_SIGNED));
745
746
747
            }
        }
    }
748
    return (FindResult(NXDOMAIN, RRsetPtr()));
749
750
}

751
752
753
754
755
class QueryTest : public ::testing::Test {
protected:
    QueryTest() :
        qname(Name("www.example.com")), qclass(RRClass::IN()),
        qtype(RRType::A()), response(Message::RENDER),
756
757
758
759
760
761
762
763
        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 " +
                             getCommonRRSIGText("A"))
764
765
    {
        response.setRcode(Rcode::NOERROR());
766
767
        response.setOpcode(Opcode::QUERY());
        // create and add a matching zone.
768
        mock_finder = new MockZoneFinder();
769
        memory_client.addZone(ZoneFinderPtr(mock_finder));
770
    }
771
    MockZoneFinder* mock_finder;
772
773
774
775
    // We use InMemoryClient here. We could have some kind of mock client
    // here, but historically, the Query supported only InMemoryClient
    // (originally named MemoryDataSrc) and was tested with it, so we keep
    // it like this for now.
776
    InMemoryClient memory_client;
777
778
779
780
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
    Message response;
781
782
    const qid_t qid;
    const uint16_t query_code;
783
    const string ns_addrs_and_sig_txt; // convenient shortcut
784
785
};

786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
// A wrapper to check resulting response message commonly used in
// tests below.
// check_origin needs to be specified only when the authority section has
// an SOA RR.  The interface is not generic enough but should be okay
// for our test cases in practice.
void
responseCheck(Message& response, const isc::dns::Rcode& rcode,
              unsigned int flags, const unsigned int ancount,
              const unsigned int nscount, const unsigned int arcount,
              const char* const expected_answer,
              const char* const expected_authority,
              const char* const expected_additional,
              const Name& check_origin = Name::ROOT_NAME())
{
    // In our test cases QID, Opcode, and QDCOUNT should be constant, so
    // we don't bother the test cases specifying these values.
    headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
                flags, 0, ancount, nscount, arcount);
    if (expected_answer != NULL) {
        rrsetsCheck(expected_answer,
                    response.beginSection(Message::SECTION_ANSWER),
chenzhengzhang's avatar
chenzhengzhang committed
807
808
                    response.endSection(Message::SECTION_ANSWER),
                    check_origin);
809
810
811
812
813
814
815
816
817
818
819
820
821
822
    }
    if (expected_authority != NULL) {
        rrsetsCheck(expected_authority,
                    response.beginSection(Message::SECTION_AUTHORITY),
                    response.endSection(Message::SECTION_AUTHORITY),
                    check_origin);
    }
    if (expected_additional != NULL) {
        rrsetsCheck(expected_additional,
                    response.beginSection(Message::SECTION_ADDITIONAL),
                    response.endSection(Message::SECTION_ADDITIONAL));
    }
}

823
TEST_F(QueryTest, noZone) {
824
    // There's no zone in the memory datasource.  So the response should have
825
    // REFUSED.
826
827
    InMemoryClient empty_memory_client;
    Query nozone_query(empty_memory_client, qname, qtype, response);
828
    EXPECT_NO_THROW(nozone_query.process());
829
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
830
831
}

832
TEST_F(QueryTest, exactMatch) {
833
    Query query(memory_client, qname, qtype, response);
Jerry's avatar
Jerry committed
834
    EXPECT_NO_THROW(query.process());
835
    // find match rrset
836
837
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  www_a_txt, zone_ns_txt, ns_addrs_txt);
838
}
839

840
841
842
843
844
845
846
847
848
849
850
TEST_F(QueryTest, exactMatchIgnoreSIG) {
    // Check that we do not include the RRSIG when not requested even when
    // we receive it from the data source.
    mock_finder->setIncludeRRSIGAnyway(true);
    Query query(memory_client, qname, qtype, response);
    EXPECT_NO_THROW(query.process());
    // find match rrset
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  www_a_txt, zone_ns_txt, ns_addrs_txt);
}

851
852
853
854
855
TEST_F(QueryTest, dnssecPositive) {
    // Just like exactMatch, but the signatures should be included as well
    Query query(memory_client, qname, qtype, response, true);
    EXPECT_NO_THROW(query.process());
    // find match rrset
856
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
857
                  (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
858
                                           "A 5 3 3600 20000101000000 "
859
860
861
862
863
864
                                           "20000201000000 12345 example.com. "
                                           "FAKEFAKEFAKE\n")).c_str(),
                  (zone_ns_txt + std::string("example.com. 3600 IN RRSIG NS 5 "
                                             "3 3600 20000101000000 "
                                             "20000201000000 12345 "
                                             "example.com. FAKEFAKEFAKE\n")).
865
866
                  c_str(),
                  ns_addrs_and_sig_txt.c_str());
867
868
}

869
870
871
TEST_F(QueryTest, exactAddrMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
872
    EXPECT_NO_THROW(Query(memory_client, Name("noglue.example.com"), qtype,
873
                          response).process());
874

875
876
877
878
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 2,
                  "noglue.example.com. 3600 IN A 192.0.2.53\n", zone_ns_txt,
                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
879
880
}

881
882
883
TEST_F(QueryTest, apexNSMatch) {
    // find match rrset, omit authority data which has already been provided
    // in the answer section from the authority section.
884
    EXPECT_NO_THROW(Query(memory_client, Name("example.com"), RRType::NS(),
885
                          response).process());
886

887
888
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
                  zone_ns_txt, NULL, ns_addrs_txt);
889
890
}

chenzhengzhang's avatar
chenzhengzhang committed
891
// test type any query logic
892
893
894
TEST_F(QueryTest, exactAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
895
    EXPECT_NO_THROW(Query(memory_client, Name("noglue.example.com"),
896
                          RRType::ANY(), response).process());
897

898
899
900
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
                  (string("noglue.example.com. 3600 IN A 192.0.2.53\n") +
                   string(nsec_nxdomain_txt)).c_str(),
901
902
903
                  zone_ns_txt,
                  "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
                  "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
904
905
}

chenzhengzhang's avatar
chenzhengzhang committed
906
907
908
TEST_F(QueryTest, apexAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
909
    EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
910
                          RRType::ANY(), response).process());
911
912
913
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
                  (string(soa_txt) + string(zone_ns_txt) +
                   string(nsec_apex_txt)).c_str(),
914
                  NULL, ns_addrs_txt, mock_finder->getOrigin());
915
916
917
}

TEST_F(QueryTest, mxANYMatch) {
918
    EXPECT_NO_THROW(Query(memory_client, Name("mx.example.com"),
919
                          RRType::ANY(), response).process());
920
921
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
                  (string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
922
                  (string(ns_addrs_txt) + string(www_a_txt)).c_str());
chenzhengzhang's avatar
chenzhengzhang committed
923
924
925
}

TEST_F(QueryTest, glueANYMatch) {
926
    EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
927
928
929
930
931
932
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
}

TEST_F(QueryTest, nodomainANY) {
933
    EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
934
935
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
936
                  NULL, soa_txt, NULL, mock_finder->getOrigin());
chenzhengzhang's avatar
chenzhengzhang committed
937
938
}

939
// This tests that when we need to look up Zone's apex NS records for
Jerry's avatar
Jerry committed
940
941
942
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
TEST_F(QueryTest, noApexNS) {
943
    // Disable apex NS record
944
    mock_finder->setApexNSFlag(false);
945

946
    EXPECT_THROW(Query(memory_client, Name("noglue.example.com"), qtype,
947
948
                       response).process(), Query::NoApexNS);
    // We don't look into the response, as it threw
Jerry's avatar
Jerry committed
949
950
}

951
TEST_F(QueryTest, delegation) {
952
    EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
953
                          qtype, response).process());
954

955
956
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
957
}
958

959
TEST_F(QueryTest, secureDelegation) {
Jelte Jansen's avatar
Jelte Jansen committed
960
961
    EXPECT_NO_THROW(Query(memory_client,
                          Name("foo.signed-delegation.example.com"),
962
963
964
                          qtype, response, true).process());

    // Should now contain RRSIG and DS record as well.
965
    responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
966
                  NULL,
967
968
969
                  (string(signed_delegation_txt) +
                   string(signed_delegation_ds_txt) +
                   string("signed-delegation.example.com. 3600 IN RRSIG ") +
970
                   getCommonRRSIGText("DS")).c_str(),
971
972
973
974
975
976
977
978
                  NULL);
}

TEST_F(QueryTest, secureUnsignedDelegation) {
    EXPECT_NO_THROW(Query(memory_client,
                          Name("foo.unsigned-delegation.example.com"),
                          qtype, response, true).process());

Jelte Jansen's avatar
Jelte Jansen committed
979
    // Should now contain RRSIG and NSEC record as well.
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
    responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
                  NULL,
                  (string(unsigned_delegation_txt) +
                   string(unsigned_delegation_nsec_txt) +
                   string("unsigned-delegation.example.com. 3600 IN RRSIG ") +
                   getCommonRRSIGText("NSEC")).c_str(),
                  NULL);
}

TEST_F(QueryTest, badSecureDelegation) {
    // Test whether exception is raised if DS query at delegation results in
    // something different than SUCCESS or NXRRSET
    EXPECT_THROW(Query(memory_client, Name("bad-delegation.example.com"),
                       qtype, response, true).process(), Query::BadDS);

    // But only if DNSSEC is requested (it shouldn't even try to look for
    // the DS otherwise)
    EXPECT_NO_THROW(Query(memory_client, Name("bad-delegation.example.com"),
                          qtype, response).process());
999
1000
}

For faster browsing, not all history is shown. View entire blame