Browse Source

[2977] Implemented unit test checking DNS Update send and receive.

Marcin Siodelski 11 years ago
parent
commit
4ab333e8a8
1 changed files with 116 additions and 2 deletions
  1. 116 2
      src/bin/d2/tests/dns_client_unittests.cc

+ 116 - 2
src/bin/d2/tests/dns_client_unittests.cc

@@ -18,6 +18,9 @@
 #include <asiodns/logger.h>
 #include <dns/rcode.h>
 #include <dns/rrclass.h>
+#include <asio/ip/udp.hpp>
+#include <asio/socket_base.hpp>
+#include <boost/bind.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
@@ -29,11 +32,15 @@ using namespace isc::d2;
 
 using namespace isc;
 using namespace isc::dns;
+using namespace isc::util;
+using namespace asio;
+using namespace asio::ip;
 
 namespace {
 
 const char* TEST_ADDRESS = "127.0.0.1";
 const uint16_t TEST_PORT = 5301;
+const size_t MAX_SIZE = 1024;
 
 // @brief Test Fixture class.
 //
@@ -48,6 +55,7 @@ public:
     IOService service_;
     D2UpdateMessagePtr response_;
     IOFetch::Result result_;
+    uint8_t receive_buffer_[MAX_SIZE];
 
     // @brief Constructor.
     //
@@ -82,6 +90,40 @@ public:
         service_.stop();
     }
 
+    // @brief Handler invoked when test request is received.
+    //
+    // 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.
+    void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+                           size_t receive_length) {
+        // 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);
+
+        // What must be different between a request and response is the QR
+        // flag bit. This bit differentiates both types of messages. We have
+        // to set this bit to 1. 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 comprises:
+        //             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);
+        // A response message is now ready to send. Send it!
+        socket->send_to(asio::buffer(response_buf.getData(),
+                                     response_buf.getLength()),
+                        *remote);
+    }
+
     // 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
@@ -107,9 +149,8 @@ public:
         // scope.
         boost::scoped_ptr<DNSClient> dns_client;
         // Constructor may throw if the response placehoder is NULL.
-        EXPECT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
+        ASSERT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
 
-        IOService io_service;
         // Set the response wait time to 0 so as our test is not hanging. This
         // should cause instant timeout.
         const int timeout = 0;
@@ -128,6 +169,75 @@ public:
         // value of result_ with the TIME_OUT error code.
         EXPECT_EQ(IOFetch::TIME_OUT, result_);
     }
+
+    // This test verifies that DNSClient can send DNS Update and receive a
+    // corresponding response from a server.
+    void runSendReceiveTest() {
+        // 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()));
+
+        // Create an instance of the DNSClient. Constructor may throw an
+        // exception, so we guard it with EXPECT_NO_THROW.
+        boost::scoped_ptr<DNSClient> dns_client;
+        EXPECT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
+
+        // 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.
+        udp::socket udp_socket(service_.get_io_service(), asio::ip::udp::v4());
+        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;
+        udp_socket.async_receive_from(asio::buffer(receive_buffer_,
+                                                   sizeof(receive_buffer_)),
+                                      remote,
+                                      boost::bind(&DNSClientTest::udpReceiveHandler,
+                                                  this, &udp_socket, &remote, _2));
+
+        // The socket is now ready to receive the data. Let's post some request
+        // message then.
+        const int timeout = 5;
+        dns_client->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+                             message, timeout);
+
+        // Kick of the message exchange by actually running the scheduled
+        // "send" and "receive" operations.
+        service_.run();
+
+        udp_socket.close();
+
+        // We should have received a response.
+        EXPECT_EQ(IOFetch::SUCCESS, result_);
+
+        ASSERT_TRUE(response_);
+        EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+        ASSERT_EQ(1, response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+        D2ZonePtr zone = response_->getZone();
+        ASSERT_TRUE(zone);
+        EXPECT_EQ("example.com.", zone->getName().toText());
+        EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+    }
 };
 
 TEST_F(DNSClientTest, constructor) {
@@ -138,4 +248,8 @@ TEST_F(DNSClientTest, timeout) {
     runSendNoReceiveTest();
 }
 
+TEST_F(DNSClientTest, sendReceive) {
+    runSendReceiveTest();
+}
+
 } // End of anonymous namespace