Commit 555c5766 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge #1599

parents 04f9d8eb 3b889283
......@@ -24,6 +24,7 @@ libasiodns_la_SOURCES += dns_server.h
libasiodns_la_SOURCES += dns_service.cc dns_service.h
libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
libasiodns_la_SOURCES += udp_server.cc udp_server.h
libasiodns_la_SOURCES += sync_udp_server.cc sync_udp_server.h
libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
libasiodns_la_SOURCES += logger.h logger.cc
......
......@@ -88,22 +88,6 @@ public:
/// to return.
virtual void resume(const bool done) { self_->resume(done); }
/// \brief Indicate whether the server is able to send an answer
/// to a query.
///
/// This is presently used only for testing purposes.
virtual bool hasAnswer() { return (self_->hasAnswer()); }
/// \brief Returns the current value of the 'coroutine' object
///
/// This is a temporary method, intended to be used for debugging
/// purposes during development and removed later. It allows
/// callers from outside the coroutine object to retrieve information
/// about its current state.
///
/// \return The value of the 'coroutine' object
virtual int value() { return (self_->value()); }
/// \brief Returns a pointer to a clone of this DNSServer object.
///
/// When a \c DNSServer object is copied or assigned, the result will
......
// Copyright (C) 2012 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 <config.h>
#include <asio.hpp>
#include <asio/error.hpp>
#include "sync_udp_server.h"
#include "logger.h"
#include <asiolink/dummy_io_cb.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
#include <boost/bind.hpp>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
#include <errno.h>
using namespace std;
using namespace boost;
using namespace isc::asiolink;
namespace isc {
namespace asiodns {
SyncUDPServer::SyncUDPServer(asio::io_service& io_service,
const asio::ip::address& addr,
const uint16_t port,
asiolink::SimpleCallback* checkin,
DNSLookup* lookup, DNSAnswer* answer) :
output_buffer_(new isc::util::OutputBuffer(0)),
query_(new isc::dns::Message(isc::dns::Message::PARSE)),
answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
io_(io_service), checkin_callback_(checkin), lookup_callback_(lookup),
answer_callback_(answer), stopped_(false)
{
// We must use different instantiations for v4 and v6;
// otherwise ASIO will bind to both
asio::ip::udp proto = addr.is_v4() ? asio::ip::udp::v4() :
asio::ip::udp::v6();
socket_.reset(new asio::ip::udp::socket(io_service, proto));
socket_->set_option(asio::socket_base::reuse_address(true));
if (addr.is_v6()) {
socket_->set_option(asio::ip::v6_only(true));
}
socket_->bind(asio::ip::udp::endpoint(addr, port));
}
SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
const int af, asiolink::SimpleCallback* checkin,
DNSLookup* lookup, DNSAnswer* answer) :
output_buffer_(new isc::util::OutputBuffer(0)),
query_(new isc::dns::Message(isc::dns::Message::PARSE)),
answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
io_(io_service), checkin_callback_(checkin), lookup_callback_(lookup),
answer_callback_(answer), stopped_(false)
{
if (af != AF_INET && af != AF_INET6) {
isc_throw(InvalidParameter, "Address family must be either AF_INET "
"or AF_INET6, not " << af);
}
LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
try {
socket_.reset(new asio::ip::udp::socket(io_service));
socket_->assign(af == AF_INET6 ? asio::ip::udp::v6() :
asio::ip::udp::v4(), fd);
} catch (const std::exception& exception) {
// Whatever the thing throws, it is something from ASIO and we
// convert it
isc_throw(IOError, exception.what());
}
}
void
SyncUDPServer::scheduleRead() {
socket_->async_receive_from(asio::buffer(data_, MAX_LENGTH), sender_,
boost::bind(&SyncUDPServer::handleRead, this,
_1, _2));
}
void
SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
// Abort on fatal errors
if (ec) {
using namespace asio::error;
if (ec.value() != would_block && ec.value() != try_again &&
ec.value() != interrupted) {
return;
}
}
// Some kind of interrupt, spurious wakeup, or like that. Just try reading
// again.
if (ec || length == 0) {
scheduleRead();
return;
}
// OK, we have a real packet of data. Let's dig into it!
// XXX: This is taken (and ported) from UDPSocket class. What the hell does
// it really mean?
// The UDP socket class has been extended with asynchronous functions
// and takes as a template parameter a completion callback class. As
// UDPServer does not use these extended functions (only those defined
// in the IOSocket base class) - but needs a UDPSocket to get hold of
// the underlying Boost UDP socket - DummyIOCallback is used. This
// provides the appropriate operator() but is otherwise functionless.
UDPSocket<DummyIOCallback> socket(*socket_);
UDPEndpoint endpoint(sender_);
IOMessage message(data_, length, socket, endpoint);
if (checkin_callback_ != NULL) {
(*checkin_callback_)(message);
if (stopped_) {
return;
}
}
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (lookup_callback_ == NULL) {
scheduleRead();
return;
}
// Make sure the buffers are fresh
output_buffer_->clear();
query_->clear(isc::dns::Message::PARSE);
answer_->clear(isc::dns::Message::RENDER);
// Mark that we don't have an answer yet.
done_ = false;
resume_called_ = false;
// Call the actual lookup
(*lookup_callback_)(message, query_, answer_, output_buffer_, this);
if (!resume_called_) {
isc_throw(isc::Unexpected,
"No resume called from the lookup callback");
}
if (stopped_) {
return;
}
if (done_) {
// Good, there's an answer.
// Call the answer callback to render it.
(*answer_callback_)(message, query_, answer_, output_buffer_);
if (stopped_) {
return;
}
socket_->send_to(asio::buffer(output_buffer_->getData(),
output_buffer_->getLength()),
sender_);
}
// And schedule handling another socket.
scheduleRead();
}
void
SyncUDPServer::operator()(asio::error_code, size_t) {
// To start the server, we just schedule reading of data when they
// arrive.
scheduleRead();
}
/// Stop the UDPServer
void
SyncUDPServer::stop() {
/// Using close instead of cancel, because cancel
/// will only cancel the asynchornized event already submitted
/// to io service, the events post to io service after
/// cancel still can be scheduled by io service, if
/// the socket is cloesed, all the asynchronized event
/// for it won't be scheduled by io service not matter it is
/// submit to io serice before or after close call. And we will
//. get bad_descriptor error
socket_->close();
stopped_ = true;
}
/// Post this coroutine on the ASIO service queue so that it will
/// resume processing where it left off. The 'done' parameter indicates
/// whether there is an answer to return to the client.
void
SyncUDPServer::resume(const bool done) {
resume_called_ = true;
done_ = done;
}
bool
SyncUDPServer::hasAnswer() {
return (done_);
}
} // namespace asiodns
} // namespace isc
// Copyright (C) 2012 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.
#ifndef __SYNC_UDP_SERVER_H
#define __SYNC_UDP_SERVER_H 1
#ifndef ASIO_HPP
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
#include "dns_answer.h"
#include "dns_lookup.h"
#include "dns_server.h"
#include <dns/message.h>
#include <asiolink/simple_callback.h>
#include <util/buffer.h>
#include <exceptions.h>
#include <boost/noncopyable.hpp>
namespace isc {
namespace asiodns {
/// \brief An UDP server that doesn't asynchronous lookup handlers.
///
/// That means, the lookup handler must provide the answer right away.
/// This allows for implementation with less overhead, compared with
/// the UDPClass.
class SyncUDPServer : public DNSServer, public boost::noncopyable {
public:
/// \brief Constructor
/// \param io_service the asio::io_service to work with
/// \param addr the IP address to listen for queries on
/// \param port the port to listen for queries on
/// \param checkin the callbackprovider for non-DNS events
/// \param lookup the callbackprovider for DNS lookup events
/// \param answer the callbackprovider for DNS answer events
explicit SyncUDPServer(asio::io_service& io_service,
const asio::ip::address& addr, const uint16_t port,
isc::asiolink::SimpleCallback* checkin = NULL,
DNSLookup* lookup = NULL,
DNSAnswer* answer = NULL);
/// \brief Constructor
/// \param io_service the asio::io_service to work with
/// \param fd the file descriptor of opened UDP socket
/// \param af address family, either AF_INET or AF_INET6
/// \param checkin the callbackprovider for non-DNS events
/// \param lookup the callbackprovider for DNS lookup events
/// \param answer the callbackprovider for DNS answer events
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor.
SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
isc::asiolink::SimpleCallback* checkin = NULL,
DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
/// \brief Start the SyncUDPServer.
///
/// This is the function operator to keep interface with other server
/// classes. They need that because they're coroutines.
virtual void operator()(asio::error_code ec = asio::error_code(),
size_t length = 0);
/// \brief Calls the lookup callback
virtual void asyncLookup() {
isc_throw(Unexpected,
"SyncUDPServer doesn't support asyncLookup by design, use "
"UDPServer if you need it.");
}
/// \brief Stop the running server
/// \note once the server stopped, it can't restart
virtual void stop();
/// \brief Resume operation
///
/// Note that unlike other servers, this one expects it to be called
/// directly from the lookup callback. If it isn't, the server will
/// throw an Unexpected exception (probably to the event loop, which
/// would usually lead to termination of the program, but that's OK,
/// as it would be serious programmer error).
///
/// \param done Set this to true if the lookup action is done and
/// we have an answer
virtual void resume(const bool done);
/// \brief Check if we have an answer
///
/// \return true if we have an answer
virtual bool hasAnswer();
/// \brief Clones the object
///
/// Since cloning is for the use of coroutines, the synchronous UDP server
/// does not need to be cloned. Therefore supporting it would be needless
/// work, and trying to clone it would be a programmer error anyway, this
/// throws Unexpected.
///
/// \return a newly allocated copy of this object
virtual DNSServer* clone() {
isc_throw(Unexpected, "SyncUDPServer can't be cloned.");
}
private:
// Internal state & buffers. We don't use the PIMPL idiom, as this class
// isn't usually used directly anyway.
// Maximum size of incoming UDP packet
static const size_t MAX_LENGTH = 4096;
// Buffer for incoming data
uint8_t data_[MAX_LENGTH];
// The buffer to render the output to and send it.
// If it was OK to have just a buffer, not the wrapper class,
// we could reuse the data_
isc::util::OutputBufferPtr output_buffer_;
// Objects to hold the query message and the answer
isc::dns::MessagePtr query_, answer_;
// The socket used for the communication
std::auto_ptr<asio::ip::udp::socket> socket_;
// The event loop we use
asio::io_service& io_;
// Place the socket puts the sender of a packet when it is received
asio::ip::udp::endpoint sender_;
// Callbacks
const asiolink::SimpleCallback* checkin_callback_;
const DNSLookup* lookup_callback_;
const DNSAnswer* answer_callback_;
// Answers from the lookup callback (not sent directly, but signalled
// through resume()
bool resume_called_, done_;
// This turns true when the server stops. Allows for not sending the
// answer after we closed the socket.
bool stopped_;
// Auxiliary functions
// Schedule next read on the socket. Just a wrapper around
// socket_->async_read_from with the correct parameters.
void scheduleRead();
// Callback from the socket's read call (called when there's an error or
// when a new packet comes).
void handleRead(const asio::error_code& ec, const size_t length);
};
} // namespace asiodns
} // namespace isc
#endif // __SYNC_UDP_SERVER_H
......@@ -62,9 +62,6 @@ public:
void asyncLookup();
void stop();
void resume(const bool done);
bool hasAnswer() { return (done_); }
int value() { return (get_value()); }
DNSServer* clone() {
TCPServer* s = new TCPServer(*this);
return (s);
......
......@@ -19,6 +19,7 @@
#include <asiolink/io_endpoint.h>
#include <asiolink/io_error.h>
#include <asiodns/udp_server.h>
#include <asiodns/sync_udp_server.h>
#include <asiodns/tcp_server.h>
#include <asiodns/dns_answer.h>
#include <asiodns/dns_lookup.h>
......@@ -112,15 +113,22 @@ class DummyChecker : public SimpleCallback, public ServerStopper {
// \brief no lookup logic at all,just provide a checkpoint to stop the server
class DummyLookup : public DNSLookup, public ServerStopper {
public:
void operator()(const IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::MessagePtr answer_message,
isc::util::OutputBufferPtr buffer,
DNSServer* server) const {
stopServer();
public:
DummyLookup() :
allow_resume_(true)
{ }
void operator()(const IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::MessagePtr answer_message,
isc::util::OutputBufferPtr buffer,
DNSServer* server) const {
stopServer();
if (allow_resume_) {
server->resume(true);
}
}
// If you want it not to call resume, set this to false
bool allow_resume_;
};
// \brief copy the data received from user to the answer part
......@@ -314,10 +322,11 @@ class TCPClient : public SimpleClient {
// two servers, UDP client will only communicate with UDP server, same for TCP
// client
//
// This is only the active part of the test. We run the test case twice, once
// This is only the active part of the test. We run the test case four times, once
// for each type of initialization (once when giving it the address and port,
// once when giving the file descriptor), to ensure it works both ways exactly
// the same.
// once when giving the file descriptor) multiplied by once for each type of UDP
// server (UDPServer and SyncUDPServer), to ensure it works exactly the same.
template<class UDPServerClass>
class DNSServerTestBase : public::testing::Test {
protected:
DNSServerTestBase() :
......@@ -396,7 +405,7 @@ class DNSServerTestBase : public::testing::Test {
SimpleAnswer* const answer_;
UDPClient* const udp_client_;
TCPClient* const tcp_client_;
UDPServer* udp_server_;
UDPServerClass* udp_server_;
TCPServer* tcp_server_;
// To access them in signal handle function, the following
......@@ -406,18 +415,23 @@ class DNSServerTestBase : public::testing::Test {
};
// Initialization with name and port
class AddrPortInit : public DNSServerTestBase {
template<class UDPServerClass>
class AddrPortInit : public DNSServerTestBase<UDPServerClass> {
protected:
AddrPortInit() {
udp_server_ = new UDPServer(service, server_address_, server_port,
checker_, lookup_, answer_);
tcp_server_ = new TCPServer(service, server_address_, server_port,
checker_, lookup_, answer_);
this->udp_server_ = new UDPServerClass(this->service,
this->server_address_,
server_port, this->checker_,
this->lookup_, this->answer_);
this->tcp_server_ = new TCPServer(this->service, this->server_address_,
server_port, this->checker_,
this->lookup_, this->answer_);
}
};
// Initialization by the file descriptor
class FdInit : public DNSServerTestBase {
template<class UDPServerClass>
class FdInit : public DNSServerTestBase<UDPServerClass> {
private:
// Opens the file descriptor for us
// It uses the low-level C api, as it seems to be the easiest way to get
......@@ -465,12 +479,14 @@ protected:
void SetUp() {
const int fdUDP(getFd(SOCK_DGRAM));
ASSERT_NE(-1, fdUDP) << strerror(errno);
udp_server_ = new UDPServer(service, fdUDP, AF_INET6, checker_,
lookup_, answer_);
this->udp_server_ = new UDPServerClass(this->service, fdUDP, AF_INET6,
this->checker_, this->lookup_,
this->answer_);
const int fdTCP(getFd(SOCK_STREAM));
ASSERT_NE(-1, fdTCP) << strerror(errno);
tcp_server_ = new TCPServer(service, fdTCP, AF_INET6, checker_,
lookup_, answer_);
this->tcp_server_ = new TCPServer(this->service, fdTCP, AF_INET6,
this->checker_, this->lookup_,
this->answer_);
}
};
......@@ -478,11 +494,24 @@ protected:
template<class Parent>
class DNSServerTest : public Parent { };
typedef ::testing::Types<AddrPortInit, FdInit> ServerTypes;
typedef ::testing::Types<AddrPortInit<UDPServer>, AddrPortInit<SyncUDPServer>,
FdInit<UDPServer>, FdInit<SyncUDPServer> >
ServerTypes;
TYPED_TEST_CASE(DNSServerTest, ServerTypes);
bool DNSServerTestBase::io_service_is_time_out = false;
asio::io_service* DNSServerTestBase::current_service(NULL);
typedef ::testing::Types<UDPServer, SyncUDPServer> UDPServerTypes;
TYPED_TEST_CASE(DNSServerTestBase, UDPServerTypes);
template<class UDPServerClass>
bool DNSServerTestBase<UDPServerClass>::io_service_is_time_out = false;
template<class UDPServerClass>
asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
typedef ::testing::Types<AddrPortInit<SyncUDPServer>, FdInit<SyncUDPServer> >
SyncTypes;
template<class Parent>
class SyncServerTest : public Parent { };
TYPED_TEST_CASE(SyncServerTest, SyncTypes);
// Test whether server stopped successfully after client get response
// client will send query and start to wait for response, once client
......@@ -608,17 +637,20 @@ TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
}
// It raises an exception when invalid address family is passed
TEST_F(DNSServerTestBase, invalidFamily) {
// The parameter here doesn't mean anything
TYPED_TEST(DNSServerTestBase, invalidFamily) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
EXPECT_THROW(UDPServer(service, 0, AF_UNIX, checker_, lookup_,
answer_), isc::InvalidParameter);
EXPECT_THROW(TCPServer(service, 0, AF_UNIX, checker_, lookup_,
answer_), isc::InvalidParameter);
EXPECT_THROW(TypeParam(this->service, 0, AF_UNIX, this->checker_,
this->lookup_, this->answer_),
isc::InvalidParameter);
EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
this->lookup_, this->answer_),
isc::InvalidParameter);
}
// It raises an exception when invalid address family is passed
TEST_F(DNSServerTestBase, invalidTCPFD) {
TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
/*
......@@ -630,11 +662,12 @@ TEST_F(DNSServerTestBase, invalidTCPFD) {
EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
answer_), isc::asiolink::IOError);
*/
EXPECT_THROW(TCPServer(service, -1, AF_INET, checker_, lookup_,
answer_), isc::asiolink::IOError);
EXPECT_THROW(TCPServer(this->service, -1, AF_INET, this->checker_,
this->lookup_, this->answer_),
isc::asiolink::IOError);
}
TEST_F(DNSServerTestBase, DISABLED_invalidUDPFD) {
TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
/*
FIXME: The UDP server doesn't fail reliably with an invalid FD.
We need to find a way to trigger it reliably (it seems epoll
......@@ -642,8 +675,24 @@ TEST_F(DNSServerTestBase, DISABLED_invalidUDPFD) {
not the others, maybe we could make it run this at least on epoll-based
systems).
*/
EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
answer_), isc::asiolink::IOError);
EXPECT_THROW(TypeParam(this->service, -1, AF_INET, this->checker_,
this->lookup_, this->answer_),
isc::asiolink::IOError);
}
// Check it rejects some of the unsupported operatirons
TYPED_TEST(SyncServerTest, unsupportedOps) {
EXPECT_THROW(this->udp_server_->clone(), isc::Unexpected);
EXPECT_THROW(this->udp_server_->asyncLookup(), isc::Unexpected);
}