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

[master] Merge branch 'trac1539-2' with fixing minor conflicts.

parents 18faaf14 fd12fc92
......@@ -255,6 +255,20 @@ processed by the authoritative server has been found to contain an
unsupported opcode. (The opcode is included in the message.) The server
will return an error code of NOTIMPL to the sender.
% AUTH_MESSAGE_FORWARD_ERROR failed to forward %1 request from %2: %3
The authoritative server tried to forward some type DNS request
message to a separate process (e.g., forwarding dynamic update
requests to b10-ddns) to handle it, but it failed. The authoritative
server returns SERVFAIL to the client on behalf of the separate
process. The error could be configuration mismatch between b10-auth
and the recipient component, or it may be because the requests are
coming too fast and the receipient process cannot keep up with the
rate, or some system level failure. In either case this means the
BIND 10 system is not working as expected, so the administrator should
look into the cause and address the issue. The log message includes
the client's address (and port), and the error message sent from the
lower layer that detects the failure.
% AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
This is a debug message indicating that the authoritative server has
created a channel to the XFRIN (Transfer-in) process. It is issued
......
......@@ -14,18 +14,10 @@
#include <config.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
#include <memory>
#include <boost/bind.hpp>
#include <util/io/socketsession.h>
#include <asiolink/asiolink.h>
#include <asiolink/io_endpoint.h>
#include <config/ccsession.h>
......@@ -64,6 +56,18 @@
#include <auth/statistics.h>
#include <auth/auth_log.h>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
#include <memory>
#include <sys/types.h>
#include <netinet/in.h>
using namespace std;
using namespace isc;
......@@ -71,6 +75,7 @@ using namespace isc::cc;
using namespace isc::datasrc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::util::io;
using namespace isc::auth;
using namespace isc::dns::rdata;
using namespace isc::data;
......@@ -107,6 +112,107 @@ public:
private:
MessageRenderer& renderer_;
};
// A helper container of socket session forwarder.
//
// This class provides a simple wrapper interface to SocketSessionForwarder
// so that the caller doesn't have to worry about connection management,
// exception handling or parameter building.
//
// It internally maintains whether the underlying forwarder establishes a
// connection to the receiver. On a forwarding request, if the connection
// hasn't been established yet, it automatically opens a new one, then
// pushes the session over it. It also closes the connection on destruction,
// or a non-recoverable error happens, automatically. So the only thing
// the application has to do is to create this object and push any session
// to be forwarded.
class SocketSessionForwarderHolder {
public:
/// \brief The constructor.
///
/// \param message_name Any string that can identify the type of messages
/// to be forwarded via this session. It will be only used as part of
/// log message, so it can be anything, but in practice something like
/// "update" or "xfr" is expected.
/// \param forwarder The underlying socket session forwarder.
SocketSessionForwarderHolder(const string& message_name,
BaseSocketSessionForwarder& forwarder) :
message_name_(message_name), forwarder_(forwarder), connected_(false)
{}
~SocketSessionForwarderHolder() {
if (connected_) {
forwarder_.close();
}
}
/// \brief Push a socket session corresponding to given IOMessage.
///
/// If the connection with the receiver process hasn't been established,
/// it automatically establishes one, then push the session over it.
///
/// If either connect or push fails, the underlying forwarder object should
/// throw an exception. This method logs the event, and propagates the
/// exception to the caller, which will eventually result in SERVFAIL.
/// The connection, if established, is automatically closed, so the next
/// forward request will trigger reopening a new connection.
///
/// \note: Right now, there's no API to retrieve the local address from
/// the IOMessage. Until it's added, we pass the remote address as
/// local.
///
/// \param io_message The request message to be forwarded as a socket
/// session. It will be converted to the parameters that the underlying
/// SocketSessionForwarder expects.
void push(const IOMessage& io_message) {
const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
const int protocol = remote_ep.getProtocol();
const int sock_type = getSocketType(protocol);
try {
connect();
forwarder_.push(io_message.getSocket().getNative(),
remote_ep.getFamily(), sock_type, protocol,
remote_ep.getSockAddr(), remote_ep.getSockAddr(),
io_message.getData(), io_message.getDataSize());
} catch (const SocketSessionError& ex) {
LOG_ERROR(auth_logger, AUTH_MESSAGE_FORWARD_ERROR).
arg(message_name_).arg(remote_ep).arg(ex.what());
close();
throw;
}
}
private:
const string message_name_;
BaseSocketSessionForwarder& forwarder_;
bool connected_;
void connect() {
if (!connected_) {
forwarder_.connectToReceiver();
connected_ = true;
}
}
void close() {
if (connected_) {
forwarder_.close();
connected_ = false;
}
}
static int getSocketType(int protocol) {
switch (protocol) {
case IPPROTO_UDP:
return (SOCK_DGRAM);
case IPPROTO_TCP:
return (SOCK_STREAM);
default:
isc_throw(isc::InvalidParameter,
"Unexpected socket address family: " << protocol);
}
}
};
}
class AuthSrvImpl {
......@@ -115,7 +221,8 @@ private:
AuthSrvImpl(const AuthSrvImpl& source);
AuthSrvImpl& operator=(const AuthSrvImpl& source);
public:
AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client);
AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client,
BaseSocketSessionForwarder& ddns_forwarder);
~AuthSrvImpl();
isc::data::ConstElementPtr setDbFile(isc::data::ConstElementPtr config);
......@@ -128,6 +235,7 @@ public:
bool processNotify(const IOMessage& io_message, Message& message,
OutputBuffer& buffer,
auto_ptr<TSIGContext> tsig_context);
bool processUpdate(const IOMessage& io_message);
IOService io_service_;
......@@ -189,6 +297,9 @@ private:
bool xfrout_connected_;
AbstractXfroutClient& xfrout_client_;
// Socket session forwarder for dynamic update requests
SocketSessionForwarderHolder ddns_forwarder_;
/// Increment query counter
void incCounter(const int protocol);
......@@ -199,7 +310,8 @@ private:
};
AuthSrvImpl::AuthSrvImpl(const bool use_cache,
AbstractXfroutClient& xfrout_client) :
AbstractXfroutClient& xfrout_client,
BaseSocketSessionForwarder& ddns_forwarder) :
config_session_(NULL),
xfrin_session_(NULL),
memory_client_class_(RRClass::IN()),
......@@ -207,7 +319,8 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache,
counters_(),
keyring_(NULL),
xfrout_connected_(false),
xfrout_client_(xfrout_client)
xfrout_client_(xfrout_client),
ddns_forwarder_("update", ddns_forwarder)
{
// cur_datasrc_ is automatically initialized by the default constructor,
// effectively being an empty (sqlite) data source. once ccsession is up
......@@ -277,9 +390,10 @@ private:
AuthSrv* server_;
};
AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client)
AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client,
BaseSocketSessionForwarder& ddns_forwarder)
{
impl_ = new AuthSrvImpl(use_cache, xfrout_client);
impl_ = new AuthSrvImpl(use_cache, xfrout_client, ddns_forwarder);
checkin_ = new ConfigChecker(this);
dns_lookup_ = new MessageLookup(this);
dns_answer_ = new MessageAnswer(this);
......@@ -527,16 +641,19 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
return;
}
const Opcode opcode = message.getOpcode();
bool send_answer = true;
try {
// update per opcode statistics counter. This can only be reliable
// after TSIG check succeeds.
impl_->counters_.inc(message.getOpcode());
if (message.getOpcode() == Opcode::NOTIFY()) {
if (opcode == Opcode::NOTIFY()) {
send_answer = impl_->processNotify(io_message, message, buffer,
tsig_context);
} else if (message.getOpcode() != Opcode::QUERY()) {
} else if (opcode == Opcode::UPDATE()) {
send_answer = impl_->processUpdate(io_message);
} else if (opcode != Opcode::QUERY()) {
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
.arg(message.getOpcode().toText());
makeErrorMessage(impl_->renderer_, message, buffer,
......@@ -546,7 +663,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
Rcode::FORMERR(), tsig_context);
} else {
ConstQuestionPtr question = *message.beginQuestion();
const RRType &qtype = question->getType();
const RRType& qtype = question->getType();
if (qtype == RRType::AXFR()) {
send_answer = impl_->processXfrQuery(io_message, message,
buffer, tsig_context);
......@@ -754,6 +871,15 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
return (true);
}
bool
AuthSrvImpl::processUpdate(const IOMessage& io_message) {
// Push the update request to a separate process via the forwarder.
// On successful push, the request shouldn't be responded from b10-auth,
// so we return false.
ddns_forwarder_.push(io_message);
return (false);
}
void
AuthSrvImpl::incCounter(const int protocol) {
// Increment query counter.
......
......@@ -37,6 +37,14 @@
#include <auth/statistics.h>
namespace isc {
namespace util {
namespace io {
class BaseSocketSessionForwarder;
}
}
namespace datasrc {
class InMemoryClient;
}
namespace xfr {
class AbstractXfroutClient;
}
......@@ -90,7 +98,8 @@ public:
/// but can refer to a local mock object for testing (or other
/// experimental) purposes.
AuthSrv(const bool use_cache,
isc::xfr::AbstractXfroutClient& xfrout_client);
isc::xfr::AbstractXfroutClient& xfrout_client,
isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
~AuthSrv();
//@}
......
......@@ -31,9 +31,10 @@
#include <dns/rrclass.h>
#include <log/logger_support.h>
#include <xfr/xfrout_client.h>
#include <util/unittests/mock_socketsession.h>
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/query.h>
......@@ -48,6 +49,7 @@ using namespace isc::auth;
using namespace isc::dns;
using namespace isc::log;
using namespace isc::util;
using namespace isc::util::unittests;
using namespace isc::xfr;
using namespace isc::bench;
using namespace isc::asiodns;
......@@ -78,7 +80,7 @@ protected:
QueryBenchMark(const bool enable_cache,
const BenchQueries& queries, Message& query_message,
OutputBuffer& buffer) :
server_(new AuthSrv(enable_cache, xfrout_client)),
server_(new AuthSrv(enable_cache, xfrout_client, ddns_forwarder)),
queries_(queries),
query_message_(query_message),
buffer_(buffer),
......@@ -103,6 +105,8 @@ public:
return (queries_.size());
}
private:
MockSocketSessionForwarder ddns_forwarder;
protected:
AuthSrvPtr server_;
private:
......
......@@ -33,7 +33,25 @@ getXfroutSocketPath() {
if (getenv("BIND10_XFROUT_SOCKET_FILE") != NULL) {
return (getenv("BIND10_XFROUT_SOCKET_FILE"));
} else {
return (UNIX_SOCKET_FILE);
return (UNIX_XFROUT_SOCKET_FILE);
}
}
}
string
getDDNSSocketPath() {
if (getenv("B10_FROM_BUILD") != NULL) {
if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR") != NULL) {
return (string(getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) +
"/ddns_socket");
} else {
return (string(getenv("B10_FROM_BUILD")) + "/ddns_socket");
}
} else {
if (getenv("BIND10_DDNS_SOCKET_FILE") != NULL) {
return (getenv("BIND10_DDNS_SOCKET_FILE"));
} else {
return (UNIX_DDNS_SOCKET_FILE);
}
}
}
......
......@@ -38,6 +38,20 @@ public:
/// The logic should be the same as in b10-xfrout, so they find each other.
std::string getXfroutSocketPath();
/// \brief Get the path of socket to talk to ddns
///
/// It takes some environment variables into account (B10_FROM_BUILD,
/// B10_FROM_SOURCE_LOCALSTATEDIR and BIND10_DDNS_SOCKET_FILE). It
/// also considers the installation prefix.
///
/// The logic should be the same as in b10-ddns, so they find each other.
///
/// Note: eventually we should find a better way so that we don't have to
/// repeat the same magic value (and how to tweak it with some magic
/// environment variable) twice, at which point this function may be able
/// to be deprecated.
std::string getDDNSSocketPath();
/// \brief The name used when identifieng the process
///
/// This is currently b10-auth, but it can be changed easily in one place.
......
......@@ -28,6 +28,7 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <util/io/socketsession.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
......@@ -60,6 +61,7 @@ using namespace isc::data;
using namespace isc::dns;
using namespace isc::log;
using namespace isc::util;
using namespace isc::util::io;
using namespace isc::xfr;
namespace {
......@@ -130,6 +132,7 @@ main(int argc, char* argv[]) {
bool statistics_session_established = false; // XXX (see Trac #287)
ModuleCCSession* config_session = NULL;
XfroutClient xfrout_client(getXfroutSocketPath());
SocketSessionForwarder ddns_forwarder(getDDNSSocketPath());
try {
string specfile;
if (getenv("B10_FROM_BUILD")) {
......@@ -139,7 +142,7 @@ main(int argc, char* argv[]) {
specfile = string(AUTH_SPECFILE_LOCATION);
}
auth_server = new AuthSrv(cache, xfrout_client);
auth_server = new AuthSrv(cache, xfrout_client, ddns_forwarder);
LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
SimpleCallback* checkin = auth_server->getCheckinProvider();
......
......@@ -13,4 +13,5 @@
// PERFORMANCE OF THIS SOFTWARE.
#define AUTH_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/auth.spec"
#define UNIX_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
#define UNIX_XFROUT_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/auth_xfrout_conn"
#define UNIX_DDNS_SOCKET_FILE "@@LOCALSTATEDIR@@/@PACKAGE@/ddns_socket"
......@@ -14,12 +14,7 @@
#include <config.h>
#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <util/io/sockaddr_util.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
......@@ -39,6 +34,7 @@
#include <auth/common.h>
#include <auth/statistics.h>
#include <util/unittests/mock_socketsession.h>
#include <dns/tests/unittest_util.h>
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
......@@ -46,10 +42,24 @@
#include <testutils/portconfig.h>
#include <testutils/socket_request.h>
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <vector>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
using namespace std;
using namespace isc::cc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::util::io::internal;
using namespace isc::util::unittests;
using namespace isc::dns::rdata;
using namespace isc::data;
using namespace isc::xfr;
......@@ -58,6 +68,7 @@ using namespace isc::asiolink;
using namespace isc::testutils;
using namespace isc::server_common::portconfig;
using isc::UnitTestUtil;
using boost::scoped_ptr;
namespace {
const char* const CONFIG_TESTDB =
......@@ -78,7 +89,7 @@ class AuthSrvTest : public SrvTestBase {
protected:
AuthSrvTest() :
dnss_(),
server(true, xfrout),
server(true, xfrout, ddns_forwarder),
rrclass(RRClass::IN()),
// The empty string is expected value of the parameter of
// requestSocket, not the app_name (there's no fallback, it checks
......@@ -144,9 +155,30 @@ protected:
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
// Convenient shortcut of creating a simple request and having the
// server process it.
void createAndSendRequest(RRType req_type, Opcode opcode = Opcode::QUERY(),
const Name& req_name = Name("example.com"),
RRClass req_class = RRClass::IN(),
int protocol = IPPROTO_UDP,
const char* const remote_address =
DEFAULT_REMOTE_ADDRESS,
uint16_t remote_port = DEFAULT_REMOTE_PORT)
{
UnitTestUtil::createRequestMessage(request_message, opcode,
default_qid, req_name,
req_class, req_type);
createRequestPacket(request_message, protocol, NULL,
remote_address, remote_port);
parse_message->clear(Message::PARSE);
server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
}
MockDNSService dnss_;
MockSession statistics_session;
MockXfroutClient xfrout;
MockSocketSessionForwarder ddns_forwarder;
AuthSrv server;
const RRClass rrclass;
vector<uint8_t> response_data;
......@@ -254,8 +286,8 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
// Unsupported requests. Should result in NOTIMP.
TEST_F(AuthSrvTest, unsupportedRequest) {
unsupportedRequest();
// unsupportedRequest tries 14 different opcodes
checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 14);
// unsupportedRequest tries 13 different opcodes
checkAllRcodeCountersZeroExcept(Rcode::NOTIMP(), 13);
}
// Multiple questions. Should result in FORMERR.
......@@ -1488,4 +1520,128 @@ TEST_F(AuthSrvTest,
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
//
// DDNS related tests
//
// Helper subroutine to check if the given socket address has the expected
// address and port. It depends on specific output of getnameinfo() (while
// there can be multiple textual representation of the same address) but
// in practice it should be reliable.
void
checkAddrPort(const struct sockaddr& actual_sa,
const string& expected_addr, uint16_t expected_port)
{
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
sizeof(hbuf), sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (error != 0) {
isc_throw(isc::Unexpected, "getnameinfo failed: " <<
gai_strerror(error));
}
EXPECT_EQ(expected_addr, hbuf);
EXPECT_EQ(boost::lexical_cast<string>(expected_port), sbuf);
}
TEST_F(AuthSrvTest, DDNSForward) {
EXPECT_FALSE(ddns_forwarder.isConnected());
// Repeat sending an update request 4 times, differing some network
// parameters: UDP/IPv4, TCP/IPv4, UDP/IPv6, TCP/IPv6, in this order.
// By doing that we can also confirm the forwarder connection will be
// established exactly once, and kept established.
for (size_t i = 0; i < 4; ++i) {
// Use different names for some different cases
const Name zone_name = Name(i < 2 ? "example.com" : "example.org");
const socklen_t family = (i < 2) ? AF_INET : AF_INET6;
const char* const remote_addr =
(family == AF_INET) ? "192.0.2.1" : "2001:db8::1";
const uint16_t remote_port =
(family == AF_INET) ? 53214 : 53216;
const int protocol = ((i % 2) == 0) ? IPPROTO_UDP : IPPROTO_TCP;
createAndSendRequest(RRType::SOA(), Opcode::UPDATE(), zone_name,
RRClass::IN(), protocol, remote_addr,
remote_port);
EXPECT_FALSE(dnsserv.hasAnswer());
EXPECT_TRUE(ddns_forwarder.isConnected());
// Examine the pushed data (note: currently "local end" has a dummy
// value equal to remote)
EXPECT_EQ(family, ddns_forwarder.getPushedFamily());
const int expected_type =
(protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
EXPECT_EQ(expected_type, ddns_forwarder.getPushedType());
EXPECT_EQ(protocol, ddns_forwarder.getPushedProtocol());
checkAddrPort(ddns_forwarder.getPushedRemoteend(),
remote_addr, remote_port);
checkAddrPort(ddns_forwarder.getPushedLocalend(),
remote_addr, remote_port);
EXPECT_EQ(io_message->getDataSize(),
ddns_forwarder.getPushedData().size());
EXPECT_EQ(0, memcmp(io_message->getData(),
&ddns_forwarder.getPushedData()[0],
ddns_forwarder.getPushedData().size()));
}
}
TEST_F(AuthSrvTest, DDNSForwardConnectFail) {
// make connect attempt fail. It should result in SERVFAIL. Note that
// the question (zone) section should be cleared for opcode of update.
ddns_forwarder.disableConnect();
createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
EXPECT_TRUE(dnsserv.hasAnswer());
headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
EXPECT_FALSE(ddns_forwarder.isConnected());
// Now make connect okay again. Despite the previous failure the new
// connection should now be established.
ddns_forwarder.enableConnect();
createAndSendRequest(RRType::SOA(), Opcode::UPDATE());
EXPECT_FALSE(dnsserv.hasAnswer());
EXPECT_TRUE(ddns_forwarder.isConnected());
}