Commit 97d0d852 authored by Evan Hunt's avatar Evan Hunt
Browse files

Addressed more review comments:

- moved createRequestMessage() into unittest_utils
- added more bogus-address tests in asiolink unit test
- added tests with IPv4-mapped IPv6 addresses, but disabled because
  v4-mapped addresses don't work on my test systems 
- added documentation for asiolink test helper functions
- moved MockSession, MockServer, etc from auth/recurse unit tests
  into a single external mockups.h to reduce code duplication


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac327@3264 e5f2f494-b856-4b98-b285-d166d9295462
parent f0f818b2
This diff is collapsed.
......@@ -17,6 +17,7 @@ TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += ../recursor.h ../recursor.cc
run_unittests_SOURCES += ../../auth/tests/mockups.h
run_unittests_SOURCES += recursor_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
......
......@@ -35,6 +35,8 @@
#include <dns/tests/unittest_util.h>
#include <auth/tests/mockups.h>
using isc::UnitTestUtil;
using namespace std;
using namespace isc::cc;
......@@ -59,56 +61,6 @@ private:
};
class RecursorTest : public ::testing::Test {
private:
class MockSession : public AbstractSession {
public:
MockSession() :
// by default we return a simple "success" message.
msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
send_ok_(true), receive_ok_(true)
{}
virtual void establish(const char* socket_file);
virtual void disconnect();
virtual int group_sendmsg(ConstElementPtr msg, string group,
string instance, string to);
virtual bool group_recvmsg(ConstElementPtr& envelope,
ConstElementPtr& msg,
bool nonblock, int seq);
virtual void subscribe(string group, string instance);
virtual void unsubscribe(string group, string instance);
virtual void startRead(boost::function<void()> read_callback);
virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
virtual bool hasQueuedMsgs() const;
virtual void setTimeout(size_t timeout UNUSED_PARAM) {};
virtual size_t getTimeout() const { return 0; };
void setMessage(ConstElementPtr msg) { msg_ = msg; }
void disableSend() { send_ok_ = false; }
void disableReceive() { receive_ok_ = false; }
ConstElementPtr sent_msg;
string msg_destination;
private:
ConstElementPtr msg_;
bool send_ok_;
bool receive_ok_;
};
// A nonoperative task object to be used in calls to processMessage()
class MockTask : public DNSServer {
public:
MockTask() : done_(false) {}
void operator()(asio::error_code ec UNUSED_PARAM,
size_t length UNUSED_PARAM)
{}
// virtual void doLookup() { return; }
virtual void resume(const bool done) { done_ = done; }
virtual bool hasAnswer() { return (done_); }
virtual int value() { return (0); }
private:
bool done_;
};
protected:
RecursorTest() : ios(*TEST_PORT, true, false, NULL, NULL, NULL),
server(*DEFAULT_REMOTE_ADDRESS),
......@@ -126,7 +78,7 @@ protected:
delete endpoint;
}
MockSession notify_session;
MockTask task;
MockServer dnsserv;
IOService ios;
Recursor server;
Message request_message;
......@@ -145,86 +97,9 @@ protected:
vector<uint8_t> data;
void createDataFromFile(const char* const datafile, int protocol);
void createRequestMessage(const Opcode& opcode, const Name& request_name,
const RRClass& rrclass, const RRType& rrtype);
void createRequestPacket(const Opcode& opcode, const Name& request_name,
const RRClass& rrclass, const RRType& rrtype,
int protocol);
void createRequestPacket(int protocol);
void createRequestPacket(Message& message, int protocol);
};
void
RecursorTest::MockSession::establish(const char* socket_file UNUSED_PARAM) {}
void
RecursorTest::MockSession::disconnect() {}
void
RecursorTest::MockSession::subscribe(string group UNUSED_PARAM,
string instance UNUSED_PARAM)
{}
void
RecursorTest::MockSession::unsubscribe(string group UNUSED_PARAM,
string instance UNUSED_PARAM)
{}
void
RecursorTest::MockSession::startRead(
boost::function<void()> read_callback UNUSED_PARAM)
{}
int
RecursorTest::MockSession::reply(ConstElementPtr envelope UNUSED_PARAM,
ConstElementPtr newmsg UNUSED_PARAM)
{
return (-1);
}
bool
RecursorTest::MockSession::hasQueuedMsgs() const {
return (false);
}
int
RecursorTest::MockSession::group_sendmsg(ConstElementPtr msg, string group,
string instance UNUSED_PARAM,
string to UNUSED_PARAM)
{
if (!send_ok_) {
isc_throw(FatalError, "mock session send is disabled for test");
}
sent_msg = msg;
msg_destination = group;
return (0);
}
bool
RecursorTest::MockSession::group_recvmsg(ConstElementPtr& envelope UNUSED_PARAM,
ConstElementPtr& msg,
bool nonblock UNUSED_PARAM,
int seq UNUSED_PARAM)
{
if (!receive_ok_) {
isc_throw(FatalError, "mock session receive is disabled for test");
}
msg = msg_;
return (true);
}
// 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
RecursorTest::createDataFromFile(const char* const datafile,
const int protocol = IPPROTO_UDP)
......@@ -241,30 +116,10 @@ RecursorTest::createDataFromFile(const char* const datafile,
}
void
RecursorTest::createRequestMessage(const Opcode& opcode,
const Name& request_name,
const RRClass& rrclass,
const RRType& rrtype)
{
request_message.clear(Message::RENDER);
request_message.setOpcode(opcode);
request_message.setQid(default_qid);
request_message.addQuestion(Question(request_name, rrclass, rrtype));
}
void
RecursorTest::createRequestPacket(const Opcode& opcode,
const Name& request_name,
const RRClass& rrclass, const RRType& rrtype,
const int protocol = IPPROTO_UDP)
RecursorTest::createRequestPacket(Message& message,
const int protocol = IPPROTO_UDP)
{
createRequestMessage(opcode, request_name, rrclass, rrtype);
createRequestPacket(protocol);
}
void
RecursorTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
request_message.toWire(request_renderer);
message.toWire(request_renderer);
delete io_message;
......@@ -276,6 +131,17 @@ RecursorTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
*io_sock, *endpoint);
}
// 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
headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
const uint16_t opcodeval, const unsigned int flags,
......@@ -314,8 +180,8 @@ TEST_F(RecursorTest, unsupportedRequest) {
parse_message->clear(Message::PARSE);
server.processMessage(*io_message, parse_message,
response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
0, 0, 0, 0);
}
......@@ -333,8 +199,8 @@ TEST_F(RecursorTest, verbose) {
// Multiple questions. Should result in FORMERR.
TEST_F(RecursorTest, multiQuestion) {
createDataFromFile("multiquestion_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
QR_FLAG, 2, 0, 0, 0);
......@@ -354,8 +220,8 @@ TEST_F(RecursorTest, multiQuestion) {
// dropped.
TEST_F(RecursorTest, shortMessage) {
createDataFromFile("shortmessage_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_FALSE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
// Response messages. Must be silently dropped, whether it's a valid response
......@@ -363,26 +229,26 @@ TEST_F(RecursorTest, shortMessage) {
TEST_F(RecursorTest, response) {
// A valid (although unusual) response
createDataFromFile("simpleresponse_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_FALSE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
// A response with a broken question section. must be dropped rather than
// returning FORMERR.
createDataFromFile("shortresponse_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_FALSE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
// A response to iquery. must be dropped rather than returning NOTIMP.
createDataFromFile("iqueryresponse_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_FALSE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_FALSE(dnsserv.hasAnswer());
}
// Query with a broken question
TEST_F(RecursorTest, shortQuestion) {
createDataFromFile("shortquestion_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
// 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(),
......@@ -392,8 +258,8 @@ TEST_F(RecursorTest, shortQuestion) {
// Query with a broken answer section
TEST_F(RecursorTest, shortAnswer) {
createDataFromFile("shortanswer_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
// This is a bogus query, but question section is valid. So the response
// should copy the question section.
......@@ -411,8 +277,8 @@ TEST_F(RecursorTest, shortAnswer) {
// Query with unsupported version of EDNS.
TEST_F(RecursorTest, ednsBadVers) {
createDataFromFile("queryBadEDNS_fromWire");
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
// 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
......@@ -425,20 +291,23 @@ TEST_F(RecursorTest, ednsBadVers) {
TEST_F(RecursorTest, AXFROverUDP) {
// AXFR over UDP is invalid and should result in FORMERR.
createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
RRType::AXFR(), IPPROTO_UDP);
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
Name("example.com"), RRClass::IN(), RRType::AXFR());
createRequestPacket(request_message, IPPROTO_UDP);
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
QR_FLAG, 1, 0, 0, 0);
}
TEST_F(RecursorTest, AXFRFail) {
createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
RRType::AXFR(), IPPROTO_TCP);
UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
Name("example.com"), RRClass::IN(),
RRType::AXFR());
createRequestPacket(request_message, IPPROTO_TCP);
// AXFR is not implemented and should always send NOTIMP.
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
QR_FLAG, 1, 0, 0, 0);
}
......@@ -449,10 +318,10 @@ TEST_F(RecursorTest, notifyFail) {
request_message.setOpcode(Opcode::NOTIFY());
request_message.setHeaderFlag(MessageFlag::AA());
request_message.setQid(default_qid);
request_message.toWire(request_renderer);
createRequestPacket(IPPROTO_UDP);
server.processMessage(*io_message, parse_message, response_obuffer, &task);
EXPECT_TRUE(task.hasAnswer());
request_message.setHeaderFlag(MessageFlag::AA());
createRequestPacket(request_message, IPPROTO_UDP);
server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
}
......
......@@ -71,8 +71,14 @@ TEST(IOAddressTest, fromText) {
// bogus IPv4 address-like input
EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
// bogus IPv4 address-like input: out-of-range octet
EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
// bogus IPv6 address-like input
EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
// bogus IPv6 address-like input
EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
}
TEST(IOEndpointTest, createUDPv4) {
......@@ -178,9 +184,27 @@ TEST(IOServiceTest, duplicateBind) {
delete io_service;
}
// Disabled because IPv4-mapped addresses don't seem to be working with
// the IOService constructor
TEST(IOServiceTest, DISABLED_IPv4MappedDuplicateBind) {
// Duplicate bind on IPv4-mapped IPv6 address
IOService* io_service = new IOService(*TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL);
EXPECT_THROW(IOService(*TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL), IOError);
delete io_service;
// XXX:
// Currently, this throws an "invalid argument" exception. I have
// not been able to get IPv4-mapped addresses to work.
io_service = new IOService(*TEST_SERVER_PORT, *"::ffff:127.0.0.1", NULL, NULL, NULL);
EXPECT_THROW(IOService(*TEST_SERVER_PORT, *"127.0.0.1", NULL, NULL, NULL), IOError);
delete io_service;
}
// This function returns an addrinfo structure for use by tests, using
// different addresses and ports depending on whether we're testing
// IPv4 or v6, TCP or UDP, and client or server operation.
struct addrinfo*
resolveAddress(const int family, const int sock_type, const int protocol,
const bool client) {
resolveAddress(const int family, const int protocol, const bool client) {
const char* const addr = (family == AF_INET6) ?
TEST_IPV6_ADDR : TEST_IPV4_ADDR;
const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
......@@ -188,7 +212,7 @@ resolveAddress(const int family, const int sock_type, const int protocol,
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = family;
hints.ai_socktype = sock_type;
hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_protocol = protocol;
hints.ai_flags = AI_NUMERICSERV;
......@@ -230,8 +254,10 @@ protected:
delete callback_;
}
}
// Send a test UDP packet to a mock server
void sendUDP(const int family) {
res_ = resolveAddress(family, SOCK_DGRAM, IPPROTO_UDP, false);
res_ = resolveAddress(family, IPPROTO_UDP, false);
sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
if (sock_ < 0) {
......@@ -244,8 +270,10 @@ protected:
}
io_service_->run();
}
// Send a test TCP packet to a mock server
void sendTCP(const int family) {
res_ = resolveAddress(family, SOCK_STREAM, IPPROTO_TCP, false);
res_ = resolveAddress(family, IPPROTO_TCP, false);
sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
if (sock_ < 0) {
......@@ -260,8 +288,12 @@ protected:
}
io_service_->run();
}
// Receive a UDP packet from a mock server; used for testing
// recursive lookup. The caller must place a RecursiveQuery
// on the IO Service queue before running this routine.
void recvUDP(const int family, void* buffer, size_t& size) {
res_ = resolveAddress(family, SOCK_DGRAM, IPPROTO_UDP, true);
res_ = resolveAddress(family, IPPROTO_UDP, true);
sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
if (sock_ < 0) {
......@@ -289,12 +321,18 @@ protected:
// Pass the message size back via the size parameter
size = ret;
}
// Set up an IO Service queue using the specified address
void setIOService(const char& address) {
delete io_service_;
io_service_ = NULL;
callback_ = new ASIOCallBack(this);
io_service_ = new IOService(*TEST_SERVER_PORT, address, callback_, NULL, NULL);
}
// Set up an IO Service queue using the "any" address, on IPv4 if
// 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
void setIOService(const bool use_ipv4, const bool use_ipv6) {
delete io_service_;
io_service_ = NULL;
......@@ -302,6 +340,13 @@ protected:
io_service_ = new IOService(*TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
NULL, NULL);
}
// Run a simple server test, on either IPv4 or IPv6, and over either
// UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
// start the IO Service queue. The UDPServer or TCPServer that was
// created by setIOSerice() will receive the test packet and issue a
// callback, which enables us to check that the data it received
// matches what we sent.
void doTest(const int family, const int protocol) {
if (protocol == IPPROTO_UDP) {
sendUDP(family);
......@@ -328,6 +373,9 @@ protected:
}
protected:
// This is a nonfunctional mockup of a DNSServer object. Its purpose
// is to resume after a recursive query or other asynchronous call
// has completed.
class MockServer : public DNSServer {
public:
explicit MockServer(asio::io_service& io_service,
......@@ -336,16 +384,14 @@ protected:
DNSLookup* lookup = NULL,
DNSAnswer* answer = NULL) :
io_(io_service),
message_(new Message(Message::PARSE)),
respbuf_(new OutputBuffer(0)),
checkin_(checkin), lookup_(lookup), answer_(answer)
{
// HERE set up address
}
{}
void operator()(asio::error_code ec = asio::error_code(),
size_t length = 0)
{
// Do some stuff
}
{}
void resume(const bool done) {
done_ = done;
......@@ -358,13 +404,21 @@ protected:
}
inline void asyncLookup() {
// (*lookup_)(*io_message_, message_, respbuf_, this);
if (lookup_) {
(*lookup_)(*io_message_, message_, respbuf_, this);
}
}
private:
asio::io_service& io_;
bool done_;
// Currently unused; these will be used for testing
// asynchronous lookup calls via the asyncLookup() method
boost::shared_ptr<asiolink::IOMessage> io_message_;
isc::dns::MessagePtr message_;
isc::dns::OutputBufferPtr respbuf_;
// Callback functions provided by the caller
const SimpleCallback* checkin_;
const DNSLookup* lookup_;
......
......@@ -26,12 +26,13 @@
#include <gtest/gtest.h>
#include <dns/name.h>
#include <dns/message.h>
#include <dns/tests/unittest_util.h>
using namespace std;
using namespace isc::dns;
using isc::UnitTestUtil;
using isc::dns::NameComparisonResult;
namespace {
class UnitTestUtilConfig {
......@@ -179,3 +180,18 @@ UnitTestUtil::matchName(const char* nameexp1 UNUSED_PARAM,
}
return (::testing::AssertionSuccess());
}
void
UnitTestUtil::createRequestMessage(Message& message,
const Opcode& opcode,
const uint16_t qid,
const Name& name,
const RRClass& rrclass,
const RRType& rrtype)
{
message.clear(Message::RENDER);
message.setOpcode(opcode);
message.setQid(qid);
message.addQuestion(Question(name, rrclass, rrtype));
}
......@@ -21,6 +21,7 @@
#include <string>
#include <dns/name.h>
#include <dns/message.h>
#include <gtest/gtest.h>
......@@ -80,6 +81,20 @@ public:
static ::testing::AssertionResult
matchName(const char* nameexp1, const char* nameexp2,
const isc::dns::Name& name1, const isc::dns::Name& name2);
///
/// Populate a request message
///
/// Create a request message in 'request_message' using the
/// opcode 'opcode' and the name/class/type query tuple specified in
/// 'name', 'rrclass' and 'rrtype.
static void
createRequestMessage(isc::dns::Message& request_message,
const isc::dns::Opcode& opcode,
const uint16_t qid,
const isc::dns::Name& name,
const isc::dns::RRClass& rrclass,
const isc::dns::RRType& rrtype);
};
}
#endif // __UNITTEST_UTIL_H
......
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