summaryrefslogtreecommitdiffstats
path: root/src/lib/resolve
diff options
context:
space:
mode:
authorDima Volodin <dvv@isc.org>2011-12-16 12:13:34 +0100
committerDima Volodin <dvv@isc.org>2011-12-16 12:13:34 +0100
commit8c96b066c55f216096107b51f3d634505dd3f8f0 (patch)
tree42267c7e8002751552df79a8e7870d1d89c44aa8 /src/lib/resolve
parent[1386] Changelog for #1386 (diff)
downloadkea-8c96b066c55f216096107b51f3d634505dd3f8f0.tar.xz
kea-8c96b066c55f216096107b51f3d634505dd3f8f0.zip
[1386] EDNS fallback unittest
Diffstat (limited to 'src/lib/resolve')
-rw-r--r--src/lib/resolve/tests/recursive_query_unittest_3.cc570
1 files changed, 570 insertions, 0 deletions
diff --git a/src/lib/resolve/tests/recursive_query_unittest_3.cc b/src/lib/resolve/tests/recursive_query_unittest_3.cc
new file mode 100644
index 0000000000..5d5941e0c9
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest_3.cc
@@ -0,0 +1,570 @@
+// Copyright (C) 2011 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.
+
+#include <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+#include <asio.hpp>
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#include <util/io_utilities.h>
+#include <asiodns/dns_service.h>
+#include <asiodns/io_fetch.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+#include <resolve/resolver_interface.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace isc::asiolink;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using namespace isc::resolve;
+using namespace std;
+
+/// RecursiveQuery Test - 3
+///
+/// The second part of the RecursiveQuery unit tests, this attempts to get the
+/// RecursiveQuery object to follow a set of EDNS-induced errors, causing the
+/// resolver to follow the fallback logic.
+///
+/// - Send EDNS question over UDP - get FORMERR
+/// - Send EDNS question over TCP - get FORMERR
+/// - Send non-EDNS question over UDP - get RESPONSE
+///
+/// By using the "test_server_" element of RecursiveQuery, all queries are
+/// directed to one or other of the "servers" in the RecursiveQueryTest3 class.
+
+namespace isc {
+namespace asiodns {
+
+const std::string TEST_ADDRESS3 = "127.0.0.1";
+ ///< Servers are on this address
+const uint16_t TEST_PORT3 = 5303; ///< ... and this port
+const size_t BUFFER_SIZE = 1024; ///< For all buffers
+
+const std::string DUMMY_ADDR3 = "1.2.3.4"; ///< address to return as A
+
+class MockResolver3 : public isc::resolve::ResolverInterface {
+public:
+ virtual void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+
+ virtual ~MockResolver3() {}
+};
+
+
+
+/// \brief Test fixture for the RecursiveQuery Test
+class RecursiveQueryTest3 : public virtual ::testing::Test
+{
+public:
+
+ /// \brief Status of query
+ ///
+ /// Set before the query and then by each "server" when responding.
+ enum QueryStatus {
+ NONE = 0, ///< Default
+ EDNS_UDP = 1, ///< EDNS query over UDP
+ EDNS_TCP = 2, ///< EDNS query over TCP
+ NON_EDNS_UDP = 3, ///< Non-EDNS query over UDP
+ COMPLETE = 6 ///< Query is complete
+ };
+
+ // Common stuff
+ IOService service_; ///< Service to run everything
+ DNSService dns_service_; ///< Resolver is part of "server"
+ QuestionPtr question_; ///< What to ask
+ QueryStatus last_; ///< What was the last state
+ QueryStatus expected_; ///< Expected next state
+ OutputBufferPtr question_buffer_; ///< Question we expect to receive
+ boost::shared_ptr<MockResolver3> resolver_; ///< Mock resolver
+ isc::nsas::NameserverAddressStore* nsas_; ///< Nameserver address store
+ isc::cache::ResolverCache cache_; ///< Resolver cache
+
+ // Data for TCP Server
+ size_t tcp_cumulative_; ///< Cumulative TCP data received
+ tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives
+ size_t tcp_length_; ///< Expected length value
+ uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O
+ OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O
+ tcp::socket tcp_socket_; ///< Socket used by TCP server
+
+ /// Data for UDP
+ udp::endpoint udp_remote_; ///< Endpoint for UDP receives
+ size_t udp_length_; ///< Expected length value
+ uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O
+ OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O
+ udp::socket udp_socket_; ///< Socket used by UDP server
+
+ /// \brief Constructor
+ RecursiveQueryTest3() :
+ service_(),
+ dns_service_(service_, NULL, NULL, NULL),
+ question_(new Question(Name("ednsfallback"),
+ RRClass::IN(), RRType::A())),
+ last_(NONE),
+ expected_(NONE),
+ question_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ resolver_(new MockResolver3()),
+ nsas_(new isc::nsas::NameserverAddressStore(resolver_)),
+ tcp_cumulative_(0),
+ tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS3),
+ TEST_PORT3),
+ tcp_length_(0),
+ tcp_receive_buffer_(),
+ tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_socket_(service_.get_io_service()),
+ udp_remote_(),
+ udp_length_(0),
+ udp_receive_buffer_(),
+ udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ udp_socket_(service_.get_io_service(), udp::v4())
+ {
+ }
+
+ /// \brief Set Common Message Bits
+ ///
+ /// Sets up the common bits of a response message returned by the handlers.
+ ///
+ /// \param message Message buffer in RENDER mode.
+ /// \param qid QID to set the message to
+ void setCommonMessage(isc::dns::Message& message, uint16_t qid) {
+ message.setQid(qid);
+ message.setHeaderFlag(Message::HEADERFLAG_QR);
+ message.setOpcode(Opcode::QUERY());
+ message.setHeaderFlag(Message::HEADERFLAG_AA);
+ message.addQuestion(*question_);
+ }
+
+ /// \brief Set FORMERR answer
+ ///
+ /// \param message Message to update with FORMERR status
+ void setFORMERR(isc::dns::Message& message) {
+ message.setRcode(Rcode::FORMERR());
+ }
+
+ /// \brief Set Answer
+ ///
+ /// \param message Message to update with FORMERR status
+ void setAnswer(isc::dns::Message& message) {
+ // Give a response
+ RRsetPtr answer(new RRset(Name("ednsfallback."), RRClass::IN(),
+ RRType::A(), RRTTL(300)));
+ answer->addRdata(createRdata(RRType::A(), RRClass::IN(), DUMMY_ADDR3));
+ message.addRRset(Message::SECTION_ANSWER, answer);
+ message.setRcode(Rcode::NOERROR());
+ }
+
+ /// \brief UDP Receive Handler
+ ///
+ /// This is invoked when a message is received over UDP from the
+ /// RecursiveQuery object under test. It formats an answer and sends it
+ /// asynchronously, with the UdpSendHandler method being specified as the
+ /// completion handler.
+ ///
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ // Expected state should be one greater than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ Message query(Message::PARSE);
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ // And check that question we received is what was expected.
+ checkReceivedPacket(udp_receive_buffer_, length, query);
+
+ // The message returned depends on what state we are in. Set up
+ // common stuff first: bits not mentioned are set to 0.
+ Message message(Message::RENDER);
+ setCommonMessage(message, query.getQid());
+
+ // Set up state-dependent bits:
+ switch (expected_) {
+ case EDNS_UDP:
+ EXPECT_TRUE(query.getEDNS());
+ // Return FORMERROR
+ setFORMERR(message);
+ expected_ = EDNS_TCP;
+ break;
+
+ case NON_EDNS_UDP:
+ EXPECT_TRUE(query.getEDNS());
+ // Return the answer to the question.
+ setAnswer(message);
+ expected_ = COMPLETE;
+ break;
+
+ default:
+ FAIL() << "UdpReceiveHandler called with unknown state";
+ }
+
+ // Convert to wire format
+ udp_send_buffer_->clear();
+ MessageRenderer renderer(*udp_send_buffer_);
+ message.toWire(renderer);
+
+ // Return a message back to the IOFetch object (after setting the
+ // expected length of data for the check in the send handler).
+ udp_length_ = udp_send_buffer_->getLength();
+ udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(),
+ udp_send_buffer_->getLength()),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest3::udpSendHandler,
+ this, _1, _2));
+ }
+
+ /// \brief UDP Send Handler
+ ///
+ /// Called when a send operation of the UDP server (i.e. a response
+ /// being sent to the RecursiveQuery) has completed, this re-issues
+ /// a read call.
+ ///
+ /// \param ec Completion error code of the send.
+ /// \param length Actual number of bytes sent.
+ void udpSendHandler(error_code ec = error_code(), size_t length = 0) {
+ // Check send was OK
+ EXPECT_EQ(0, ec.value());
+ EXPECT_EQ(udp_length_, length);
+
+ // Reissue the receive call to await the next message.
+ udp_socket_.async_receive_from(
+ asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest3::udpReceiveHandler,
+ this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Accepting TCP Data
+ ///
+ /// Called when the remote system connects to the "TCP server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) {
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Initiate a read on the socket, indicating that nothing has yet been
+ // received.
+ tcp_cumulative_ = 0;
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)),
+ boost::bind(&RecursiveQueryTest3::tcpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Receiving TCP Data
+ ///
+ /// Reads data from the RecursiveQuery object and loops, reissuing reads,
+ /// until all the message has been read. It then returns an appropriate
+ /// response.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Have we received all the data? We know this by checking if the two-
+ // byte length count in the message is equal to the data received.
+ tcp_cumulative_ += length;
+ bool complete = false;
+ if (tcp_cumulative_ > 2) {
+ uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ complete = ((dns_length + 2) == tcp_cumulative_);
+ }
+
+ if (!complete) {
+ // Not complete yet, issue another read.
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_ + tcp_cumulative_,
+ sizeof(tcp_receive_buffer_) - tcp_cumulative_),
+ boost::bind(&RecursiveQueryTest3::tcpReceiveHandler,
+ this, _1, _2));
+ return;
+ }
+
+ // Have received a TCP message. Expected state should be one greater
+ // than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ Message query(Message::PARSE);
+
+ // Check that question we received is what was expected. Note that we
+ // have to ignore the two-byte header in order to parse the message.
+ checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2, query);
+
+ // Return a message back. This is a referral to example.org, which
+ // should result in another query over UDP. Note the setting of the
+ // QID in the returned message with what was in the received message.
+ Message message(Message::RENDER);
+ setCommonMessage(message, query.getQid());
+
+ // Set up state-dependent bits:
+ switch (expected_) {
+ case EDNS_TCP:
+ EXPECT_TRUE(query.getEDNS());
+ // Return FORMERROR
+ setFORMERR(message);
+ expected_ = NON_EDNS_UDP;
+ break;
+
+ default:
+ FAIL() << "TcpReceiveHandler called with unknown state";
+ }
+
+
+ // Convert to wire format
+ // Use a temporary buffer for the dns wire data (we copy it
+ // to the 'real' buffer below)
+ OutputBuffer msg_buf(BUFFER_SIZE);
+ MessageRenderer renderer(msg_buf);
+ message.toWire(renderer);
+
+ // Also, take this opportunity to clear the accumulated read count in
+ // readiness for the next read. (If any - at present, there is only
+ // one read in the test, although extensions to this test suite could
+ // change that.)
+ tcp_cumulative_ = 0;
+
+ // Unless we go through a callback loop we cannot simply use
+ // async_send() multiple times, so we cannot send the size first
+ // followed by the actual data. We copy them to a new buffer
+ // first
+ tcp_send_buffer_->clear();
+ tcp_send_buffer_->writeUint16(msg_buf.getLength());
+ tcp_send_buffer_->writeData(msg_buf.getData(), msg_buf.getLength());
+ tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
+ tcp_send_buffer_->getLength()),
+ boost::bind(&RecursiveQueryTest3::tcpSendHandler,
+ this, tcp_send_buffer_->getLength(), _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the RecursiveQuery
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// \param expected_length Number of bytes that were expected to have been
+ /// sent.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected_length = 0,
+ error_code ec = error_code(),
+ size_t length = 0)
+ {
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected_length, length); // And that amount sent is as
+ // expected
+ }
+
+ /// \brief Check Received Packet
+ ///
+ /// Checks the packet received from the RecursiveQuery object to ensure
+ /// that the question is what is expected.
+ ///
+ /// \param data Start of data. This is the start of the received buffer in
+ /// the case of UDP data, and an offset into the buffer past the
+ /// count field for TCP data.
+ /// \param length Length of data.
+ /// \return The QID of the message
+ void checkReceivedPacket(uint8_t* data, size_t length, Message& message) {
+
+ // Decode the received buffer.
+ InputBuffer buffer(data, length);
+ message.fromWire(buffer);
+
+ // Check the packet.
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ Question question = **(message.beginQuestion());
+ EXPECT_TRUE(question == *question_);
+ }
+};
+
+/// \brief Resolver Callback Object
+///
+/// Holds the success and failure callback methods for the resolver
+class ResolverCallback3 : public isc::resolve::ResolverInterface::Callback {
+public:
+ /// \brief Constructor
+ ResolverCallback3(IOService& service) :
+ service_(service), run_(false), status_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~ResolverCallback3()
+ {}
+
+ /// \brief Resolver Callback Success
+ ///
+ /// Called if the resolver detects that the call has succeeded.
+ ///
+ /// \param response Answer to the question.
+ virtual void success(const isc::dns::MessagePtr response) {
+ // There should be one RR each in the question and answer sections,
+ // and two RRs in each of the the authority and additional sections.
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER));
+
+ // Check the answer - that the RRset is there...
+ EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER,
+ RRsetPtr(new RRset(Name("ednsfallback."),
+ RRClass::IN(),
+ RRType::A(),
+ RRTTL(300)))));
+ const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER);
+
+ // ... get iterator into the Rdata of this RRset and point to first
+ // element...
+ const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator();
+ rdata_i->first();
+
+ // ... and check it is what we expect.
+ EXPECT_EQ(string(DUMMY_ADDR3), rdata_i->getCurrent().toText());
+
+ // Flag completion
+ run_ = true;
+ status_ = true;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Resolver Failure Completion
+ ///
+ /// Called if the resolver detects that the resolution has failed.
+ virtual void failure() {
+ FAIL() << "Resolver reported completion failure";
+
+ // Flag completion
+ run_ = true;
+ status_ = false;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Return status of "run" flag
+ bool getRun() const {
+ return (run_);
+ }
+
+ /// \brief Return "status" flag
+ bool getStatus() const {
+ return (status_);
+ }
+
+private:
+ IOService& service_; ///< Service handling the run queue
+ bool run_; ///< Set true when completion handler run
+ bool status_; ///< Set true for success, false on error
+};
+
+// Sets up the UDP and TCP "servers", then tries a resolution.
+
+TEST_F(RecursiveQueryTest3, Resolve) {
+ // Set up the UDP server and issue the first read. The endpoint from which
+ // the query is sent is put in udp_endpoint_ when the read completes, which
+ // is referenced in the callback as the place to which the response is sent.
+ udp_socket_.set_option(socket_base::reuse_address(true));
+ udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS3),
+ TEST_PORT3));
+ udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_,
+ sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest3::udpReceiveHandler,
+ this, _1, _2));
+
+ // Set up the TCP server and issue the accept. Acceptance will cause the
+ // read to be issued.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT3));
+ acceptor.async_accept(tcp_socket_,
+ boost::bind(&RecursiveQueryTest3::tcpAcceptHandler,
+ this, _1, 0));
+
+ // Set up the RecursiveQuery object. We will also test that it correctly records
+ // RTT times by setting up a RTT recorder object as well.
+ std::vector<std::pair<std::string, uint16_t> > upstream; // Empty
+ std::vector<std::pair<std::string, uint16_t> > upstream_root; // Empty
+ RecursiveQuery query(dns_service_, *nsas_, cache_,
+ upstream, upstream_root);
+ query.setTestServer(TEST_ADDRESS3, TEST_PORT3);
+
+ boost::shared_ptr<RttRecorder> recorder(new RttRecorder());
+ query.setRttRecorder(recorder);
+
+ // Set up callback to receive notification that the query has completed.
+ isc::resolve::ResolverInterface::CallbackPtr
+ resolver_callback(new ResolverCallback3(service_));
+
+ // Kick off the resolution process.
+ expected_ = EDNS_UDP;
+ query.resolve(question_, resolver_callback);
+ service_.run();
+
+ // Check what ran. (We have to cast the callback to ResolverCallback3 as we
+ // lost the information on the derived class when we used a
+ // ResolverInterface::CallbackPtr to store a pointer to it.)
+ ResolverCallback3* rc = static_cast<ResolverCallback3*>(resolver_callback.get());
+ EXPECT_TRUE(rc->getRun());
+ EXPECT_TRUE(rc->getStatus());
+
+ // Finally, check that all the RTTs were "reasonable" (defined here as
+ // being below 2 seconds). This is an explicit check to test that the
+ // variables in the RTT calculation are at least being initialized; if they
+ // weren't, we would expect some absurdly high answers.
+ vector<uint32_t> rtt = recorder->getRtt();
+ EXPECT_GT(rtt.size(), 0);
+ for (int i = 0; i < rtt.size(); ++i) {
+ EXPECT_LT(rtt[i], 2000);
+ }
+}
+
+} // namespace asiodns
+} // namespace isc