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

15
#include <sstream>
16
17
#include <vector>
#include <map>
18

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

#include <dns/masterload.h>
22
23
#include <dns/message.h>
#include <dns/name.h>
24
#include <dns/opcode.h>
25
#include <dns/rcode.h>
26
#include <dns/rrttl.h>
27
#include <dns/rrtype.h>
28
#include <dns/rdataclass.h>
29

Michal Vaner's avatar
Michal Vaner committed
30
#include <datasrc/memory_datasrc.h>
31
32
33

#include <auth/query.h>

34
#include <testutils/dnsmessage_test.h>
35

36
37
#include <gtest/gtest.h>

38
using namespace std;
39
using namespace isc::dns;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
40
using namespace isc::dns::rdata;
41
42
using namespace isc::datasrc;
using namespace isc::auth;
43
using namespace isc::testutils;
44

45
46
namespace {

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 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).
const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
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";
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 =
        "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";
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";
const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
71
72
const char* const cname_txt =
    "cname.example.com. 3600 IN CNAME www.example.com.\n";
73
74
75
76
77
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";
78
79
80
81
82
// 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";
Michal Vaner's avatar
Michal Vaner committed
83

84
// This is a mock Zone class for testing.
85
86
87
88
89
90
91
92
// It is a derived class of Zone for the convenient of tests.
// Its find() method emulates the common behavior of protocol compliant
// zone classes, but simplifies some minor cases and also supports broken
// behavior.
// For simplicity, most names are assumed to be "in zone"; there's only
// one zone cut at the point of name "delegation.example.com".
// It doesn't handle empty non terminal nodes (if we need to test such cases
// find() should have specialized code for it).
Michal Vaner's avatar
Michal Vaner committed
93
class MockZone : public Zone {
94
public:
95
    MockZone() :
Michal Vaner's avatar
Michal Vaner committed
96
        origin_(Name("example.com")),
97
98
99
100
        delegation_name_("delegation.example.com"),
        has_SOA_(true),
        has_apex_NS_(true),
        rrclass_(RRClass::IN())
101
    {
102
103
        stringstream zone_stream;
        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
104
            delegation_txt << mx_txt << www_a_txt << cname_txt <<
105
            cname_nxdom_txt << cname_out_txt << other_zone_rrs;
106
107
108

        masterLoad(zone_stream, origin_, rrclass_,
                   boost::bind(&MockZone::loadRRset, this, _1));
109
    }
110
111
112
113
    virtual const isc::dns::Name& getOrigin() const { return (origin_); }
    virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
    virtual FindResult find(const isc::dns::Name& name,
                            const isc::dns::RRType& type,
chenzhengzhang's avatar
chenzhengzhang committed
114
                            RRsetList* target = NULL,
115
                            const FindOptions options = FIND_DEFAULT) const;
116

117
118
    // If false is passed, it makes the zone broken as if it didn't have the
    // SOA.
119
    void setSOAFlag(bool on) { has_SOA_ = on; }
120

121
122
123
    // 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; }
124

Jerry's avatar
Jerry committed
125
private:
126
127
128
129
130
131
132
    typedef map<RRType, ConstRRsetPtr> RRsetStore;
    typedef map<Name, RRsetStore> Domains;
    Domains domains_;
    void loadRRset(ConstRRsetPtr rrset) {
        domains_[rrset->getName()][rrset->getType()] = rrset;
        if (rrset->getName() == delegation_name_ &&
            rrset->getType() == RRType::NS()) {
133
            delegation_rrset_ = rrset;
134
135
136
137
138
        }
    }

    const Name origin_;
    const Name delegation_name_;
Michal Vaner's avatar
Michal Vaner committed
139
    bool has_SOA_;
Jerry's avatar
Jerry committed
140
    bool has_apex_NS_;
141
    ConstRRsetPtr delegation_rrset_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
142
    const RRClass rrclass_;
143
144
145
};

Zone::FindResult
146
MockZone::find(const Name& name, const RRType& type,
147
               RRsetList* target, const FindOptions options) const
148
{
149
150
151
    // 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_) {
152
        return (FindResult(NXDOMAIN, RRsetPtr()));
153
154
155
156
157
158
159
160
161
    } else if (name == origin_ && type == RRType::NS() && !has_apex_NS_) {
        return (FindResult(NXDOMAIN, RRsetPtr()));
    }

    // Special case for names on or under a zone cut
    if ((options & FIND_GLUE_OK) == 0 &&
        (name == delegation_name_ ||
         name.compare(delegation_name_).getRelation() ==
         NameComparisonResult::SUBDOMAIN)) {
162
        return (FindResult(DELEGATION, delegation_rrset_));
163
164
165
166
167
168
169
170
171
172
173
174
175
176
    }

    // 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()) {
            return (FindResult(SUCCESS, found_rrset->second));
        }

        // If not found but the qtype is ANY, return the first RRset
chenzhengzhang's avatar
chenzhengzhang committed
177
178
179
180
181
182
183
184
185
186
        if (!found_domain->second.empty() && type == RRType::ANY()) {
            for (found_rrset = found_domain->second.begin();
                 found_rrset != found_domain->second.end(); found_rrset++)
            {
                // Insert RRs under the domain name into target
                if (target) {
                    target->addRRset(
                        boost::const_pointer_cast<RRset>(found_rrset->second));
                }
            }
187
188
189
190
191
192
193
194
195
196
            return (FindResult(SUCCESS, found_domain->second.begin()->second));
        }

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

        // Otherwise it's NXRRSET case.
197
        return (FindResult(NXRRSET, RRsetPtr()));
198
199
    }

200
201
    // query name isn't found in our domains.  returns NXDOMAIN.
    return (FindResult(NXDOMAIN, RRsetPtr()));
202
203
}

204
205
206
207
208
class QueryTest : public ::testing::Test {
protected:
    QueryTest() :
        qname(Name("www.example.com")), qclass(RRClass::IN()),
        qtype(RRType::A()), response(Message::RENDER),
209
        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
210
211
    {
        response.setRcode(Rcode::NOERROR());
212
213
        response.setOpcode(Opcode::QUERY());
        // create and add a matching zone.
214
215
        mock_zone = new MockZone();
        memory_datasrc.addZone(ZonePtr(mock_zone));
216
    }
217
    MockZone* mock_zone;
218
    MemoryDataSrc memory_datasrc;
219
220
221
222
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
    Message response;
223
224
    const qid_t qid;
    const uint16_t query_code;
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
// 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),
                    response.endSection(Message::SECTION_ANSWER));
    }
    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));
    }
}

263
TEST_F(QueryTest, noZone) {
264
    // There's no zone in the memory datasource.  So the response should have
265
    // REFUSED.
266
267
268
    MemoryDataSrc empty_memory_datasrc;
    Query nozone_query(empty_memory_datasrc, qname, qtype, response);
    EXPECT_NO_THROW(nozone_query.process());
269
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
270
271
}

272
TEST_F(QueryTest, exactMatch) {
273
    Query query(memory_datasrc, qname, qtype, response);
Jerry's avatar
Jerry committed
274
    EXPECT_NO_THROW(query.process());
275
    // find match rrset
276
277
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  www_a_txt, zone_ns_txt, ns_addrs_txt);
278
}
279

280
281
282
TEST_F(QueryTest, exactAddrMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
283
284
    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
                          response).process());
285

286
287
288
289
    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");
290
291
}

292
293
294
TEST_F(QueryTest, apexNSMatch) {
    // find match rrset, omit authority data which has already been provided
    // in the answer section from the authority section.
295
296
    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
                          response).process());
297

298
299
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
                  zone_ns_txt, NULL, ns_addrs_txt);
300
301
}

302
303
304
TEST_F(QueryTest, exactAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
305
306
    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
                          RRType::ANY(), response).process());
307

308
309
310
311
312
    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");
313
314
}

315
// This tests that when we need to look up Zone's apex NS records for
Jerry's avatar
Jerry committed
316
317
318
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
TEST_F(QueryTest, noApexNS) {
319
320
    // Disable apex NS record
    mock_zone->setApexNSFlag(false);
321

322
323
324
    EXPECT_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
                       response).process(), Query::NoApexNS);
    // We don't look into the response, as it threw
Jerry's avatar
Jerry committed
325
326
}

327
TEST_F(QueryTest, delegation) {
328
329
    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
                          qtype, response).process());
330

331
332
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
333
}
334

335
TEST_F(QueryTest, nxdomain) {
336
337
    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
                          response).process());
338
339
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                  NULL, soa_txt, NULL, mock_zone->getOrigin());
340
}
341

342
TEST_F(QueryTest, nxrrset) {
343
344
    EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
                          RRType::TXT(), response).process());
345

346
347
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
                  NULL, soa_txt, NULL, mock_zone->getOrigin());
348
349
}

Michal Vaner's avatar
Michal Vaner committed
350
351
352
353
354
/*
 * This tests that when there's no SOA and we need a negative answer. It should
 * throw in that case.
 */
TEST_F(QueryTest, noSOA) {
355
356
    // disable zone's SOA RR.
    mock_zone->setSOAFlag(false);
Michal Vaner's avatar
Michal Vaner committed
357
358

    // The NX Domain
359
360
    EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
361
362
363
    // Of course, we don't look into the response, as it throwed

    // NXRRSET
364
365
    EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
366
367
}

368
TEST_F(QueryTest, noMatchZone) {
369
    // there's a zone in the memory datasource but it doesn't match the qname.
370
    // should result in REFUSED.
371
    Query(memory_datasrc, Name("example.org"), qtype, response).process();
372
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
373
}
374

Michal Vaner's avatar
Michal Vaner committed
375
376
377
378
379
380
/*
 * Test MX additional processing.
 *
 * The MX RRset has two RRs, one pointing to a known domain with
 * A record, other to unknown out of zone one.
 */
Michal Vaner's avatar
Michal Vaner committed
381
TEST_F(QueryTest, MX) {
382
383
    Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
          response).process();
384

385
386
387
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
                  mx_txt, NULL,
                  (string(ns_addrs_txt) + string(www_a_txt)).c_str());
Michal Vaner's avatar
Michal Vaner committed
388
389
}

Michal Vaner's avatar
Michal Vaner committed
390
/*
391
 * Test when we ask for MX whose exchange is an alias (CNAME in this case).
Michal Vaner's avatar
Michal Vaner committed
392
 *
393
 * This should not trigger the additional processing for the exchange.
Michal Vaner's avatar
Michal Vaner committed
394
395
 */
TEST_F(QueryTest, MXAlias) {
396
397
    Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
          response).process();
Michal Vaner's avatar
Michal Vaner committed
398

399
400
401
402
403
    // there shouldn't be no additional RRs for the exchanges (we have 3
    // RRs for the NS).  The normal MX case is tested separately so we don't
    // bother to examine the answer (and authority) sections.
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  NULL, NULL, ns_addrs_txt);
Michal Vaner's avatar
Michal Vaner committed
404
}
405
406

/*
407
408
409
410
 * Tests encountering a cname.
 *
 * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
 * out of the zone.
411
412
413
414
415
416
417
418
419
420
421
422
 *
 * TODO: We currently don't do chaining, so only the CNAME itself should be
 * returned.
 */
TEST_F(QueryTest, CNAME) {
    Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_txt, zone_ns_txt, ns_addrs_txt);
}

423
424
425
426
427
428
429
430
431
432
TEST_F(QueryTest, explicitCNAME) {
    // same owner name as the CNAME test but explicitly query for CNAME RR.
    // expect the same response as we don't provide a full chain yet.
    Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_txt, zone_ns_txt, ns_addrs_txt);
}

433
434
TEST_F(QueryTest, CNAME_NX_RRSET) {
    // Leads to www.example.com, it doesn't have TXT
435
436
437
    // note: with chaining, what should be expected is not trivial:
    // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
    // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
438
439
440
441
442
443
444
    Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_txt, zone_ns_txt, ns_addrs_txt);
}

445
446
447
448
449
450
451
452
453
TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
    // same owner name as the NXRRSET test but explicitly query for CNAME RR.
    Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_txt, zone_ns_txt, ns_addrs_txt);
}

454
455
TEST_F(QueryTest, CNAME_NX_DOMAIN) {
    // Leads to nxdomain.example.com
456
457
458
459
460
    // note: with chaining, what should be expected is not trivial:
    // BIND 9 returns the CNAME in answer and SOA in authority, no additional,
    // RCODE being NXDOMAIN.
    // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
    // RCODE being NOERROR.
461
462
463
464
465
466
467
    Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}

468
469
470
471
472
473
474
475
476
TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
    // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
    Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::CNAME(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}

477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
TEST_F(QueryTest, CNAME_OUT) {
    /*
     * This leads out of zone. This should have only the CNAME even
     * when we do chaining.
     *
     * TODO: We should be able to have two zones in the mock data source.
     * Then the same test should be done with .org included there and
     * see what it does (depends on what we want to do)
     */
    Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_out_txt, zone_ns_txt, ns_addrs_txt);
}

493
494
495
496
497
498
499
500
501
TEST_F(QueryTest, explicitCNAME_OUT) {
    // same owner name as the OUT test but explicitly query for CNAME RR.
    Query(memory_datasrc, Name("cnameout.example.com"), RRType::CNAME(),
        response).process();

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
        cname_out_txt, zone_ns_txt, ns_addrs_txt);
}

502
}