Commit e0744372 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac999'

with fixing Conflicts:
	ChangeLog
	src/bin/resolver/main.cc
	src/bin/resolver/resolver.cc
	src/bin/resolver/resolver_messages.mes
	src/lib/acl/Makefile.am
	src/lib/acl/tests/Makefile.am
parents 499ca0aa 041c3ec8
......@@ -218,6 +218,10 @@ main(int argc, char* argv[]) {
}
resolver->setConfigSession(config_session);
// Install all initial configurations. If loading configuration
// fails, it will be logged, but we start the server anyway, giving
// the user a second chance to correct the configuration.
resolver->updateConfig(config_session->getFullConfig());
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CONFIG_LOADED);
LOG_INFO(resolver_logger, RESOLVER_STARTED);
......
......@@ -20,12 +20,18 @@
#include <vector>
#include <cassert>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include <exceptions/exceptions.h>
#include <acl/acl.h>
#include <acl/loader.h>
#include <asiodns/asiodns.h>
#include <asiolink/asiolink.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <config/ccsession.h>
#include <exceptions/exceptions.h>
......@@ -41,6 +47,8 @@
#include <dns/rrttl.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <server_common/client.h>
#include <server_common/portconfig.h>
#include <resolve/recursive_query.h>
......@@ -49,14 +57,17 @@
#include "resolver_log.h"
using namespace std;
using namespace boost;
using namespace isc;
using namespace isc::util;
using namespace isc::acl;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::config;
using namespace isc::asiodns;
using namespace isc::asiolink;
using namespace isc::server_common;
using namespace isc::server_common::portconfig;
class ResolverImpl {
......@@ -71,6 +82,7 @@ public:
client_timeout_(4000),
lookup_timeout_(30000),
retries_(3),
query_acl_(new Resolver::ClientACL(REJECT)),
rec_query_(NULL)
{}
......@@ -141,10 +153,20 @@ public:
void resolve(const isc::dns::QuestionPtr& question,
const isc::resolve::ResolverInterface::CallbackPtr& callback);
void processNormalQuery(ConstMessagePtr query_message,
MessagePtr answer_message,
OutputBufferPtr buffer,
DNSServer* server);
enum NormalQueryResult { RECURSION, DROPPED, ERROR };
NormalQueryResult processNormalQuery(const IOMessage& io_message,
MessagePtr query_message,
MessagePtr answer_message,
OutputBufferPtr buffer,
DNSServer* server);
const Resolver::ClientACL& getQueryACL() const {
return (*query_acl_);
}
void setQueryACL(shared_ptr<const Resolver::ClientACL> new_acl) {
query_acl_ = new_acl;
}
/// Currently non-configurable, but will be.
static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
......@@ -169,6 +191,8 @@ public:
unsigned retries_;
private:
/// ACL on incoming queries
shared_ptr<const Resolver::ClientACL> query_acl_;
/// Object to handle upstream queries
RecursiveQuery* rec_query_;
......@@ -401,7 +425,6 @@ Resolver::processMessage(const IOMessage& io_message,
server->resume(false);
return;
}
} catch (const Exception& ex) {
LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_HEADER_ERROR)
.arg(ex.what());
......@@ -435,76 +458,40 @@ Resolver::processMessage(const IOMessage& io_message,
RESOLVER_DNS_MESSAGE_RECEIVED).arg(*query_message);
// Perform further protocol-level validation.
bool sendAnswer = true;
bool send_answer = true;
if (query_message->getOpcode() == Opcode::NOTIFY()) {
makeErrorMessage(query_message, answer_message,
buffer, Rcode::NOTAUTH());
// Notify arrived, but we are not authoritative.
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
RESOLVER_NOTIFY_RECEIVED);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NFYNOTAUTH);
} else if (query_message->getOpcode() != Opcode::QUERY()) {
// Unsupported opcode.
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
RESOLVER_UNSUPPORTED_OPCODE).arg(query_message->getOpcode());
makeErrorMessage(query_message, answer_message,
buffer, Rcode::NOTIMP());
} else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
// Not one question
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
RESOLVER_NOT_ONE_QUESTION)
.arg(query_message->getRRCount(Message::SECTION_QUESTION));
makeErrorMessage(query_message, answer_message,
buffer, Rcode::FORMERR());
makeErrorMessage(query_message, answer_message, buffer,
Rcode::FORMERR());
} else {
ConstQuestionPtr question = *query_message->beginQuestion();
const RRType &qtype = question->getType();
if (qtype == RRType::AXFR()) {
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
// Can't process AXFR request receoved over UDP
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
RESOLVER_AXFR_UDP);
makeErrorMessage(query_message, answer_message,
buffer, Rcode::FORMERR());
} else {
// ... or over TCP for that matter
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
RESOLVER_AXFR_TCP);
makeErrorMessage(query_message, answer_message,
buffer, Rcode::NOTIMP());
}
} else if (qtype == RRType::IXFR()) {
// Can't process IXFR request
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_IXFR);
makeErrorMessage(query_message, answer_message,
buffer, Rcode::NOTIMP());
} else if (question->getClass() != RRClass::IN()) {
// Non-IN message received, refuse it.
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NON_IN_PACKET)
.arg(question->getClass());
makeErrorMessage(query_message, answer_message,
buffer, Rcode::REFUSED());
} else {
const ResolverImpl::NormalQueryResult result =
impl_->processNormalQuery(io_message, query_message,
answer_message, buffer, server);
if (result == ResolverImpl::RECURSION) {
// The RecursiveQuery object will post the "resume" event to the
// DNSServer when an answer arrives, so we don't have to do it now.
sendAnswer = false;
impl_->processNormalQuery(query_message, answer_message,
buffer, server);
return;
} else if (result == ResolverImpl::DROPPED) {
send_answer = false;
}
}
if (sendAnswer) {
server->resume(true);
}
server->resume(send_answer);
}
void
......@@ -514,24 +501,102 @@ ResolverImpl::resolve(const QuestionPtr& question,
rec_query_->resolve(question, callback);
}
void
ResolverImpl::processNormalQuery(ConstMessagePtr query_message,
ResolverImpl::NormalQueryResult
ResolverImpl::processNormalQuery(const IOMessage& io_message,
MessagePtr query_message,
MessagePtr answer_message,
OutputBufferPtr buffer,
DNSServer* server)
{
const ConstQuestionPtr question = *query_message->beginQuestion();
const RRType qtype = question->getType();
const RRClass qclass = question->getClass();
// Apply query ACL
Client client(io_message);
const BasicAction query_action(getQueryACL().execute(client));
if (query_action == isc::acl::REJECT) {
LOG_INFO(resolver_logger, RESOLVER_QUERY_REJECTED)
.arg(question->getName()).arg(qtype).arg(qclass).arg(client);
makeErrorMessage(query_message, answer_message, buffer,
Rcode::REFUSED());
return (ERROR);
} else if (query_action == isc::acl::DROP) {
LOG_INFO(resolver_logger, RESOLVER_QUERY_DROPPED)
.arg(question->getName()).arg(qtype).arg(qclass).arg(client);
return (DROPPED);
}
LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_QUERY_ACCEPTED)
.arg(question->getName()).arg(qtype).arg(question->getClass())
.arg(client);
// ACL passed. Reject inappropriate queries for the resolver.
if (qtype == RRType::AXFR()) {
if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
// Can't process AXFR request receoved over UDP
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_UDP);
makeErrorMessage(query_message, answer_message, buffer,
Rcode::FORMERR());
} else {
// ... or over TCP for that matter
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_TCP);
makeErrorMessage(query_message, answer_message, buffer,
Rcode::NOTIMP());
}
return (ERROR);
} else if (qtype == RRType::IXFR()) {
// Can't process IXFR request
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_IXFR);
makeErrorMessage(query_message, answer_message, buffer,
Rcode::NOTIMP());
return (ERROR);
} else if (qclass != RRClass::IN()) {
// Non-IN message received, refuse it.
LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NON_IN_PACKET)
.arg(question->getClass());
makeErrorMessage(query_message, answer_message, buffer,
Rcode::REFUSED());
return (ERROR);
}
// Everything is okay. Start resolver.
if (upstream_.empty()) {
// Processing normal query
LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_NORMAL_QUERY);
ConstQuestionPtr question = *query_message->beginQuestion();
LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_NORMQUERY);
rec_query_->resolve(*question, answer_message, buffer, server);
} else {
// Processing forward query
LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_FORWARD_QUERY);
rec_query_->forward(query_message, answer_message, buffer, server);
}
return (RECURSION);
}
namespace {
// This is a simplified ACL parser for the initial implementation with minimal
// external dependency. For a longer term we'll switch to a more generic
// loader with allowing more complicated ACL syntax.
shared_ptr<const Resolver::ClientACL>
createQueryACL(isc::data::ConstElementPtr acl_config) {
if (!acl_config) {
return (shared_ptr<const Resolver::ClientACL>());
}
shared_ptr<Resolver::ClientACL> new_acl(
new Resolver::ClientACL(REJECT));
BOOST_FOREACH(ConstElementPtr rule, acl_config->listValue()) {
ConstElementPtr action = rule->get("action");
ConstElementPtr from = rule->get("from");
if (!action || !from) {
isc_throw(BadValue, "query ACL misses mandatory parameter");
}
new_acl->append(shared_ptr<IPCheck<Client> >(
new IPCheck<Client>(from->stringValue())),
defaultActionLoader(action));
}
return (new_acl);
}
}
ConstElementPtr
......@@ -550,6 +615,8 @@ Resolver::updateConfig(ConstElementPtr config) {
ConstElementPtr listenAddressesE(config->get("listen_on"));
AddressList listenAddresses(parseAddresses(listenAddressesE,
"listen_on"));
shared_ptr<const ClientACL> query_acl(createQueryACL(
config->get("query_acl")));
bool set_timeouts(false);
int qtimeout = impl_->query_timeout_;
int ctimeout = impl_->client_timeout_;
......@@ -607,15 +674,6 @@ Resolver::updateConfig(ConstElementPtr config) {
if (listenAddressesE) {
setListenAddresses(listenAddresses);
need_query_restart = true;
} else {
if (!configured_) {
// TODO: ModuleSpec needs getDefault()
AddressList initial_addresses;
initial_addresses.push_back(AddressPair("127.0.0.1", 53));
initial_addresses.push_back(AddressPair("::1", 53));
setListenAddresses(initial_addresses);
need_query_restart = true;
}
}
if (forwardAddressesE) {
setForwardAddresses(forwardAddresses);
......@@ -629,6 +687,9 @@ Resolver::updateConfig(ConstElementPtr config) {
setTimeouts(qtimeout, ctimeout, ltimeout, retries);
need_query_restart = true;
}
if (query_acl) {
setQueryACL(query_acl);
}
if (need_query_restart) {
impl_->queryShutdown();
......@@ -714,3 +775,18 @@ AddressList
Resolver::getListenAddresses() const {
return (impl_->listen_);
}
const Resolver::ClientACL&
Resolver::getQueryACL() const {
return (impl_->getQueryACL());
}
void
Resolver::setQueryACL(shared_ptr<const ClientACL> new_acl) {
if (!new_acl) {
isc_throw(InvalidParameter, "NULL pointer is passed to setQueryACL");
}
LOG_INFO(resolver_logger, RESOLVER_SET_QUERY_ACL);
impl_->setQueryACL(new_acl);
}
......@@ -19,6 +19,10 @@
#include <vector>
#include <utility>
#include <boost/shared_ptr.hpp>
#include <acl/acl.h>
#include <cc/data.h>
#include <config/ccsession.h>
#include <dns/message.h>
......@@ -37,6 +41,12 @@
#include <resolve/resolver_interface.h>
namespace isc {
namespace server_common {
class Client;
}
}
class ResolverImpl;
/**
......@@ -236,6 +246,27 @@ public:
*/
int getRetries() const;
// Shortcut typedef used for query ACL.
typedef isc::acl::ACL<isc::server_common::Client> ClientACL;
/// Get the query ACL.
///
/// \exception None
const ClientACL& getQueryACL() const;
/// Set the new query ACL.
///
/// This method replaces the existing query ACL completely.
/// Normally this method will be called via the configuration handler,
/// but is publicly available for convenience of tests (and other
/// experimental purposes).
/// \c new_acl must not be a NULL pointer.
///
/// \exception InvalidParameter The given pointer is NULL
///
/// \param new_acl The new ACL to replace the existing one.
void setQueryACL(boost::shared_ptr<const ClientACL> new_acl);
private:
ResolverImpl* impl_;
isc::asiodns::DNSService* dnss_;
......
......@@ -113,6 +113,41 @@
}
]
}
},
{
"item_name": "query_acl",
"item_type": "list",
"item_optional": false,
"item_default": [
{
"action": "ACCEPT",
"from": "127.0.0.1"
},
{
"action": "ACCEPT",
"from": "::1"
}
],
"list_item_spec": {
"item_name": "rule",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "action",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{
"item_name": "from",
"item_type": "string",
"item_optional": false,
"item_default": ""
}
]
}
}
],
"commands": [
......
......@@ -196,3 +196,24 @@ query and is ignoring it.
A debug message, the resolver received a message with an unsupported opcode
(it can only process QUERY opcodes). It will return a message to the sender
with the RCODE set to NOTIMP.
% RESOLVER_SET_QUERY_ACL query ACL is configured
A debug message that appears when a new query ACL is configured for the
resolver.
% RESOLVER_QUERY_ACCEPTED query accepted: '%1/%2/%3' from %4
A debug message that indicates an incoming query is accepted in terms of
the query ACL. The log message shows the query in the form of
<query name>/<query type>/<query class>, and the client that sends the
query in the form of <Source IP address>#<source port>.
% RESOLVER_QUERY_REJECTED query rejected: '%1/%2/%3' from %4
An informational message that indicates an incoming query is rejected
in terms of the query ACL. This results in a response with an RCODE of
REFUSED. See QUERYACCEPTED for the information given in the message.
% RESOLVER_QUERY_DROPPED query dropped: '%1/%2/%3' from %4
An informational message that indicates an incoming query is dropped
in terms of the query ACL. Unlike the QUERYREJECTED case, the server does
not return any response. See QUERYACCEPTED for the information given in
the message.
......@@ -47,6 +47,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
run_unittests_LDADD += $(top_builddir)/src/lib/acl/libacl.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
# Note the ordering matters: -Wno-... must follow -Wextra (defined in
......
......@@ -16,12 +16,23 @@
#include <string>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <cc/data.h>
#include <config/ccsession.h>
#include <asiodns/asiodns.h>
#include <asiolink/asiolink.h>
#include <asiolink/io_address.h>
#include <asiolink/io_socket.h>
#include <asiolink/io_message.h>
#include <acl/acl.h>
#include <server_common/client.h>
#include <resolver/resolver.h>
......@@ -30,25 +41,37 @@
#include <testutils/portconfig.h>
using namespace std;
using boost::scoped_ptr;
using namespace isc::acl;
using namespace isc::data;
using namespace isc::testutils;
using namespace isc::asiodns;
using namespace isc::asiolink;
using namespace isc::server_common;
using isc::UnitTestUtil;
namespace {
class ResolverConfig : public ::testing::Test {
public:
IOService ios;
DNSService dnss;
Resolver server;
ResolverConfig() :
dnss(ios, NULL, NULL, NULL)
{
server.setDNSService(dnss);
server.setConfigured();
}
void invalidTest(const string &JSON, const string& name);
protected:
IOService ios;
DNSService dnss;
Resolver server;
scoped_ptr<const IOEndpoint> endpoint;
scoped_ptr<const IOMessage> request;
scoped_ptr<const Client> client;
ResolverConfig() : dnss(ios, NULL, NULL, NULL) {
server.setDNSService(dnss);
server.setConfigured();
}
const Client& createClient(const string& source_addr) {
endpoint.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress(source_addr),
53210));
request.reset(new IOMessage(NULL, 0, IOSocket::getDummyUDPSocket(),
*endpoint));
client.reset(new Client(*request));
return (*client);
}
void invalidTest(const string &JSON, const string& name);
};
TEST_F(ResolverConfig, forwardAddresses) {
......@@ -228,4 +251,128 @@ TEST_F(ResolverConfig, invalidTimeoutsConfig) {
"}", "Negative number of retries");
}
TEST_F(ResolverConfig, defaultQueryACL) {
// If no configuration is loaded, the default ACL should reject everything.
EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
EXPECT_EQ(REJECT, server.getQueryACL().execute(
createClient("2001:db8::1")));
// The following would be allowed if the server had loaded the default
// configuration from the spec file. In this context it should not have
// happened, and they should be rejected just like the above cases.
EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("127.0.0.1")));
EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("::1")));
}
TEST_F(ResolverConfig, emptyQueryACL) {
// Explicitly configured empty ACL should have the same effect.
ElementPtr config(Element::fromJSON("{ \"query_acl\": [] }"));
ConstElementPtr result(server.updateConfig(config));
EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
EXPECT_EQ(REJECT, server.getQueryACL().execute(
createClient("2001:db8::1")));
}
TEST_F(ResolverConfig, queryACLIPv4) {
// A simple "accept" query for a specific IPv4 address
ElementPtr config(Element::fromJSON(
"{ \"query_acl\": "
" [ {\"action\": \"ACCEPT\","
" \"from\": \"192.0.2.1\"} ] }"));
ConstElementPtr result(server.updateConfig(config));
EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
EXPECT_EQ(ACCEPT, server.getQueryACL().execute(createClient("192.0.2.1")));
EXPECT_EQ(REJECT, server.getQueryACL().execute(
createClient("2001:db8::1")));
}
TEST_F(ResolverConfig, queryACLIPv6) {
// same for IPv6
ElementPtr config(Element::fromJSON(
"{ \"query_acl\": "
" [ {\"action\": \"ACCEPT\","
" \"from\": \"2001:db8::1\"} ] }"));
ConstElementPtr result(server.updateConfig(config));
EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
EXPECT_EQ(ACCEPT, server.getQueryACL().execute(
createClient("2001:db8::1")));
}
TEST_F(ResolverConfig, multiEntryACL) {