Commit 326b3dc5 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[2977] Implemented tests covering concurrent DNSUpdate send.

parent ca87835d
......@@ -25,6 +25,10 @@
namespace isc {
namespace d2 {
class DNSClient;
typedef boost::shared_ptr<DNSClient> DNSClientPtr;
/// DNSClient class implementation.
class DNSClientImpl;
/// @brief The @c DNSClient class handles communication with the DNS server.
......
......@@ -56,6 +56,9 @@ public:
D2UpdateMessagePtr response_;
DNSClient::Status status_;
uint8_t receive_buffer_[MAX_SIZE];
DNSClientPtr dns_client_;
bool corrupt_response_;
bool expect_response_;
// @brief Constructor.
//
......@@ -67,9 +70,12 @@ public:
// become messy if such errors were logged.
DNSClientTest()
: service_(),
status_(DNSClient::SUCCESS) {
status_(DNSClient::SUCCESS),
corrupt_response_(false),
expect_response_(true) {
asiodns::logger.setSeverity(log::INFO);
response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
dns_client_.reset(new DNSClient(response_, this));
}
// @brief Destructor.
......@@ -79,7 +85,7 @@ public:
asiodns::logger.setSeverity(log::DEBUG);
};
// @brief Exchange completion calback.
// @brief Exchange completion callback.
//
// This callback is called when the exchange with the DNS server is
// complete or an error occured. This includes the occurence of a timeout.
......@@ -88,6 +94,29 @@ public:
virtual void operator()(DNSClient::Status status) {
status_ = status;
service_.stop();
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());
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());
} 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_);
}
}
// @brief Handler invoked when test request is received.
......@@ -102,22 +131,27 @@ public:
// @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)
void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
size_t receive_length) {
size_t receive_length, const bool corrupt_response) {
// 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);
// 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);
}
// A response message is now ready to send. Send it!
socket->send_to(asio::buffer(response_buf.getData(),
response_buf.getLength()),
......@@ -138,6 +172,9 @@ public:
// 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() {
// We expect no response from a server.
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.
......@@ -145,12 +182,6 @@ public:
ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
// Use scoped pointer so as we can declare dns_client in the function
// scope.
boost::scoped_ptr<DNSClient> dns_client;
// Constructor may throw if the response placehoder is NULL.
ASSERT_NO_THROW(dns_client.reset(new DNSClient(response_, this)));
// Set the response wait time to 0 so as our test is not hanging. This
// should cause instant timeout.
const int timeout = 0;
......@@ -158,31 +189,26 @@ public:
// server. When message exchange is done or timeout occurs, the
// completion callback will be triggered. The doUpdate function returns
// immediately.
EXPECT_NO_THROW(dns_client->doUpdate(service_, IOAddress(TEST_ADDRESS),
EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
TEST_PORT, message, timeout));
// This starts the execution of tasks posted to IOService. run() blocks
// until stop() is called in the completion callback function.
service_.run();
// If callback function was called it should have modified the default
// value of status_ with the TIMEOUT error code.
EXPECT_EQ(DNSClient::TIMEOUT, status_);
}
// This test verifies that DNSClient can send DNS Update and receive a
// corresponding response from a server.
void runSendReceiveTest() {
void runSendReceiveTest(const bool corrupt_response,
const bool two_sends = false) {
corrupt_response_ = corrupt_response;
// 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
......@@ -213,43 +239,75 @@ public:
sizeof(receive_buffer_)),
remote,
boost::bind(&DNSClientTest::udpReceiveHandler,
this, &udp_socket, &remote, _2));
this, &udp_socket, &remote, _2,
corrupt_response));
// 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,
dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
message, timeout);
// It is possible to request that two packets are sent concurrently.
if (two_sends) {
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(DNSClient::SUCCESS, status_);
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());
}
};
// Verify that the DNSClient object can be created if provided parameters are
// valid. Constructor should throw exceptions when parameters are invalid.
TEST_F(DNSClientTest, constructor) {
runConstructorTest();
}
// Verify that timeout is reported when no response is received from DNS.
TEST_F(DNSClientTest, timeout) {
runSendNoReceiveTest();
}
// Verify that the DNSClient receives the response from DNS and the received
// buffer can be decoded as DNS Update Response.
TEST_F(DNSClientTest, sendReceive) {
runSendReceiveTest();
// false means that server response is not corrupted.
runSendReceiveTest(false);
}
// Verify that the DNSClient reports an error when the response is received from
// a DNS and this response is corrupted.
TEST_F(DNSClientTest, sendReceiveCurrupted) {
// true means that server's response is corrupted.
runSendReceiveTest(true);
}
// 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) {
runSendReceiveTest(false);
runSendReceiveTest(false);
}
// 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
TEST_F(DNSClientTest, concurrentSendReceive) {
runSendReceiveTest(true, true);
}
} // End of anonymous namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment