query_unittest.cc 31.3 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
// It is a derived class of ZoneFinder for the convenient of tests.
97
// Its find() method emulates the common behavior of protocol compliant
98
// ZoneFinder classes, but simplifies some minor cases and also supports broken
99
100
101
// 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).
106
class MockZoneFinder : public ZoneFinder {
107
public:
108
    MockZoneFinder() :
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
        has_SOA_(true),
        has_apex_NS_(true),
114
115
        rrclass_(RRClass::IN()),
        include_rrsig_anyway_(false)
116
    {
117
118
        stringstream zone_stream;
        zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
119
            delegation_txt << mx_txt << www_a_txt << cname_txt <<
120
            cname_nxdom_txt << cname_out_txt << dname_txt << dname_a_txt <<
121
            other_zone_rrs;
122
123

        masterLoad(zone_stream, origin_, rrclass_,
124
                   boost::bind(&MockZoneFinder::loadRRset, this, _1));
125
    }
126
127
    virtual isc::dns::Name getOrigin() const { return (origin_); }
    virtual isc::dns::RRClass getClass() const { return (rrclass_); }
128
129
    virtual FindResult find(const isc::dns::Name& name,
                            const isc::dns::RRType& type,
chenzhengzhang's avatar
chenzhengzhang committed
130
                            RRsetList* target = NULL,
131
                            const FindOptions options = FIND_DEFAULT);
132

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

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

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

Jerry's avatar
Jerry committed
144
private:
145
146
147
    typedef map<RRType, ConstRRsetPtr> RRsetStore;
    typedef map<Name, RRsetStore> Domains;
    Domains domains_;
148
    void loadRRset(RRsetPtr rrset) {
149
150
151
        domains_[rrset->getName()][rrset->getType()] = rrset;
        if (rrset->getName() == delegation_name_ &&
            rrset->getType() == RRType::NS()) {
152
            delegation_rrset_ = rrset;
153
        } else if (rrset->getName() == dname_name_ &&
154
            rrset->getType() == RRType::DNAME()) {
155
            dname_rrset_ = rrset;
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
        // Add some signatures
        } else if (rrset->getName() == Name("example.com.") &&
                   rrset->getType() == RRType::NS()) {
            rrset->addRRsig(RdataPtr(new generic::RRSIG("NS 5 3 3600 "
                                                        "20000101000000 "
                                                        "20000201000000 "
                                                        "12345 example.com. "
                                                        "FAKEFAKEFAKE")));
        } else if (rrset->getType() == RRType::A()) {
            rrset->addRRsig(RdataPtr(new generic::RRSIG("A 5 3 3600 "
                                                        "20000101000000 "
                                                        "20000201000000 "
                                                        "12345 example.com. "
                                                        "FAKEFAKEFAKE")));
        } else if (rrset->getType() == RRType::AAAA()) {
            rrset->addRRsig(RdataPtr(new generic::RRSIG("AAAA 5 3 3600 "
                                                        "20000101000000 "
                                                        "20000201000000 "
                                                        "12345 example.com. "
                                                        "FAKEFAKEFAKE")));
176
177
178
179
        }
    }

    const Name origin_;
180
    // Names where we delegate somewhere else
181
    const Name delegation_name_;
182
    const Name dname_name_;
Michal Vaner's avatar
Michal Vaner committed
183
    bool has_SOA_;
Jerry's avatar
Jerry committed
184
    bool has_apex_NS_;
185
    ConstRRsetPtr delegation_rrset_;
186
    ConstRRsetPtr dname_rrset_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
187
    const RRClass rrclass_;
188
    bool include_rrsig_anyway_;
189
190
};

191
192
ZoneFinder::FindResult
MockZoneFinder::find(const Name& name, const RRType& type,
193
                     RRsetList* target, const FindOptions options)
194
{
195
196
197
    // 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_) {
198
        return (FindResult(NXDOMAIN, RRsetPtr()));
199
200
201
202
203
204
205
206
207
    } 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)) {
208
        return (FindResult(DELEGATION, delegation_rrset_));
209
210
    // And under DNAME
    } else if (name.compare(dname_name_).getRelation() ==
211
        NameComparisonResult::SUBDOMAIN) {
212
        return (FindResult(DNAME, dname_rrset_));
213
214
215
216
217
218
219
220
221
222
    }

    // 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()) {
223
224
225
226
            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
            if (options & ZoneFinder::FIND_DNSSEC ||
227
                include_rrsig_anyway_ ||
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
                !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));
243
244
        }

245
246
        // 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
247
            for (found_rrset = found_domain->second.begin();
248
                 found_rrset != found_domain->second.end(); ++found_rrset) {
chenzhengzhang's avatar
chenzhengzhang committed
249
                // Insert RRs under the domain name into target
chenzhengzhang's avatar
chenzhengzhang committed
250
251
                target->addRRset(
                    boost::const_pointer_cast<RRset>(found_rrset->second));
chenzhengzhang's avatar
chenzhengzhang committed
252
            }
253
254
255
256
257
258
259
260
261
262
            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.
263
        return (FindResult(NXRRSET, RRsetPtr()));
264
265
    }

266
267
    // query name isn't found in our domains.  returns NXDOMAIN.
    return (FindResult(NXDOMAIN, RRsetPtr()));
268
269
}

270
271
272
273
274
class QueryTest : public ::testing::Test {
protected:
    QueryTest() :
        qname(Name("www.example.com")), qclass(RRClass::IN()),
        qtype(RRType::A()), response(Message::RENDER),
275
        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
276
277
    {
        response.setRcode(Rcode::NOERROR());
278
279
        response.setOpcode(Opcode::QUERY());
        // create and add a matching zone.
280
        mock_finder = new MockZoneFinder();
281
        memory_client.addZone(ZoneFinderPtr(mock_finder));
282
    }
283
    MockZoneFinder* mock_finder;
284
285
286
287
    // 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.
288
    InMemoryClient memory_client;
289
290
291
292
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
    Message response;
293
294
    const qid_t qid;
    const uint16_t query_code;
295
296
};

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// 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
318
319
                    response.endSection(Message::SECTION_ANSWER),
                    check_origin);
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    }
    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));
    }
}

334
TEST_F(QueryTest, noZone) {
335
    // There's no zone in the memory datasource.  So the response should have
336
    // REFUSED.
337
338
    InMemoryClient empty_memory_client;
    Query nozone_query(empty_memory_client, qname, qtype, response);
339
    EXPECT_NO_THROW(nozone_query.process());
340
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
341
342
}

343
TEST_F(QueryTest, exactMatch) {
344
    Query query(memory_client, qname, qtype, response);
Jerry's avatar
Jerry committed
345
    EXPECT_NO_THROW(query.process());
346
    // find match rrset
347
348
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                  www_a_txt, zone_ns_txt, ns_addrs_txt);
349
}
350

351
352
353
354
355
356
357
358
359
360
361
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);
}

362
363
364
365
366
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
367
368
369
370
371
    // We can't let responseCheck to check the additional section as well,
    // it gets confused by the two RRs for glue.delegation.../RRSIG due
    // to it's design and fixing it would be hard. Therefore we simply
    // check manually this one time.
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
372
                  (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
373
                                           "A 5 3 3600 20000101000000 "
374
375
376
377
378
379
                                           "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")).
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
                  c_str(), NULL);
    RRsetIterator iterator(response.beginSection(Message::SECTION_ADDITIONAL));
    const char* additional[] = {
        "glue.delegation.example.com. 3600 IN A 192.0.2.153\n",
        "glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
        "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n",
        "glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 "
            "20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE\n",
        "noglue.example.com. 3600 IN A 192.0.2.53\n",
        "noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
        NULL
    };
    for (const char** rr(additional); *rr != NULL; ++ rr) {
        ASSERT_FALSE(iterator ==
                     response.endSection(Message::SECTION_ADDITIONAL));
        EXPECT_EQ(*rr, (*iterator)->toText());
        iterator ++;
    }
    EXPECT_TRUE(iterator == response.endSection(Message::SECTION_ADDITIONAL));
401
402
}

403
404
405
TEST_F(QueryTest, exactAddrMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
406
    EXPECT_NO_THROW(Query(memory_client, Name("noglue.example.com"), qtype,
407
                          response).process());
408

409
410
411
412
    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");
413
414
}

415
416
417
TEST_F(QueryTest, apexNSMatch) {
    // find match rrset, omit authority data which has already been provided
    // in the answer section from the authority section.
418
    EXPECT_NO_THROW(Query(memory_client, Name("example.com"), RRType::NS(),
419
                          response).process());
420

421
422
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
                  zone_ns_txt, NULL, ns_addrs_txt);
423
424
}

chenzhengzhang's avatar
chenzhengzhang committed
425
// test type any query logic
426
427
428
TEST_F(QueryTest, exactAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
429
    EXPECT_NO_THROW(Query(memory_client, Name("noglue.example.com"),
430
                          RRType::ANY(), response).process());
431

432
433
434
435
436
    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");
437
438
}

chenzhengzhang's avatar
chenzhengzhang committed
439
440
441
TEST_F(QueryTest, apexAnyMatch) {
    // find match rrset, omit additional data which has already been provided
    // in the answer section from the additional.
442
    EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
443
                          RRType::ANY(), response).process());
444
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 3,
chenzhengzhang's avatar
chenzhengzhang committed
445
446
447
448
                  "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",
449
                  NULL, ns_addrs_txt, mock_finder->getOrigin());
450
451
452
}

TEST_F(QueryTest, mxANYMatch) {
453
    EXPECT_NO_THROW(Query(memory_client, Name("mx.example.com"),
454
455
456
457
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
                  mx_txt, zone_ns_txt,
                  (string(ns_addrs_txt) + string(www_a_txt)).c_str());
chenzhengzhang's avatar
chenzhengzhang committed
458
459
460
}

TEST_F(QueryTest, glueANYMatch) {
461
    EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
462
463
464
465
466
467
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
}

TEST_F(QueryTest, nodomainANY) {
468
    EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"),
chenzhengzhang's avatar
chenzhengzhang committed
469
470
                          RRType::ANY(), response).process());
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
471
                  NULL, soa_txt, NULL, mock_finder->getOrigin());
chenzhengzhang's avatar
chenzhengzhang committed
472
473
}

474
// This tests that when we need to look up Zone's apex NS records for
Jerry's avatar
Jerry committed
475
476
477
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
TEST_F(QueryTest, noApexNS) {
478
    // Disable apex NS record
479
    mock_finder->setApexNSFlag(false);
480

481
    EXPECT_THROW(Query(memory_client, Name("noglue.example.com"), qtype,
482
483
                       response).process(), Query::NoApexNS);
    // We don't look into the response, as it threw
Jerry's avatar
Jerry committed
484
485
}

486
TEST_F(QueryTest, delegation) {
487
    EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
488
                          qtype, response).process());
489

490
491
    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
                  NULL, delegation_txt, ns_addrs_txt);
492
}
493

494
TEST_F(QueryTest, nxdomain) {
495
    EXPECT_NO_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
496
                          response).process());
497
    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
498
                  NULL, soa_txt, NULL, mock_finder->getOrigin());
499
}
500

501
TEST_F(QueryTest, nxrrset) {
502
    EXPECT_NO_THROW(Query(memory_client, Name("www.example.com"),
503
                          RRType::TXT(), response).process());
504

505
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
506
                  NULL, soa_txt, NULL, mock_finder->getOrigin());
507
508
}

Michal Vaner's avatar
Michal Vaner committed
509
510
511
512
513
/*
 * This tests that when there's no SOA and we need a negative answer. It should
 * throw in that case.
 */
TEST_F(QueryTest, noSOA) {
514
    // disable zone's SOA RR.
515
    mock_finder->setSOAFlag(false);
Michal Vaner's avatar
Michal Vaner committed
516
517

    // The NX Domain
518
    EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"),
519
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
520
521
522
    // Of course, we don't look into the response, as it throwed

    // NXRRSET
523
    EXPECT_THROW(Query(memory_client, Name("nxrrset.example.com"),
524
                       qtype, response).process(), Query::NoSOA);
Michal Vaner's avatar
Michal Vaner committed
525
526
}

527
TEST_F(QueryTest, noMatchZone) {
528
    // there's a zone in the memory datasource but it doesn't match the qname.
529
    // should result in REFUSED.
530
    Query(memory_client, Name("example.org"), qtype, response).process();
531
    EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
532
}
533

Michal Vaner's avatar
Michal Vaner committed
534
535
536
537
538
539
/*
 * 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
540
TEST_F(QueryTest, MX) {
541
    Query(memory_client, Name("mx.example.com"), RRType::MX(),
542
          response).process();
543

544
545
546
    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
547
548
}

Michal Vaner's avatar
Michal Vaner committed
549
/*
550
 * Test when we ask for MX whose exchange is an alias (CNAME in this case).
Michal Vaner's avatar
Michal Vaner committed
551
 *
552
 * This should not trigger the additional processing for the exchange.
Michal Vaner's avatar
Michal Vaner committed
553
554
 */
TEST_F(QueryTest, MXAlias) {
555
    Query(memory_client, Name("cnamemx.example.com"), RRType::MX(),
556
          response).process();
Michal Vaner's avatar
Michal Vaner committed
557

558
559
560
561
562
    // 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
563
}
564
565

/*
566
567
568
569
 * Tests encountering a cname.
 *
 * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
 * out of the zone.
570
571
572
573
574
 *
 * TODO: We currently don't do chaining, so only the CNAME itself should be
 * returned.
 */
TEST_F(QueryTest, CNAME) {
575
    Query(memory_client, Name("cname.example.com"), RRType::A(),
576
577
        response).process();

578
579
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_txt, NULL, NULL);
580
581
}

582
583
584
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.
585
    Query(memory_client, Name("cname.example.com"), RRType::CNAME(),
586
587
588
589
590
591
        response).process();

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

592
593
TEST_F(QueryTest, CNAME_NX_RRSET) {
    // Leads to www.example.com, it doesn't have TXT
594
595
596
    // 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.
597
    Query(memory_client, Name("cname.example.com"), RRType::TXT(),
598
599
        response).process();

600
601
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_txt, NULL, NULL);
602
603
}

604
605
TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
    // same owner name as the NXRRSET test but explicitly query for CNAME RR.
606
    Query(memory_client, Name("cname.example.com"), RRType::CNAME(),
607
608
609
610
611
612
        response).process();

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

613
614
TEST_F(QueryTest, CNAME_NX_DOMAIN) {
    // Leads to nxdomain.example.com
615
616
617
618
619
    // 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.
620
    Query(memory_client, Name("cnamenxdom.example.com"), RRType::A(),
621
622
        response).process();

623
624
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_nxdom_txt, NULL, NULL);
625
626
}

627
628
TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
    // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
629
    Query(memory_client, Name("cnamenxdom.example.com"), RRType::CNAME(),
630
631
632
633
634
635
        response).process();

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

636
637
638
639
640
641
642
643
644
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)
     */
645
    Query(memory_client, Name("cnameout.example.com"), RRType::A(),
646
647
        response).process();

648
649
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
        cname_out_txt, NULL, NULL);
650
651
}

652
653
TEST_F(QueryTest, explicitCNAME_OUT) {
    // same owner name as the OUT test but explicitly query for CNAME RR.
654
    Query(memory_client, Name("cnameout.example.com"), RRType::CNAME(),
655
656
657
658
659
660
        response).process();

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

661
662
663
664
665
666
667
668
669
/*
 * 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) {
670
    Query(memory_client, Name("www.dname.example.com"), RRType::A(),
671
672
        response).process();

673
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
674
        (string(dname_txt) + synthetized_cname_txt).c_str(),
675
        NULL, NULL);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
676
677
678
679
680
681
}

/*
 * Ask an ANY query below a DNAME. Should return the DNAME and synthetized
 * CNAME.
 *
682
683
 * ANY is handled specially sometimes. We check it is not the case with
 * DNAME.
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
684
685
 */
TEST_F(QueryTest, DNAME_ANY) {
686
    Query(memory_client, Name("www.dname.example.com"), RRType::ANY(),
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
687
688
        response).process();

689
690
    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
        (string(dname_txt) + synthetized_cname_txt).c_str(), NULL, NULL);
691
692
693
694
}

// Test when we ask for DNAME explicitly, it does no synthetizing.
TEST_F(QueryTest, explicitDNAME) {
695
    Query(memory_client, Name("dname.example.com"), RRType::DNAME(),
696
697
698
699
700
701
702
703
704
705
706
        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) {
707
    Query(memory_client, Name("dname.example.com"), RRType::A(),
708
709
710
711
712
713
714
715
716
717
718
        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) {
719
    EXPECT_NO_THROW(Query(memory_client, Name("dname.example.com"),
720
721
722
        RRType::TXT(), response).process());

    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
723
        NULL, soa_txt, NULL, mock_finder->getOrigin());
724
725
}

726
727
728
729
730
731
/*
 * 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) {
732
733
734
735
736
737
738
    // A name that is as long as it can be
    Name longname(
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
        "dname.example.com.");
739
    EXPECT_NO_THROW(Query(memory_client, longname, RRType::A(),
740
741
        response).process());

742
    responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
743
        dname_txt, NULL, NULL);
744
745
}

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
746
747
748
749
750
751
752
753
754
755
756
757
/*
 * 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.");
758
    EXPECT_NO_THROW(Query(memory_client, longname, RRType::A(),
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
        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?
             */
778
            const size_t max_len(Name::MAX_WIRE);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
779
780
781
782
783
784
785
            ASSERT_EQ(max_len, dynamic_cast<const rdata::generic::CNAME&>(
                ci->getCurrent()).getCname().getLength());
        }
    }
    EXPECT_TRUE(ok) << "The synthetized CNAME not found";
}

786
}