Commit 5fc69482 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

sync with trunk


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac454@4093 e5f2f494-b856-4b98-b285-d166d9295462
parents 818530c7 7d371961
141. [bug] jinmei
b10-auth: Fixed a bug that the authoritative server includes
trailing garbage data in responses. This is a regression due to
change #135. (Trac #462, svn r4081)
140. [func] y-aharen
src/bin/auth: Added a feature to count queries and send counter
values to statistics periodically. To support it, added wrapping
......
......@@ -162,33 +162,20 @@ private:
AuthSrv* server_;
};
// This is a derived class of \c DNSAnswer, to serve as a
// callback in the asiolink module. It takes a completed
// set of answer data from the DNS lookup and assembles it
// into a wire-format response.
// This is a derived class of \c DNSAnswer, to serve as a callback in the
// asiolink module. We actually shouldn't do anything in this class because
// we build complete response messages in the process methods; otherwise
// the response message will contain trailing garbage. In future, we should
// probably even drop the reliance on DNSAnswer. We don't need the coroutine
// tricks provided in that framework, and its overhead would be significant
// in terms of performance consideration for the authoritative server
// implementation.
class MessageAnswer : public DNSAnswer {
public:
MessageAnswer(AuthSrv* srv) : server_(srv) {}
virtual void operator()(const IOMessage& io_message, MessagePtr message,
OutputBufferPtr buffer) const
{
MessageRenderer renderer(*buffer);
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
ConstEDNSPtr edns(message->getEDNS());
renderer.setLengthLimit(edns ? edns->getUDPSize() :
Message::DEFAULT_MAX_UDPSIZE);
} else {
renderer.setLengthLimit(65535);
}
message->toWire(renderer);
if (server_->getVerbose()) {
cerr << "[b10-auth] sending a response (" << renderer.getLength()
<< " bytes):\n" << message->toText() << endl;
}
}
private:
AuthSrv* server_;
MessageAnswer(AuthSrv*) {}
virtual void operator()(const IOMessage&, MessagePtr,
OutputBufferPtr) const
{}
};
// This is a derived class of \c SimpleCallback, to serve
......
......@@ -151,16 +151,21 @@ MemoryDatasourceConfig::build(ConstElementPtr config_value) {
isc_throw(AuthConfigError, "Missing zone file for zone: "
<< origin->str());
}
const result::Result result = memory_datasrc_->addZone(
ZonePtr(new MemoryZone(rrclass_, Name(origin->stringValue()))));
shared_ptr<MemoryZone> new_zone(new MemoryZone(rrclass_,
Name(origin->stringValue())));
const result::Result result = memory_datasrc_->addZone(new_zone);
if (result == result::EXIST) {
isc_throw(AuthConfigError, "zone "<< origin->str()
<< " already exists");
}
// TODO
// then load the zone from 'file', which is currently not implemented.
//
/*
* TODO: Once we have better reloading of configuration (something
* else than throwing everything away and loading it again), we will
* need the load method to be split into some kind of build and
* commit/abort parts.
*/
new_zone->load(file->stringValue());
}
}
......
......@@ -25,6 +25,24 @@ using namespace isc::datasrc;
namespace isc {
namespace auth {
void
Query::putSOA(const Zone& zone) const {
Zone::FindResult soa_result(zone.find(zone.getOrigin(),
RRType::SOA()));
if (soa_result.code != Zone::SUCCESS) {
isc_throw(NoSOA, "There's no SOA record in zone " <<
zone.getOrigin().toText());
} else {
/*
* FIXME:
* The const-cast is wrong, but the Message interface seems
* to insist.
*/
response_.addRRset(Message::SECTION_AUTHORITY,
boost::const_pointer_cast<RRset>(soa_result.rrset));
}
}
void
Query::process() const {
bool keep_doing = true;
......@@ -59,12 +77,14 @@ Query::process() const {
// TODO : add NS to authority section, fill in additional section.
break;
case Zone::NXDOMAIN:
// Just empty answer with SOA in authority section
response_.setRcode(Rcode::NXDOMAIN());
// TODO : add SOA to authority section
putSOA(*result.zone);
break;
case Zone::NXRRSET:
// Just empty answer with SOA in authority section
response_.setRcode(Rcode::NOERROR());
// TODO : add SOA to authority section
putSOA(*result.zone);
break;
case Zone::CNAME:
case Zone::DNAME:
......
......@@ -14,6 +14,8 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <exceptions/exceptions.h>
namespace isc {
namespace dns {
class Message;
......@@ -23,6 +25,7 @@ class RRType;
namespace datasrc {
class MemoryDataSrc;
class Zone;
}
namespace auth {
......@@ -100,15 +103,46 @@ public:
/// providing compatible behavior may have its own benefit, so this point
/// should be revisited later.
///
/// Right now this method never throws an exception, but it may in a
/// future version.
/// This might throw BadZone or any of its specific subclasses, but that
/// shouldn't happen in real-life (as BadZone means wrong data, it should
/// have been rejected upon loading).
void process() const;
/// \short Bad zone data encountered.
///
/// This is thrown when process encounteres misconfigured zone in a way
/// it can't continue. This throws, not sets the Rcode, because such
/// misconfigured zone should not be present in the data source and
/// should have been rejected sooner.
struct BadZone : public isc::Exception {
BadZone(const char* file, size_t line, const char* what) :
Exception(file, line, what)
{}
};
/// \short Zone is missing its SOA record.
///
/// We tried to add a SOA into the authoritative section, but the zone
/// does not contain one.
struct NoSOA : public BadZone {
NoSOA(const char* file, size_t line, const char* what) :
BadZone(file, line, what)
{}
};
private:
const isc::datasrc::MemoryDataSrc& memory_datasrc_;
const isc::dns::Name& qname_;
const isc::dns::RRType& qtype_;
isc::dns::Message& response_;
/**
* \short Adds a SOA.
*
* Adds a SOA of the zone into the authority zone of response_.
* Can throw NoSOA.
*/
void putSOA(const isc::datasrc::Zone& zone) const;
};
}
......
......@@ -15,6 +15,19 @@
// $Id$
#include <config.h>
#include <vector>
#include <gtest/gtest.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
#include <testutils/srv_unittest.h>
......@@ -22,6 +35,7 @@
using namespace isc::cc;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::xfr;
using namespace asiolink;
......@@ -45,8 +59,104 @@ protected:
MockXfroutClient xfrout;
AuthSrv server;
const RRClass rrclass;
vector<uint8_t> response_data;
};
// A helper function that builds a response to version.bind/TXT/CH that
// should be identical to the response from our builtin (static) data source
// by default. The resulting wire-format data will be stored in 'data'.
void
createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
const Name version_name("version.bind");
Message message(Message::RENDER);
UnitTestUtil::createRequestMessage(message, Opcode::QUERY(),
qid, version_name,
RRClass::CH(), RRType::TXT());
message.setHeaderFlag(Message::HEADERFLAG_QR);
message.setHeaderFlag(Message::HEADERFLAG_AA);
RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
RRType::TXT(), RRTTL(0)));
rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
message.addRRset(Message::SECTION_ANSWER, rrset_version);
RRsetPtr rrset_version_ns = RRsetPtr(new RRset(version_name, RRClass::CH(),
RRType::NS(), RRTTL(0)));
rrset_version_ns->addRdata(generic::NS(version_name));
message.addRRset(Message::SECTION_AUTHORITY, rrset_version_ns);
OutputBuffer obuffer(0);
MessageRenderer renderer(obuffer);
message.toWire(renderer);
data.clear();
data.assign(static_cast<const uint8_t*>(renderer.getData()),
static_cast<const uint8_t*>(renderer.getData()) +
renderer.getLength());
}
// In the following tests we confirm the response data is rendered in
// wire format in the expected way.
// The most primitive check: checking the result of the processMessage()
// method
TEST_F(AuthSrvTest, builtInQuery) {
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("version.bind"),
RRClass::CH(), RRType::TXT());
createRequestPacket(request_message, IPPROTO_UDP);
server.processMessage(*io_message, parse_message, response_obuffer,
&dnsserv);
createBuiltinVersionResponse(default_qid, response_data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
response_obuffer->getData(),
response_obuffer->getLength(),
&response_data[0], response_data.size());
}
// Same test emulating the UDPServer class behavior (defined in libasiolink).
// This is not a good test in that it assumes internal implementation details
// of UDPServer, but we've encountered a regression due to the introduction
// of that class, so we add a test for that case to prevent such a regression
// in future.
// Besides, the generalization of UDPServer is probably too much for the
// authoritative only server in terms of performance, and it's quite likely
// we need to drop it for the authoritative server implementation.
// At that point we can drop this test, too.
TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("version.bind"),
RRClass::CH(), RRType::TXT());
createRequestPacket(request_message, IPPROTO_UDP);
(*server.getDNSLookupProvider())(*io_message, parse_message,
response_obuffer, &dnsserv);
(*server.getDNSAnswerProvider())(*io_message, parse_message,
response_obuffer);
createBuiltinVersionResponse(default_qid, response_data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
response_obuffer->getData(),
response_obuffer->getLength(),
&response_data[0], response_data.size());
}
// Same type of test as builtInQueryViaDNSServer but for an error response.
TEST_F(AuthSrvTest, iqueryViaDNSServer) {
createDataFromFile("iquery_fromWire.wire");
(*server.getDNSLookupProvider())(*io_message, parse_message,
response_obuffer, &dnsserv);
(*server.getDNSAnswerProvider())(*io_message, parse_message,
response_obuffer);
UnitTestUtil::readWireData("iquery_response_fromWire.wire",
response_data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
response_obuffer->getData(),
response_obuffer->getLength(),
&response_data[0], response_data.size());
}
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
UNSUPPORTED_REQUEST_TEST;
......
......@@ -17,6 +17,7 @@
#include <exceptions/exceptions.h>
#include <dns/rrclass.h>
#include <dns/masterload.h>
#include <cc/data.h>
......@@ -143,33 +144,42 @@ TEST_F(MemoryDatasrcConfigTest, addZeroZone) {
}
TEST_F(MemoryDatasrcConfigTest, addOneZone) {
parser->build(Element::fromJSON(
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"example.zone\"}]}]"));
parser->commit();
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
// Check it actually loaded something
EXPECT_EQ(Zone::SUCCESS, server.getMemoryDataSrc(rrclass)->findZone(
Name("ns.example.com.")).zone->find(Name("ns.example.com."),
RRType::A()).code);
}
TEST_F(MemoryDatasrcConfigTest, addMultiZones) {
parser->build(Element::fromJSON(
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"example.zone\"},"
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"},"
" {\"origin\": \"example.org\","
" \"file\": \"example.org.zone\"},"
" \"file\": \"" TEST_DATA_DIR
"/example.org.zone\"},"
" {\"origin\": \"example.net\","
" \"file\": \"example.net.zone\"}]}]"));
parser->commit();
" \"file\": \"" TEST_DATA_DIR
"/example.net.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(3, server.getMemoryDataSrc(rrclass)->getZoneCount());
}
TEST_F(MemoryDatasrcConfigTest, replace) {
parser->build(Element::fromJSON(
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"example.zone\"}]}]"));
parser->commit();
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
EXPECT_EQ(isc::datasrc::result::SUCCESS,
server.getMemoryDataSrc(rrclass)->findZone(
......@@ -179,31 +189,69 @@ TEST_F(MemoryDatasrcConfigTest, replace) {
// should replace the old one.
delete parser;
parser = createAuthConfigParser(server, "datasources");
parser->build(Element::fromJSON(
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.org\","
" \"file\": \"example.org.zone\"},"
" \"file\": \"" TEST_DATA_DIR
"/example.org.zone\"},"
" {\"origin\": \"example.net\","
" \"file\": \"example.net.zone\"}]}]"));
parser->commit();
" \"file\": \"" TEST_DATA_DIR
"/example.net.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(2, server.getMemoryDataSrc(rrclass)->getZoneCount());
EXPECT_EQ(isc::datasrc::result::NOTFOUND,
server.getMemoryDataSrc(rrclass)->findZone(
Name("example.com")).code);
}
TEST_F(MemoryDatasrcConfigTest, exception) {
// Load a zone
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
EXPECT_EQ(isc::datasrc::result::SUCCESS,
server.getMemoryDataSrc(rrclass)->findZone(
Name("example.com")).code);
// create a new parser, and try to load something. It will throw,
// the given master file should not exist
delete parser;
parser = createAuthConfigParser(server, "datasources");
EXPECT_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.org\","
" \"file\": \"" TEST_DATA_DIR
"/example.org.zone\"},"
" {\"origin\": \"example.net\","
" \"file\": \"" TEST_DATA_DIR
"/nonexistent.zone\"}]}]")), isc::dns::MasterLoadError);
// As that one throwed exception, it is not expected from us to
// commit it
// The original should be untouched
EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
EXPECT_EQ(isc::datasrc::result::SUCCESS,
server.getMemoryDataSrc(rrclass)->findZone(
Name("example.com")).code);
}
TEST_F(MemoryDatasrcConfigTest, remove) {
parser->build(Element::fromJSON(
EXPECT_NO_THROW(parser->build(Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"example.zone\"}]}]"));
parser->commit();
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"}]}]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(1, server.getMemoryDataSrc(rrclass)->getZoneCount());
delete parser;
parser = createAuthConfigParser(server, "datasources");
parser->build(Element::fromJSON("[]"));
parser->commit();
EXPECT_NO_THROW(parser->build(Element::fromJSON("[]")));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ(AuthSrv::MemoryDataSrcPtr(), server.getMemoryDataSrc(rrclass));
}
......@@ -212,9 +260,11 @@ TEST_F(MemoryDatasrcConfigTest, adDuplicateZones) {
Element::fromJSON(
"[{\"type\": \"memory\","
" \"zones\": [{\"origin\": \"example.com\","
" \"file\": \"example.zone\"},"
" \"file\": \"" TEST_DATA_DIR
"/example.zone\"},"
" {\"origin\": \"example.com\","
" \"file\": \"example.com.zone\"}]}]")),
" \"file\": \"" TEST_DATA_DIR
"/example.com.zone\"}]}]")),
AuthConfigError);
}
......
......@@ -28,10 +28,14 @@ using namespace isc::dns;
using namespace isc::datasrc;
using namespace isc::auth;
namespace {
RRsetPtr a_rrset = RRsetPtr(new RRset(Name("www.example.com"),
RRClass::IN(), RRType::A(),
RRTTL(3600)));
namespace {
RRsetPtr soa_rrset = RRsetPtr(new RRset(Name("example.com"),
RRClass::IN(), RRType::SOA(),
RRTTL(3600)));
// This is a mock Zone class for testing.
// It is a derived class of Zone, and simply hardcode the results of find()
// return SUCCESS for "www.example.com",
......@@ -41,7 +45,9 @@ namespace {
// else return DNAME
class MockZone : public Zone {
public:
MockZone() : origin_(Name("example.com"))
MockZone(bool has_SOA = true) :
origin_(Name("example.com")),
has_SOA_(has_SOA)
{}
virtual const isc::dns::Name& getOrigin() const;
virtual const isc::dns::RRClass& getClass() const;
......@@ -51,6 +57,7 @@ public:
private:
Name origin_;
bool has_SOA_;
};
const Name&
......@@ -64,20 +71,24 @@ MockZone::getClass() const {
}
Zone::FindResult
MockZone::find(const Name& name, const RRType&) const {
MockZone::find(const Name& name, const RRType& type) const {
// hardcode the find results
if (name == Name("www.example.com")) {
return FindResult(SUCCESS, a_rrset);
return (FindResult(SUCCESS, a_rrset));
} else if (name == Name("example.com") && type == RRType::SOA() &&
has_SOA_)
{
return (FindResult(SUCCESS, soa_rrset));
} else if (name == Name("delegation.example.com")) {
return FindResult(DELEGATION, RRsetPtr());
return (FindResult(DELEGATION, RRsetPtr()));
} else if (name == Name("nxdomain.example.com")) {
return FindResult(NXDOMAIN, RRsetPtr());
return (FindResult(NXDOMAIN, RRsetPtr()));
} else if (name == Name("nxrrset.example.com")) {
return FindResult(NXRRSET, RRsetPtr());
return (FindResult(NXRRSET, RRsetPtr()));
} else if (name == Name("cname.example.com")) {
return FindResult(CNAME, RRsetPtr());
return (FindResult(CNAME, RRsetPtr()));
} else {
return FindResult(DNAME, RRsetPtr());
return (FindResult(DNAME, RRsetPtr()));
}
}
......@@ -106,7 +117,7 @@ TEST_F(QueryTest, noZone) {
}
TEST_F(QueryTest, matchZone) {
// match qname, normal query
// add a matching zone.
memory_datasrc.addZone(ZonePtr(new MockZone()));
query.process();
EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
......@@ -119,12 +130,39 @@ TEST_F(QueryTest, matchZone) {
Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
nxdomain_query.process();
EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
Name("example.com"), RRClass::IN(), RRType::SOA()));
// NXRRSET
const Name nxrrset_name(Name("nxrrset.example.com"));
Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
nxrrset_query.process();
EXPECT_EQ(Rcode::NOERROR(), response.getRcode());
EXPECT_EQ(0, response.getRRCount(Message::SECTION_ANSWER));
EXPECT_EQ(0, response.getRRCount(Message::SECTION_ADDITIONAL));
EXPECT_TRUE(response.hasRRset(Message::SECTION_AUTHORITY,
Name("example.com"), RRClass::IN(), RRType::SOA()));
}
/*
* This tests that when there's no SOA and we need a negative answer. It should
* throw in that case.
*/
TEST_F(QueryTest, noSOA) {
memory_datasrc.addZone(ZonePtr(new MockZone(false)));
// The NX Domain
const Name nxdomain_name(Name("nxdomain.example.com"));
Query nxdomain_query(memory_datasrc, nxdomain_name, qtype, response);
EXPECT_THROW(nxdomain_query.process(), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
const Name nxrrset_name(Name("nxrrset.example.com"));
Query nxrrset_query(memory_datasrc, nxrrset_name, qtype, response);
EXPECT_THROW(nxrrset_query.process(), Query::NoSOA);
}
TEST_F(QueryTest, noMatchZone) {
......@@ -136,4 +174,5 @@ TEST_F(QueryTest, noMatchZone) {
nomatch_query.process();
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
}
#
# A QUERY message with unsupported version of EDNS..
# A QUERY message with unsupported version of EDNS.
#