query_unittest.cc 25.5 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
// 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 =
chenzhengzhang's avatar
chenzhengzhang committed
62
63
64
65
    "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";
66
67
68
69
70
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
// The DNAME to do tests against
79
const char* const dname_txt =
80
81
    "dname.example.com. 3600 IN DNAME "
    "somethinglong.dnametarget.example.com.\n";
82
// Some data at the dname node (allowed by RFC 2672)
83
84
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
85
86
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
87
88
    "www.dname.example.com. 3600 IN CNAME "
    "www.somethinglong.dnametarget.example.com.\n";
89
90
91
92
93
// 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
94

95
// This is a mock Zone class for testing.
96
97
98
99
100
101
// 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".
102
103
104
105
// 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).
Michal Vaner's avatar
Michal Vaner committed
106
class MockZone : public Zone {
107
public:
108
    MockZone() :
Michal Vaner's avatar
Michal Vaner committed
109
        origin_(Name("example.com")),
110
        delegation_name_("delegation.example.com"),
111
        dname_name_("dname.example.com"),
112
113
114
        has_SOA_(true),
        has_apex_NS_(true),
        rrclass_(RRClass::IN())
115
    {
116
117
        stringstream zone_stream;
        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
118
            delegation_txt << mx_txt << www_a_txt << cname_txt <<
119
            cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
120
            other_zone_rrs;
121
122
123

        masterLoad(zone_stream, origin_, rrclass_,
                   boost::bind(&MockZone::loadRRset, this, _1));
124
    }
125
126
127
128
    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
129
                            RRsetList* target = NULL,
130
                            const FindOptions options = FIND_DEFAULT) const;
131

132
133
    // If false is passed, it makes the zone broken as if it didn't have the
    // SOA.
134
    void setSOAFlag(bool on) { has_SOA_ = on; }
135

136
137
138
    // 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; }
139

Jerry's avatar
Jerry committed
140
private:
141
142
143
144
145
146
147
    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()) {
148
            delegation_rrset_ = rrset;
149
        } else if (rrset->getName() == dname_name_ &&
150
            rrset->getType() == RRType::DNAME()) {
151
            dname_rrset_ = rrset;
152
153
154
155
        }
    }

    const Name origin_;
156
    // Names where we delegate somewhere else
157
    const Name delegation_name_;
158
    const Name dname_name_;
Michal Vaner's avatar
Michal Vaner committed
159
    bool has_SOA_;
Jerry's avatar
Jerry committed
160
    bool has_apex_NS_;
161
    ConstRRsetPtr delegation_rrset_;
162
    ConstRRsetPtr dname_rrset_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
163
    const RRClass rrclass_;
164
165
166
};

Zone::FindResult
167
MockZone::find(const Name& name, const RRType& type,
168
               RRsetList* target, const FindOptions options) const
169
{
170
171
172
    // 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_) {
173
        return (FindResult(NXDOMAIN, RRsetPtr()));
174
175
176
177
178
179
180
181
182
    } 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)) {
183
        return (FindResult(DELEGATION, delegation_rrset_));
184
185
    // And under DNAME
    } else if (name.compare(dname_name_).getRelation() ==
186
        NameComparisonResult::SUBDOMAIN) {
187
        return (FindResult(DNAME, dname_rrset_));
188
189
190
191
192
193
194
195
196
197
198
199
200
    }

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

201
202
        // If not found but we have a target, fill it with all RRsets here
        if (!found_domain->second.empty() && target != NULL) {
chenzhengzhang's avatar
chenzhengzhang committed
203
            for (found_rrset = found_domain->second.begin();
204
                 found_rrset != found_domain->second.end(); found_rrset++) {
chenzhengzhang's avatar
chenzhengzhang committed
205
                // Insert RRs under the domain name into target
chenzhengzhang's avatar
chenzhengzhang committed
206
207
                target->addRRset(
                    boost::const_pointer_cast<RRset>(found_rrset->second));
chenzhengzhang's avatar
chenzhengzhang committed
208
            }
209
210
211
212
213
214
215
216
217
218
            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.
219
        return (FindResult(NXRRSET, RRsetPtr()));
220
221
    }

222
223
    // query name isn't found in our domains.  returns NXDOMAIN.
    return (FindResult(NXDOMAIN, RRsetPtr()));
224
225
}

226
227
228
229
230
class QueryTest : public ::testing::Test {
protected:
    QueryTest() :
        qname(Name("www.example.com")), qclass(RRClass::IN()),
        qtype(RRType::A()), response(Message::RENDER),
231
        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
232
233
    {
        response.setRcode(Rcode::NOERROR());
234
235
        response.setOpcode(Opcode::QUERY());
        // create and add a matching zone.
236
237
        mock_zone = new MockZone();
        memory_datasrc.addZone(ZonePtr(mock_zone));
238
    }
239
    MockZone* mock_zone;
240
    MemoryDataSrc memory_datasrc;
241
242
243
244
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
    Message response;
245
246
    const qid_t qid;
    const uint16_t query_code;
247
248
};

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// 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
270
271
                    response.endSection(Message::SECTION_ANSWER),
                    check_origin);
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    }
    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));
    }
}

286
TEST_F(QueryTest, noZone) {
287
    // There's no zone in the memory datasource.  So the response should have
288
    // REFUSED.
289
290
291
    MemoryDataSrc empty_memory_datasrc;
    Query nozone_query(empty_memory_datasrc, qname, qtype, response);
    EXPECT_NO_THROW(nozone_query.process());
292
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
293
294
}

295
TEST_F(QueryTest, exactMatch) {
296
    Query query(memory_datasrc, qname, qtype, response);
Jerry's avatar
Jerry committed
297
    EXPECT_NO_THROW(query.process());
298
    // find match rrset
299
300
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  www_a_txt, zone_ns_txt, ns_addrs_txt);
301
}
302

303
304
305
TEST_F(QueryTest, exactAddrMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
306
307
    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"), qtype,
                          response).process());
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
316
317
TEST_F(QueryTest, apexNSMatch) {
    // find match rrset, omit authority data which has already been provided
    // in the answer section from the authority section.
318
319
    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"), RRType::NS(),
                          response).process());
320

321
322
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
                  zone_ns_txt, NULL, ns_addrs_txt);
323
324
}

chenzhengzhang's avatar
chenzhengzhang committed
325
// test type any query logic
326
327
328
TEST_F(QueryTest, exactAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
329
330
    EXPECT_NO_THROW(Query(memory_datasrc, Name("noglue.example.com"),
                          RRType::ANY(), response).process());
331

332
333
334
335
336
    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");
337
338
}

chenzhengzhang's avatar
chenzhengzhang committed
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
TEST_F(QueryTest, apexAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"),
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 0,
                  "example.com. 3600 IN SOA . . 0 0 0 0 0\n"
                  "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",
                  NULL, NULL, mock_zone->getOrigin());
}

TEST_F(QueryTest, glueANYMatch) {
    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
}

TEST_F(QueryTest, nodomainANY) {
    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                  NULL, soa_txt, NULL, mock_zone->getOrigin());
}

366
// This tests that when we need to look up Zone's apex NS records for
Jerry's avatar
Jerry committed
367
368
369
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
TEST_F(QueryTest, noApexNS) {
370
371
    // Disable apex NS record
    mock_zone->setApexNSFlag(false);
372

373
374
375
    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
376
377
}

378
TEST_F(QueryTest, delegation) {
379
380
    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
                          qtype, response).process());
381

382
383
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
384
}
385

386
TEST_F(QueryTest, nxdomain) {
387
388
    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"), qtype,
                          response).process());
389
390
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
                  NULL, soa_txt, NULL, mock_zone->getOrigin());
391
}
392

393
TEST_F(QueryTest, nxrrset) {
394
395
    EXPECT_NO_THROW(Query(memory_datasrc, Name("www.example.com"),
                          RRType::TXT(), response).process());
396

397
398
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
                  NULL, soa_txt, NULL, mock_zone->getOrigin());
399
400
}

Michal Vaner's avatar
Michal Vaner committed
401
402
403
404
405
/*
 * This tests that when there's no SOA and we need a negative answer. It should
 * throw in that case.
 */
TEST_F(QueryTest, noSOA) {
406
407
    // disable zone's SOA RR.
    mock_zone->setSOAFlag(false);
Michal Vaner's avatar
Michal Vaner committed
408
409

    // The NX Domain
410
411
    EXPECT_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
412
413
414
    // Of course, we don't look into the response, as it throwed

    // NXRRSET
415
416
    EXPECT_THROW(Query(memory_datasrc, Name("nxrrset.example.com"),
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
417
418
}

419
TEST_F(QueryTest, noMatchZone) {
420
    // there's a zone in the memory datasource but it doesn't match the qname.
421
    // should result in REFUSED.
422
    Query(memory_datasrc, Name("example.org"), qtype, response).process();
423
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
424
}
425

Michal Vaner's avatar
Michal Vaner committed
426
427
428
429
430
431
/*
 * 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
432
TEST_F(QueryTest, MX) {
433
434
    Query(memory_datasrc, Name("mx.example.com"), RRType::MX(),
          response).process();
435

436
437
438
    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
439
440
}

Michal Vaner's avatar
Michal Vaner committed
441
/*
442
 * Test when we ask for MX whose exchange is an alias (CNAME in this case).
Michal Vaner's avatar
Michal Vaner committed
443
 *
444
 * This should not trigger the additional processing for the exchange.
Michal Vaner's avatar
Michal Vaner committed
445
446
 */
TEST_F(QueryTest, MXAlias) {
447
448
    Query(memory_datasrc, Name("cnamemx.example.com"), RRType::MX(),
          response).process();
Michal Vaner's avatar
Michal Vaner committed
449

450
451
452
453
454
    // 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
455
}
456
457

/*
458
459
460
461
 * Tests encountering a cname.
 *
 * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
 * out of the zone.
462
463
464
465
466
467
468
469
 *
 * 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();

470
471
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_txt, NULL, NULL);
472
473
}

474
475
476
477
478
479
480
481
482
483
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);
}

484
485
TEST_F(QueryTest, CNAME_NX_RRSET) {
    // Leads to www.example.com, it doesn't have TXT
486
487
488
    // 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.
489
490
491
    Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
        response).process();

492
493
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_txt, NULL, NULL);
494
495
}

496
497
498
499
500
501
502
503
504
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);
}

505
506
TEST_F(QueryTest, CNAME_NX_DOMAIN) {
    // Leads to nxdomain.example.com
507
508
509
510
511
    // 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.
512
513
514
    Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
        response).process();

515
516
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_nxdom_txt, NULL, NULL);
517
518
}

519
520
521
522
523
524
525
526
527
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);
}

528
529
530
531
532
533
534
535
536
537
538
539
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();

540
541
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_out_txt, NULL, NULL);
542
543
}

544
545
546
547
548
549
550
551
552
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);
}

553
554
555
556
557
558
559
560
561
562
563
564
/*
 * Test a query under a domain with DNAME. We should get a synthetized CNAME
 * as well as the DNAME.
 *
 * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
 * as well. This includes tests pointing inside the zone, outside the zone,
 * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
 */
TEST_F(QueryTest, DNAME) {
    Query(memory_datasrc, Name("www.dname.example.com"), RRType::A(),
        response).process();

565
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
566
        (string(dname_txt) + synthetized_cname_txt).c_str(),
567
        NULL, NULL);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
568
569
570
571
572
573
}

/*
 * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
 * CNAME.
 *
574
575
 * ANY is handled specially sometimes. We check it is not the case with
 * DNAME.
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
576
577
578
579
580
 */
TEST_F(QueryTest, DNAME_ANY) {
    Query(memory_datasrc, Name("www.dname.example.com"), RRType::ANY(),
        response).process();

581
582
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
        (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
}

// Test when we ask for DNAME explicitly, it does no synthetizing.
TEST_F(QueryTest, explicitDNAME) {
    Query(memory_datasrc, Name("dname.example.com"), RRType::DNAME(),
        response).process();

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

/*
 * Request a RRset at the domain with DNAME. It should not synthetize
 * the CNAME, it should return the RRset.
 */
TEST_F(QueryTest, DNAME_A) {
    Query(memory_datasrc, Name("dname.example.com"), RRType::A(),
        response).process();

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

/*
 * Request a RRset at the domain with DNAME that is not there (NXRRSET).
 * It should not synthetize the CNAME.
 */
TEST_F(QueryTest, DNAME_NX_RRSET) {
    EXPECT_NO_THROW(Query(memory_datasrc, Name("dname.example.com"),
        RRType::TXT(), response).process());

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

618
619
620
621
622
623
/*
 * Constructing the CNAME will result in a name that is too long. This,
 * however, should not throw (and crash the server), but respond with
 * YXDOMAIN.
 */
TEST_F(QueryTest, LongDNAME) {
624
625
626
627
628
629
630
631
    // A name that is as long as it can be
    Name longname(
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "dname.example.com.");
    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
632
633
        response).process());

634
    responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
635
        dname_txt, NULL, NULL);
636
637
}

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
/*
 * Constructing the CNAME will result in a name of maximal length.
 * This tests that we don't reject valid one by some kind of off by
 * one mistake.
 */
TEST_F(QueryTest, MaxLenDNAME) {
    Name longname(
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "dname.example.com.");
    EXPECT_NO_THROW(Query(memory_datasrc, longname, RRType::A(),
        response).process());

    // Check the answer is OK
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
        NULL, NULL, NULL);

    // Check that the CNAME has the maximal length.
    bool ok(false);
    for (RRsetIterator i(response.beginSection(Message::SECTION_ANSWER));
        i != response.endSection(Message::SECTION_ANSWER); ++ i) {
        if ((*i)->getType() == RRType::CNAME()) {
            ok = true;
            RdataIteratorPtr ci((*i)->getRdataIterator());
            ASSERT_FALSE(ci->isLast()) << "The CNAME is empty";
            /*
             * Does anybody have a clue why, if the Name::MAX_WIRE is put
             * directly inside ASSERT_EQ, it fails to link and complains
             * it is unresolved external?
             */
            size_t max_len(Name::MAX_WIRE);
            ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
                ci->getCurrent()).getCname().getLength());
        }
    }
    EXPECT_TRUE(ok) << "The synthetized CNAME not found";
}

678
}