auth_srv_unittest.cc 17.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 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.

// $Id$

17 18
#include <config.h>

19 20 21 22 23 24 25 26 27
#include <gtest/gtest.h>

#include <dns/buffer.h>
#include <dns/name.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>

28 29
#include <cc/data.h>

30 31
#include <xfr/xfrout_client.h>

32
#include <auth/auth_srv.h>
33
#include <auth/asio_link.h>
34 35 36 37 38 39

#include <dns/tests/unittest_util.h>

using isc::UnitTestUtil;
using namespace std;
using namespace isc::dns;
40
using namespace isc::data;
41
using namespace isc::xfr;
42
using namespace asio_link;
43 44

namespace {
45 46
const char* CONFIG_TESTDB =
    "{\"database_file\": \"" TEST_DATA_DIR "/example.sqlite3\"}";
47 48 49
// The following file must be non existent and must be non"creatable" (see
// the sqlite3 test).
const char* BADCONFIG_TESTDB =
50
    "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
51

52
class AuthSrvTest : public ::testing::Test {
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
private:
    class MockXfroutClient : public AbstractXfroutClient {
    public:
        MockXfroutClient() :
            is_connected_(false), connect_ok_(true), send_ok_(true),
            disconnect_ok_(true)
        {}
        virtual void connect();
        virtual void disconnect();
        virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
                                          uint16_t msg_len);
        bool isConnected() const { return (is_connected_); }
        void disableConnect() { connect_ok_ = false; }
        void disableDisconnect() { disconnect_ok_ = false; }
        void enableDisconnect() { disconnect_ok_ = true; }
        void disableSend() { send_ok_ = false; }
    private:
        bool is_connected_;
        bool connect_ok_;
        bool send_ok_;
        bool disconnect_ok_;
    };
75
protected:
76
    AuthSrvTest() : server(xfrout), request_message(Message::RENDER),
77
                    parse_message(Message::PARSE), default_qid(0x1035),
78
                    opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
79
                    qclass(RRClass::IN()), qtype(RRType::A()),
80
                    io_message(NULL), endpoint(NULL), request_obuffer(0),
81
                    request_renderer(request_obuffer),
82 83
                    response_obuffer(0), response_renderer(response_obuffer)
    {}
84 85
    ~AuthSrvTest() {
        delete io_message;
86
        delete endpoint;
87
    }
88
    MockXfroutClient xfrout;
89 90 91
    AuthSrv server;
    Message request_message;
    Message parse_message;
92 93 94 95 96
    const qid_t default_qid;
    const Opcode opcode;
    const Name qname;
    const RRClass qclass;
    const RRType qtype;
97 98
    IOMessage* io_message;
    const IOEndpoint* endpoint;
99 100 101 102 103
    OutputBuffer request_obuffer;
    MessageRenderer request_renderer;
    OutputBuffer response_obuffer;
    MessageRenderer response_renderer;
    vector<uint8_t> data;
104

JINMEI Tatuya's avatar
JINMEI Tatuya committed
105
    void createDataFromFile(const char* const datafile, int protocol);
106
    void createRequest(const Opcode& opcode, const Name& request_name,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
107 108
                       const RRClass& rrclass, const RRType& rrtype,
                       int protocol);
109 110
};

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
void
AuthSrvTest::MockXfroutClient::connect() {
    if (!connect_ok_) {
        isc_throw(XfroutError, "xfrout connection disabled for test");
    }
    is_connected_ = true;
}

void
AuthSrvTest::MockXfroutClient::disconnect() {
    if (!disconnect_ok_) {
        isc_throw(XfroutError,
                  "closing xfrout connection is disabled for test");
    }
    is_connected_ = false;
}

int
AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(
    const int tcp_sock UNUSED_PARAM,
    const void* msg_data UNUSED_PARAM,
    const uint16_t msg_len UNUSED_PARAM)
{
    if (!send_ok_) {
        isc_throw(XfroutError, "xfrout connection send for test");
    }
    return (0);
}

140 141 142 143 144 145 146 147 148 149 150 151
// These are flags to indicate whether the corresponding flag bit of the
// DNS header is to be set in the test cases.  (Note that the flag values
// is irrelevant to their wire-format values)
const unsigned int QR_FLAG = 0x1;
const unsigned int AA_FLAG = 0x2;
const unsigned int TC_FLAG = 0x4;
const unsigned int RD_FLAG = 0x8;
const unsigned int RA_FLAG = 0x10;
const unsigned int AD_FLAG = 0x20;
const unsigned int CD_FLAG = 0x40;

void
JINMEI Tatuya's avatar
JINMEI Tatuya committed
152 153 154
AuthSrvTest::createDataFromFile(const char* const datafile,
                                const int protocol = IPPROTO_UDP)
{
155
    delete io_message;
156 157
    data.clear();

158
    delete endpoint;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
159
    endpoint = IOEndpoint::create(protocol, IOAddress("192.0.2.1"), 5300);
160
    UnitTestUtil::readWireData(datafile, data);
161
    io_message = new IOMessage(&data[0], data.size(),
162 163 164
                               protocol == IPPROTO_UDP ?
                               IOSocket::getDummyUDPSocket() :
                               IOSocket::getDummyTCPSocket(), *endpoint);
165 166
}

167 168
void
AuthSrvTest::createRequest(const Opcode& opcode, const Name& request_name,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
169 170
                           const RRClass& rrclass, const RRType& rrtype,
                           const int protocol = IPPROTO_UDP)
171
{
172
    request_message.clear(Message::RENDER);
173
    request_message.setOpcode(opcode);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
174
    request_message.setQid(default_qid);
175 176 177 178
    request_message.addQuestion(Question(request_name, rrclass, rrtype));
    request_message.toWire(request_renderer);

    delete io_message;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
179
    endpoint = IOEndpoint::create(protocol, IOAddress("192.0.2.1"), 5300);
180 181
    io_message = new IOMessage(request_renderer.getData(),
                               request_renderer.getLength(),
182 183 184
                               protocol == IPPROTO_UDP ?
                               IOSocket::getDummyUDPSocket() :
                               IOSocket::getDummyTCPSocket(), *endpoint);
185 186
}

187 188 189 190 191 192 193 194 195 196
void
headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
            const uint16_t opcodeval, const unsigned int flags,
            const unsigned int qdcount,
            const unsigned int ancount, const unsigned int nscount,
            const unsigned int arcount)
{
    EXPECT_EQ(qid, message.getQid());
    EXPECT_EQ(rcode, message.getRcode());
    EXPECT_EQ(opcodeval, message.getOpcode().getCode());
197 198 199 200 201 202 203
    EXPECT_EQ((flags & QR_FLAG) != 0, message.getHeaderFlag(MessageFlag::QR()));
    EXPECT_EQ((flags & AA_FLAG) != 0, message.getHeaderFlag(MessageFlag::AA()));
    EXPECT_EQ((flags & TC_FLAG) != 0, message.getHeaderFlag(MessageFlag::TC()));
    EXPECT_EQ((flags & RA_FLAG) != 0, message.getHeaderFlag(MessageFlag::RA()));
    EXPECT_EQ((flags & RD_FLAG) != 0, message.getHeaderFlag(MessageFlag::RD()));
    EXPECT_EQ((flags & AD_FLAG) != 0, message.getHeaderFlag(MessageFlag::AD()));
    EXPECT_EQ((flags & CD_FLAG) != 0, message.getHeaderFlag(MessageFlag::CD()));
204 205 206 207 208 209 210 211 212 213 214

    EXPECT_EQ(qdcount, message.getRRCount(Section::QUESTION()));
    EXPECT_EQ(ancount, message.getRRCount(Section::ANSWER()));
    EXPECT_EQ(nscount, message.getRRCount(Section::AUTHORITY()));
    EXPECT_EQ(arcount, message.getRRCount(Section::ADDITIONAL()));
}

// Unsupported requests.  Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
    for (unsigned int i = 1; i < 16; ++i) {
        // set Opcode to 'i', which iterators over all possible codes except
Han Feng's avatar
Han Feng committed
215 216 217
        // the standard query (0) and notify(4)
        if (i == 4)
            continue;
218
        createDataFromFile("simplequery_fromWire");
219 220 221
        data[2] = ((i << 3) & 0xff);

        parse_message.clear(Message::PARSE);
222 223
        EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                              response_renderer));
224 225 226
        headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
                    0, 0, 0, 0);
    }
227
}
228

229 230 231 232 233 234 235 236 237
// Simple API check
TEST_F(AuthSrvTest, verbose) {
    EXPECT_FALSE(server.getVerbose());
    server.setVerbose(true);
    EXPECT_TRUE(server.getVerbose());
    server.setVerbose(false);
    EXPECT_FALSE(server.getVerbose());
}

238 239
// Multiple questions.  Should result in FORMERR.
TEST_F(AuthSrvTest, multiQuestion) {
240
    createDataFromFile("multiquestion_fromWire");
241 242
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
243
    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
244 245 246 247 248 249 250 251 252 253 254 255
                QR_FLAG, 2, 0, 0, 0);

    QuestionIterator qit = parse_message.beginQuestion();
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::A(), (*qit)->getType());
    ++qit;
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::AAAA(), (*qit)->getType());
    ++qit;
    EXPECT_TRUE(qit == parse_message.endQuestion());
256 257
}

258 259 260
// Incoming data doesn't even contain the complete header.  Must be silently
// dropped.
TEST_F(AuthSrvTest, shortMessage) {
261
    createDataFromFile("shortmessage_fromWire");
262 263
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
                                           response_renderer));
264 265 266 267 268 269
}

// Response messages.  Must be silently dropped, whether it's a valid response
// or malformed or could otherwise cause a protocol error.
TEST_F(AuthSrvTest, response) {
    // A valid (although unusual) response
270
    createDataFromFile("simpleresponse_fromWire");
271 272
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
                                           response_renderer));
273 274 275

    // A response with a broken question section.  must be dropped rather than
    // returning FORMERR.
276
    createDataFromFile("shortresponse_fromWire");
277 278
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
                                           response_renderer));
279 280

    // A response to iquery.  must be dropped rather than returning NOTIMP.
281
    createDataFromFile("iqueryresponse_fromWire");
282 283
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
                                           response_renderer));
284 285 286 287
}

// Query with a broken question
TEST_F(AuthSrvTest, shortQuestion) {
288
    createDataFromFile("shortquestion_fromWire");
289 290
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
291 292 293 294 295
    // Since the query's question is broken, the question section of the
    // response should be empty.
    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                QR_FLAG, 0, 0, 0, 0);
}
296

297 298
// Query with a broken answer section
TEST_F(AuthSrvTest, shortAnswer) {
299
    createDataFromFile("shortanswer_fromWire");
300 301
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
302 303 304

    // This is a bogus query, but question section is valid.  So the response
    // should copy the question section.
305 306 307
    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                QR_FLAG, 1, 0, 0, 0);

308 309 310 311 312 313
    QuestionIterator qit = parse_message.beginQuestion();
    EXPECT_EQ(Name("example.com"), (*qit)->getName());
    EXPECT_EQ(RRClass::IN(), (*qit)->getClass());
    EXPECT_EQ(RRType::A(), (*qit)->getType());
    ++qit;
    EXPECT_TRUE(qit == parse_message.endQuestion());
314 315
}

316 317
// Query with unsupported version of EDNS.
TEST_F(AuthSrvTest, ednsBadVers) {
318
    createDataFromFile("queryBadEDNS_fromWire");
319 320
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
321 322 323 324 325 326 327 328 329 330

    // The response must have an EDNS OPT RR in the additional section.
    // Note that the DNSSEC DO bit is cleared even if this bit in the query
    // is set.  This is a limitation of the current implementation.
    headerCheck(parse_message, default_qid, Rcode::BADVERS(), opcode.getCode(),
                QR_FLAG, 1, 0, 0, 1);
    EXPECT_EQ(4096, parse_message.getUDPSize());
    EXPECT_FALSE(parse_message.isDNSSECSupported());
}

JINMEI Tatuya's avatar
JINMEI Tatuya committed
331 332 333 334 335 336 337 338 339 340
TEST_F(AuthSrvTest, AXFROverUDP) {
    // AXFR over UDP is invalid and should result in FORMERR.
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_UDP);
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                QR_FLAG, 1, 0, 0, 0);
}

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 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
TEST_F(AuthSrvTest, AXFRSuccess) {
    EXPECT_FALSE(xfrout.isConnected());
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_TCP);
    // On success, the AXFR query has been passed to a separate process,
    // so we shouldn't have to respond.
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
                                           response_renderer));
    EXPECT_TRUE(xfrout.isConnected());
}

TEST_F(AuthSrvTest, AXFRConnectFail) {
    EXPECT_FALSE(xfrout.isConnected()); // check prerequisite
    xfrout.disableConnect();
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_TCP);
    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
                                      response_renderer));
    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
    EXPECT_FALSE(xfrout.isConnected());
}

TEST_F(AuthSrvTest, AXFRSendFail) {
    // first send a valid query, making the connection with the xfr process
    // open.
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_TCP);
    server.processMessage(*io_message, parse_message, response_renderer);
    EXPECT_TRUE(xfrout.isConnected());

    xfrout.disableSend();
    parse_message.clear(Message::PARSE);
    response_renderer.clear();
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_TCP);
    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
                                      response_renderer));
    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);

    // The connection should have been closed due to the send failure.
    EXPECT_FALSE(xfrout.isConnected());
}

TEST_F(AuthSrvTest, AXFRDisconnectFail) {
    // In our usage disconnect() shouldn't fail.  So we'll see the exception
    // should it be thrown.
    xfrout.disableSend();
    xfrout.disableDisconnect();
    createRequest(opcode, Name("example.com"), RRClass::IN(), RRType::AXFR(),
                  IPPROTO_TCP);
    EXPECT_THROW(server.processMessage(*io_message, parse_message,
                                       response_renderer),
                 XfroutError);
    EXPECT_TRUE(xfrout.isConnected());
    // XXX: we need to re-enable disconnect.  otherwise an exception would be
    // thrown via the destructor of the server.
    xfrout.enableDisconnect();
}

Han Feng's avatar
Han Feng committed
402
TEST_F(AuthSrvTest, notifyInTest) {
403 404
    createRequest(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
                  RRType::SOA());
405
    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
406
                                           response_renderer));
Han Feng's avatar
Han Feng committed
407 408
}

409
void
410 411 412
updateConfig(AuthSrv* server, const char* const dbfile,
             const bool expect_success)
{
413 414 415 416 417 418 419
    const ElementPtr config_answer =
        server->updateConfig(Element::createFromString(dbfile));
    EXPECT_EQ(Element::map, config_answer->getType());
    EXPECT_TRUE(config_answer->contains("result"));

    const ElementPtr result = config_answer->get("result");
    EXPECT_EQ(Element::list, result->getType());
420
    EXPECT_EQ(expect_success ? 0 : 1, result->get(0)->intValue());
421 422 423 424
}

// Install a Sqlite3 data source with testing data.
TEST_F(AuthSrvTest, updateConfig) {
425
    updateConfig(&server, CONFIG_TESTDB, true);
426 427 428 429

    // query for existent data in the installed data source.  The resulting
    // response should have the AA flag on, and have an RR in each answer
    // and authority section.
430
    createDataFromFile("examplequery_fromWire");
431 432
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
433 434 435 436 437
    headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                QR_FLAG | AA_FLAG, 1, 1, 1, 0);
}

TEST_F(AuthSrvTest, datasourceFail) {
438
    updateConfig(&server, CONFIG_TESTDB, true);
439 440 441 442 443

    // This query will hit a corrupted entry of the data source (the zoneload
    // tool and the data source itself naively accept it).  This will result
    // in a SERVFAIL response, and the answer and authority sections should
    // be empty.
444
    createDataFromFile("badExampleQuery_fromWire");
445 446
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
447 448 449
    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(), opcode.getCode(),
                QR_FLAG, 1, 0, 0, 0);
}
450 451 452 453 454 455 456 457 458

TEST_F(AuthSrvTest, updateConfigFail) {
    // First, load a valid data source.
    updateConfig(&server, CONFIG_TESTDB, true);

    // Next, try to update it with a non-existent one.  This should fail.
    updateConfig(&server, BADCONFIG_TESTDB, false);

    // The original data source should still exist.
459
    createDataFromFile("examplequery_fromWire");
460 461
    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
                                          response_renderer));
462 463 464
    headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                QR_FLAG | AA_FLAG, 1, 1, 1, 0);
}
465
}