dns_client_unittests.cc 24.3 KB
Newer Older
1
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 7 8

#include <config.h>
#include <d2/dns_client.h>
9
#include <dns/opcode.h>
10 11
#include <asiodns/io_fetch.h>
#include <asiodns/logger.h>
12
#include <asiolink/interval_timer.h>
13
#include <dns/messagerenderer.h>
14 15
#include <boost/asio/ip/udp.hpp>
#include <boost/asio/socket_base.hpp>
16
#include <boost/bind.hpp>
17 18
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
19
#include <nc_test_utils.h>
20 21 22 23 24 25 26 27 28

using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::asiodns;
using namespace isc::d2;

using namespace isc;
using namespace isc::dns;
29
using namespace isc::util;
30 31
using namespace boost::asio;
using namespace boost::asio::ip;
32 33 34 35 36

namespace {

const char* TEST_ADDRESS = "127.0.0.1";
const uint16_t TEST_PORT = 5301;
37
const size_t MAX_SIZE = 1024;
38
const long TEST_TIMEOUT = 5 * 1000;
39
/// @brief Test Fixture class
40 41 42 43
//
// This test fixture class implements DNSClient::Callback so as it can be
// installed as a completion callback for tests it implements. This callback
// is called when a DDNS transaction (send and receive) completes. This allows
44
// for the callback function to directly access class members. In particular,
45 46
// the callback function can access IOService on which run() was called and
// call stop() on it.
47 48 49 50 51 52
//
// Many of the tests defined here schedule execution of certain tasks and block
// until tasks are completed or a timeout is hit. However, if timeout is not
// properly handled a task may be hanging for a long time. In order to prevent
// it, the asiolink::IntervalTimer is used to break a running test if test
// timeout is hit. This will result in test failure.
53 54
class DNSClientTest : public virtual ::testing::Test, DNSClient::Callback {
public:
55
    IOService service_;
56
    D2UpdateMessagePtr response_;
57
    DNSClient::Status status_;
58
    uint8_t receive_buffer_[MAX_SIZE];
59 60 61
    DNSClientPtr dns_client_;
    bool corrupt_response_;
    bool expect_response_;
62
    asiolink::IntervalTimer test_timer_;
63 64
    int received_;
    int expected_;
65

66
    /// @brief Constructor
67 68 69 70 71 72 73 74
    //
    // This constructor overrides the default logging level of asiodns logger to
    // prevent it from emitting debug messages from IOFetch class. Such an error
    // message can be emitted if timeout occurs when DNSClient class is
    // waiting for a response. Some of the tests are checking DNSClient behavior
    // in case when response from the server is not received. Tests output would
    // become messy if such errors were logged.
    DNSClientTest()
75
        : service_(),
76 77
          status_(DNSClient::SUCCESS),
          corrupt_response_(false),
78
          expect_response_(true),
79 80
          test_timer_(service_),
          received_(0), expected_(0) {
81
        asiodns::logger.setSeverity(isc::log::INFO);
82
        response_.reset();
83
        dns_client_.reset(new DNSClient(response_, this));
84 85 86 87

        // Set the test timeout to break any running tasks if they hang.
        test_timer_.setup(boost::bind(&DNSClientTest::testTimeoutHandler, this),
                          TEST_TIMEOUT);
88 89
    }

90
    /// @brief Destructor
91 92 93
    //
    // Sets the asiodns logging level back to DEBUG.
    virtual ~DNSClientTest() {
94
        asiodns::logger.setSeverity(isc::log::DEBUG);
95 96
    };

97
    /// @brief Exchange completion callback
98 99
    //
    // This callback is called when the exchange with the DNS server is
100
    // complete or an error occurred. This includes the occurrence of a timeout.
101
    //
102 103 104
    // @param status A status code returned by DNSClient.
    virtual void operator()(DNSClient::Status status) {
        status_ = status;
105 106 107 108
        if (!expected_ || (expected_ == ++received_))
        {
            service_.stop();
        }
109 110 111 112 113 114 115 116

        if (expect_response_) {
            if (!corrupt_response_) {
                // We should have received a response.
                EXPECT_EQ(DNSClient::SUCCESS, status_);

                ASSERT_TRUE(response_);
                EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
117 118
                ASSERT_EQ(1,
                          response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
119 120
                D2ZonePtr zone = response_->getZone();
                ASSERT_TRUE(zone);
121
                EXPECT_EQ("example.com.", zone->getName().toText());
122 123 124 125 126 127 128 129 130 131 132
                EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());

            } else {
                EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);

            }
        // If we don't expect a response, the status should indicate a timeout.
        } else {
            EXPECT_EQ(DNSClient::TIMEOUT, status_);

        }
133 134
    }

135
    /// @brief Handler invoked when test timeout is hit
136 137 138
    //
    // This callback stops all running (hanging) tasks on IO service.
    void testTimeoutHandler() {
139
        service_.stop();
140 141 142
        FAIL() << "Test timeout hit.";
    }

143
    /// @brief Handler invoked when test request is received
144 145 146 147 148 149 150 151 152 153 154
    //
    // This callback handler is installed when performing async read on a
    // socket to emulate reception of the DNS Update request by a server.
    // As a result, this handler will send an appropriate DNS Update response
    // message back to the address from which the request has come.
    //
    // @param socket A pointer to a socket used to receive a query and send a
    // response.
    // @param remote A pointer to an object which specifies the host (address
    // and port) from which a request has come.
    // @param receive_length A length (in bytes) of the received data.
155 156
    // @param corrupt_response A bool value which indicates that the server's
    // response should be invalid (true) or valid (false)
157
    void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
158
                           size_t receive_length, const bool corrupt_response) {
159 160 161 162
        // The easiest way to create a response message is to copy the entire
        // request.
        OutputBuffer response_buf(receive_length);
        response_buf.writeData(receive_buffer_, receive_length);
163 164 165 166 167 168 169 170 171 172 173 174 175
        // If a response is to be valid, we have to modify it slightly. If not,
        // we leave it as is.
        if (!corrupt_response) {
            // For a valid response the QR bit must be set. This bit
            // differentiates both types of messages. Note that the 3rd byte of
            // the message header comprises this bit in the front followed by
            // the message code and reserved zeros. Therefore, this byte
            // has the following value:
            //             10101000,
            // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
            // Write it at message offset 2.
            response_buf.writeUint8At(0xA8, 2);
        }
176
        // A response message is now ready to send. Send it!
177
        socket->send_to(boost::asio::buffer(response_buf.getData(),
178 179 180 181
                                     response_buf.getLength()),
                        *remote);
    }

Andrei Pavel's avatar
Andrei Pavel committed
182
    /// @brief Request handler for testing clients using TSIG
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
    //
    // This callback handler is installed when performing async read on a
    // socket to emulate reception of the DNS Update request with TSIG by a
    // server.  As a result, this handler will send an appropriate DNS Update
    // response message back to the address from which the request has come.
    //
    // @param socket A pointer to a socket used to receive a query and send a
    // response.
    // @param remote A pointer to an object which specifies the host (address
    // and port) from which a request has come.
    // @param receive_length A length (in bytes) of the received data.
    // @param corrupt_response A bool value which indicates that the server's
    // response should be invalid (true) or valid (false)
    // @param client_key TSIG key the server should use to verify the inbound
    // request.  If the pointer is NULL, the server will not attempt to
    // verify the request.
    // @param server_key TSIG key the server should use to sign the outbound
    // request. If the pointer is NULL, the server will not sign the outbound
    // response.  If the pointer is not NULL and not the same value as the
    // client_key, the server will use a new context to sign the response then
    // the one used to verify it.  This allows us to simulate the server
    // signing with the wrong key.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
    void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
                            size_t receive_length,
                            TSIGKeyPtr client_key,
                            TSIGKeyPtr server_key) {

        TSIGContextPtr context;
        if (client_key) {
            context.reset(new TSIGContext(*client_key));
        }

        isc::util::InputBuffer received_data_buffer(receive_buffer_,
                                                    receive_length);

        dns::Message request(Message::PARSE);
        request.fromWire(received_data_buffer);

Andrei Pavel's avatar
Andrei Pavel committed
221
        // If context is not NULL, then we need to verify the message.
222 223 224 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
        if (context) {
            TSIGError error = context->verify(request.getTSIGRecord(),
                                              receive_buffer_, receive_length);
            if (error != TSIGError::NOERROR()) {
                isc_throw(TSIGVerifyError, "TSIG verification failed: "
                          << error.toText());
            }
        }

        dns::Message response(Message::RENDER);
        response.setOpcode(Opcode(Opcode::UPDATE_CODE));
        response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
        response.setQid(request.getQid());
        response.setRcode(Rcode::NOERROR());
        dns::Question question(Name("example.com."),
                                    RRClass::IN(), RRType::SOA());
        response.addQuestion(question);

        MessageRenderer renderer;

        if (!server_key) {
            // don't sign the response.
            context.reset();
        } else if (server_key != client_key) {
            // use a different key to sign the response.
            context.reset(new TSIGContext(*server_key));
        }  // otherwise use the context based on client_key.

        response.toWire(renderer, context.get());
        // A response message is now ready to send. Send it!
252
        socket->send_to(boost::asio::buffer(renderer.getData(), renderer.getLength()),
253 254 255
                        *remote);
    }

256 257 258 259 260
    // This test verifies that when invalid response placeholder object is
    // passed to a constructor, constructor throws the appropriate exception.
    // It also verifies that the constructor will not throw if the supplied
    // callback object is NULL.
    void runConstructorTest() {
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
        EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));

        // The TCP Transport is not supported right now. So, we return exception
        // if caller specified TCP as a preferred protocol. This test will be
        // removed once TCP is supported.
        EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
                     isc::NotImplemented);
    }

    // This test verifies that it accepted timeout values belong to the range of
    // <0, DNSClient::getMaxTimeout()>.
    void runInvalidTimeoutTest() {

        expect_response_ = false;

        // Create outgoing message. Simply set the required message fields:
        // error code and Zone section. This is enough to create on-wire format
        // of this message and send it.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

283
        // Start with a valid timeout equal to maximal allowed. This way we will
284 285 286 287 288 289 290 291 292 293 294 295
        // ensure that doUpdate doesn't throw an exception for valid timeouts.
        unsigned int timeout = DNSClient::getMaxTimeout();
        EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
                                           TEST_PORT, message, timeout));

        // Cross the limit and expect that exception is thrown this time.
        timeout = DNSClient::getMaxTimeout() + 1;
        EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
                                           TEST_PORT, message, timeout),
                     isc::BadValue);
    }

296 297 298 299
    // This test verifies the DNSClient behavior when a server does not respond
    // do the DNS Update message. In such case, the callback function is
    // expected to be called and the TIME_OUT error code should be returned.
    void runSendNoReceiveTest() {
300 301 302
        // We expect no response from a server.
        expect_response_ = false;

303 304 305 306 307 308 309
        // Create outgoing message. Simply set the required message fields:
        // error code and Zone section. This is enough to create on-wire format
        // of this message and send it.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

310 311 312 313
        /// @todo The timeout value could be set to 0 to trigger timeout
        /// instantly. However, it may lead to situations that the message sent
        /// in one test will not be dropped by the kernel by the time, the next
        /// test starts. This will lead to intermittent unit test errors as
314
        /// described in the ticket http://oldkea.isc.org/ticket/3265.
315 316 317 318 319
        /// Increasing the timeout to a non-zero value mitigates this problem.
        /// The proper way to solve this problem is to receive the packet
        /// on our own and drop it. Such a fix will need to be applied not only
        /// to this test but also for other tests that rely on arbitrary timeout
        /// values.
320
        const int timeout = 500;
321 322 323 324
        // The doUpdate() function starts asynchronous message exchange with DNS
        // server. When message exchange is done or timeout occurs, the
        // completion callback will be triggered. The doUpdate function returns
        // immediately.
325
        EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
326 327 328 329
                                             TEST_PORT, message, timeout));

        // This starts the execution of tasks posted to IOService. run() blocks
        // until stop() is called in the completion callback function.
330
        service_.run();
331 332

    }
333 334 335

    // This test verifies that DNSClient can send DNS Update and receive a
    // corresponding response from a server.
336
    void runSendReceiveTest(const bool corrupt_response,
337
                            const bool two_sends) {
338 339
        corrupt_response_ = corrupt_response;

340 341 342
        // Create a request DNS Update message.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
343
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
344 345 346 347 348 349 350 351 352 353

        // In order to perform the full test, when the client sends the request
        // and receives a response from the server, we have to emulate the
        // server's response in the test. A request will be sent via loopback
        // interface to 127.0.0.1 and known test port. Response must be sent
        // to 127.0.0.1 and a source port which has been used to send the
        // request. A new socket is created, specifically to handle sending
        // responses. The reuse address option is set so as both sockets can
        // use the same address. This new socket is bound to the test address
        // and port, where requests will be sent.
354
        udp::socket udp_socket(service_.get_io_service(), boost::asio::ip::udp::v4());
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
        udp_socket.set_option(socket_base::reuse_address(true));
        udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
                                      TEST_PORT));
        // Once socket is created, we can post an IO request to receive some
        // a packet from this socket. This is asynchronous operation and
        // nothing is received until another IO request to send a query is
        // posted and the run() is invoked on this IO. A callback function is
        // attached to this asynchronous read. This callback function requires
        // that a socket object used to receive the request is passed to it,
        // because the same socket will be used by the callback function to send
        // a response. Also, the remote object is passed to the callback,
        // because it holds a source address and port where request originated.
        // Callback function will send a response to this address and port.
        // The last parameter holds a length of the received request. It is
        // required to construct a response.
        udp::endpoint remote;
371
        udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_,
372 373 374
                                                   sizeof(receive_buffer_)),
                                      remote,
                                      boost::bind(&DNSClientTest::udpReceiveHandler,
375 376
                                                  this, &udp_socket, &remote, _2,
                                                  corrupt_response));
377 378

        // The socket is now ready to receive the data. Let's post some request
379 380 381 382
        // message then. Set timeout to some reasonable value to make sure that
        // there is sufficient amount of time for the test to generate a
        // response.
        const int timeout = 500;
383
        expected_++;
384
        dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
385 386
                             message, timeout);

387 388
        // It is possible to request that two packets are sent concurrently.
        if (two_sends) {
389
            expected_++;
390 391 392 393 394
            dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                                  message, timeout);

        }

395 396
        // Kick of the message exchange by actually running the scheduled
        // "send" and "receive" operations.
397
        service_.run();
398 399 400

        udp_socket.close();

401 402 403 404
        // Since the callback, operator(), calls stop() on the io_service,
        // we must reset it in order for subsequent calls to run() or
        // run_one() to work.
        service_.get_io_service().reset();
405
    }
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425

    // Performs a single request-response exchange with or without TSIG
    //
    // @param client_key TSIG passed to dns_client and also used by the
    // ""server" to verify the request.
    // request.
    // @param server_key TSIG key the "server" should use to sign the response.
    // If this is NULL, then client_key is used.
    // @param should_pass indicates if the test should pass.
    void runTSIGTest(TSIGKeyPtr client_key, TSIGKeyPtr server_key,
                     bool should_pass = true) {
        // Tell operator() method if we expect an invalid response.
        corrupt_response_ = !should_pass;

        // Create a request DNS Update message.
        D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
        ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));

        // Setup our "loopback" server.
426
        udp::socket udp_socket(service_.get_io_service(), boost::asio::ip::udp::v4());
427 428 429 430
        udp_socket.set_option(socket_base::reuse_address(true));
        udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
                                      TEST_PORT));
        udp::endpoint remote;
431
        udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_,
432 433 434 435 436 437 438 439 440 441 442 443 444
                                                   sizeof(receive_buffer_)),
                                      remote,
                                      boost::bind(&DNSClientTest::
                                                  TSIGReceiveHandler, this,
                                                  &udp_socket, &remote, _2,
                                                  client_key, server_key));

        // The socket is now ready to receive the data. Let's post some request
        // message then. Set timeout to some reasonable value to make sure that
        // there is sufficient amount of time for the test to generate a
        // response.
        const int timeout = 500;
        expected_++;
445 446
        dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                              message, timeout, client_key);
447 448 449 450 451 452 453 454 455 456 457 458

        // Kick of the message exchange by actually running the scheduled
        // "send" and "receive" operations.
        service_.run();

        udp_socket.close();

        // Since the callback, operator(), calls stop() on the io_service,
        // we must reset it in order for subsequent calls to run() or
        // run_one() to work.
        service_.get_io_service().reset();
    }
459 460
};

461 462
// Verify that the DNSClient object can be created if provided parameters are
// valid. Constructor should throw exceptions when parameters are invalid.
463 464 465 466
TEST_F(DNSClientTest, constructor) {
    runConstructorTest();
}

467 468 469 470 471 472
// This test verifies that the maximal allowed timeout value is maximal int
// value.
TEST_F(DNSClientTest, getMaxTimeout) {
    EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
}

473
// Verify that timeout is reported when no response is received from DNS.
474 475 476 477
TEST_F(DNSClientTest, timeout) {
    runSendNoReceiveTest();
}

478 479 480 481 482 483
// Verify that exception is thrown when invalid (too high) timeout value is
// specified for asynchronous DNS Update.
TEST_F(DNSClientTest, invalidTimeout) {
    runInvalidTimeoutTest();
}

484
// Verifies that TSIG can be used to sign requests and verify responses.
485
TEST_F(DNSClientTest, runTSIGTest) {
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
    std::string secret ("key number one");
    TSIGKeyPtr key_one;
    ASSERT_NO_THROW(key_one.reset(new
                                    TSIGKey(Name("one.com"),
                                            TSIGKey::HMACMD5_NAME(),
                                            secret.c_str(), secret.size())));
    secret = "key number two";
    TSIGKeyPtr key_two;
    ASSERT_NO_THROW(key_two.reset(new
                                    TSIGKey(Name("two.com"),
                                            TSIGKey::HMACMD5_NAME(),
                                            secret.c_str(), secret.size())));
    TSIGKeyPtr nokey;

    // Should be able to send and receive with no keys.
    // Neither client nor server will attempt to sign or verify.
    runTSIGTest(nokey, nokey);

Andrei Pavel's avatar
Andrei Pavel committed
504
    // Client signs the request, server verifies but doesn't sign.
505 506 507 508 509 510 511 512 513 514 515
    runTSIGTest(key_one, nokey, false);

    // Client and server use the same key to sign and verify.
    runTSIGTest(key_one, key_one);

    // Server uses different key to sign the response.
    runTSIGTest(key_one, key_two, false);

    // Client neither signs nor verifies, server responds with a signed answer
    // Since we are "liberal" in what we accept this should be ok.
    runTSIGTest(nokey, key_two);
516 517
}

518 519
// Verify that the DNSClient receives the response from DNS and the received
// buffer can be decoded as DNS Update Response.
520
TEST_F(DNSClientTest, sendReceive) {
521
    // false means that server response is not corrupted.
522
    runSendReceiveTest(false, false);
523 524 525 526
}

// Verify that the DNSClient reports an error when the response is received from
// a DNS and this response is corrupted.
Josh Soref's avatar
Josh Soref committed
527
TEST_F(DNSClientTest, sendReceiveCorrupted) {
528
    // true means that server's response is corrupted.
529
    runSendReceiveTest(true, false);
530 531 532 533 534 535 536 537 538
}

// Verify that it is possible to use the same DNSClient instance to
// perform the following sequence of message exchanges:
// 1. send
// 2. receive
// 3. send
// 4. receive
TEST_F(DNSClientTest, sendReceiveTwice) {
539 540
    runSendReceiveTest(false, false);
    runSendReceiveTest(false, false);
541
    EXPECT_EQ(2, received_);
542 543 544 545 546 547 548 549
}

// Verify that it is possible to use the DNSClient instance to perform the
// following  sequence of message exchanges:
// 1. send
// 2. send
// 3. receive
// 4. receive
550 551 552 553 554 555 556
// @todo  THIS Test does not function. The method runSendReceive only
// schedules one "server" receive.  In other words only one request is
// listened for and then received. Once it is received, the operator()
// method calls stop() on the io_service, which causes the second receive
// to be cancelled.  It is also unclear, what the asio layer does with a
// second receive on the same socket.
TEST_F(DNSClientTest, DISABLED_concurrentSendReceive) {
557
    runSendReceiveTest(false, true);
558 559
}

560
} // End of anonymous namespace