Commit f683ad20 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[3008] Initial implementation of classes for sending and

receiving NameChangeRequests for use with DHCP-DDNS. This includes
abstract listener and sender classes, as well as a derivations
supporting traffic over UDP sockets.

New files added to src/bin/d2

ncr_io.h - base classes
ncr_io.cc

ncr_udp.h - UDP derivations
ncr_udp.cc
tests/ncr_udp_unittests.cc
parent dd01c785
......@@ -57,7 +57,9 @@ b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
b10_dhcp_ddns_SOURCES += ncr_io.cc ncr_io.h
b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
b10_dhcp_ddns_SOURCES += ncr_udp.cc ncr_udp.h
nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
EXTRA_DIST += d2_messages.mes
......
......@@ -17,12 +17,6 @@
#include <boost/foreach.hpp>
using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::asiolink;
namespace isc {
namespace d2 {
......@@ -102,12 +96,14 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
D2CfgContextPtr context = getD2CfgContext();
// Create parser instance based on element_id.
DhcpConfigParser* parser = NULL;
isc::dhcp::DhcpConfigParser* parser = NULL;
if ((config_id == "interface") ||
(config_id == "ip_address")) {
parser = new StringParser(config_id, context->getStringStorage());
parser = new isc::dhcp::StringParser(config_id,
context->getStringStorage());
} else if (config_id == "port") {
parser = new Uint32Parser(config_id, context->getUint32Storage());
parser = new isc::dhcp::Uint32Parser(config_id,
context->getUint32Storage());
} else if (config_id == "forward_ddns") {
parser = new DdnsDomainListMgrParser("forward_mgr",
context->getForwardMgr(),
......
......@@ -70,7 +70,7 @@ application when it is not running.
% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
An error message which indicates that configuration being parsed includes
element ids not specified the configuration manager's parse order list. This
element ids not specified the configuration manager's parse order list. This
is a programmatic error.
% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
......@@ -78,7 +78,7 @@ An error message output during a configuration update. The program is
expecting an item but has not found it in the new configuration. This may
mean that the BIND 10 configuration database is corrupt.
% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
On receipt of message containing details to a change of its configuration,
the server failed to create a parser to decode the contents of the named
configuration element, or the creation succeeded but the parsing actions
......@@ -124,16 +124,40 @@ has been invoked.
This is a debug message issued when the Dhcp-Ddns application encounters an
unrecoverable error from within the event loop.
% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
This is an error message that indicates that an invalid request to update
a DNS entry was recevied by the application. Either the format or the content
of the request is incorret. The request will be ignored.
% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
This is a debug message issued when the DHCP-DDNS application encountered an
error while decoding a response to DNS Update message. Typically, this error
will be encountered when a response message is malformed.
% DHCP_DDNS_NCR_UDP_LISTEN_CLOSE application encountered an error while closing the UDP socket used to receive NameChangeRequests : %1
This is an error message that indicates the application was unable to close the
UDP socket being used to receive NameChangeRequests. Socket closure may occur
during the course of error recovery or duing normal shutdown procedure. In
either case the error is unlikely to impair the application's ability to
process requests but it should be reported for analysis.
% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
This is an error message indicating that an IO error occured while listening
over a UDP socket for DNS update requests. in the request. This could indicate
a network connectivity or system resource issue.
% DHCP_DDNS_NCR_UDP_SEND_CLOSE application encountered an error while closing the UDP socket used to send NameChangeRequests : %1
This is an error message that indicates the DHCP-DDNS client was unable to close
the UDP socket being used to send NameChangeRequests. Socket closure may occur
during the course of error recovery or duing normal shutdown procedure. In
either case the error is unlikely to impair the client's ability to send
requests but it should be reported for analysis.
% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
This is warning message issued when there are no domains in the configuration
which match the cited fully qualified domain name (FQDN). The DNS Update
which match the cited fully qualified domain name (FQDN). The DNS Update
request for the FQDN cannot be processed.
% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
This is a debug message issued when the DHCP-DDNS application encountered an error
while decoding a response to DNS Update message. Typically, this error will be
encountered when a response message is malformed.
% DHCP_DDNS_PROCESS_INIT application init invoked
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
......
// Copyright (C) 2013 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 <d2/d2_log.h>
#include <d2/ncr_io.h>
namespace isc {
namespace d2 {
//************************** NameChangeListener ***************************
NameChangeListener::NameChangeListener(const RequestReceiveHandler*
recv_handler)
: listening_(false), recv_handler_(recv_handler) {
if (!recv_handler) {
isc_throw(NcrListenerError,
"NameChangeListener ctor - recv_handler cannot be null");
}
};
void
NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
if (amListening()) {
// This amounts to a programmatic error.
isc_throw(NcrListenerError, "NameChangeListener is already listening");
}
// Call implementation dependent open.
try {
open(io_service);
} catch (const isc::Exception& ex) {
stopListening();
isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
}
// Set our status to listening.
setListening(true);
// Start the first asynchronous receive.
try {
doReceive();
} catch (const isc::Exception& ex) {
stopListening();
isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
}
}
void
NameChangeListener::stopListening() {
try {
// Call implementation dependent close.
close();
} catch (const isc::Exception &ex) {
// Swallow exceptions. If we have some sort of error we'll log
// it but we won't propagate the throw.
LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_LISTEN_CLOSE).arg(ex.what());
}
setListening(false);
}
void
NameChangeListener::invokeRecvHandler(Result result, NameChangeRequestPtr ncr) {
// Call the registered application layer handler.
(*(const_cast<RequestReceiveHandler*>(recv_handler_)))(result, ncr);
// Start the next IO layer asynchronous receive.
doReceive();
};
//************************* NameChangeSender ******************************
NameChangeSender::NameChangeSender(const RequestSendHandler* send_handler,
size_t send_que_max)
: sending_(false), send_handler_(send_handler),
send_que_max_(send_que_max) {
if (!send_handler) {
isc_throw(NcrSenderError,
"NameChangeSender ctor - send_handler cannot be null");
}
// Queue size must be big enough to hold at least 1 entry.
if (send_que_max == 0) {
isc_throw(NcrSenderError, "NameChangeSender ctor -"
" queue size must be greater than zero");
}
};
void
NameChangeSender::startSending(isc::asiolink::IOService & io_service) {
if (amSending()) {
// This amounts to a programmatic error.
isc_throw(NcrSenderError, "NameChangeSender is already sending");
}
// Clear send marker.
ncr_to_send_.reset();
// Call implementation dependent open.
try {
open(io_service);
} catch (const isc::Exception& ex) {
stopSending();
isc_throw(NcrSenderOpenError, "Open failed:" << ex.what());
}
// Set our status to sending.
setSending(true);
}
void
NameChangeSender::stopSending() {
try {
// Call implementation dependent close.
close();
} catch (const isc::Exception &ex) {
// Swallow exceptions. If we have some sort of error we'll log
// it but we won't propagate the throw.
LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_SEND_CLOSE).arg(ex.what());
}
setSending(false);
}
void
NameChangeSender::sendRequest(NameChangeRequestPtr ncr) {
if (!amSending()) {
isc_throw(NcrSenderError, "sender is not ready to send");
}
if (!ncr) {
isc_throw(NcrSenderError, "request to send is empty");
}
if (send_que_.size() >= send_que_max_) {
isc_throw(NcrSenderQueFull, "send queue has reached maximum capacity:"
<< send_que_max_ );
}
// Put it on the queue.
send_que_.push_back(ncr);
// Call sendNext to schedule the next one to go.
sendNext();
}
void
NameChangeSender::sendNext() {
if (ncr_to_send_) {
// @todo Not sure if there is any risk of getting stuck here but
// an interval timer to defend would be good.
// In reality, the derivation should ensure they timeout themselves
return;
}
// If queue isn't empty, then get one from the front. Note we leave
// it on the front of the queue until we successfully send it.
if (send_que_.size()) {
ncr_to_send_ = send_que_.front();
// @todo start defense timer
// If a send were to hang and we timed it out, then timeout
// handler need to cycle thru open/close ?
// Call implementation dependent send.
doSend(ncr_to_send_);
}
}
void
NameChangeSender::invokeSendHandler(NameChangeSender::Result result) {
// @todo reset defense timer
if (result == SUCCESS) {
// It shipped so pull it off the queue.
send_que_.pop_front();
}
// Invoke the completion handler passing in the result and a pointer
// the request involved.
(*(const_cast<RequestSendHandler*>(send_handler_))) (result, ncr_to_send_);
// Clear the pending ncr pointer.
ncr_to_send_.reset();
// Set up the next send
sendNext();
};
void
NameChangeSender::skipNext() {
if (send_que_.size()) {
// Discards the request at the front of the queue.
send_que_.pop_front();
}
}
void
NameChangeSender::flushSendQue() {
if (amSending()) {
isc_throw(NcrSenderError, "Cannot flush queue while sending");
}
send_que_.clear();
}
} // namespace isc::d2
} // namespace isc
This diff is collapsed.
// Copyright (C) 2013 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 <d2/d2_log.h>
#include <d2/ncr_udp.h>
#include <asio/ip/udp.hpp>
#include <asio/error_code.hpp>
#include <boost/bind.hpp>
namespace isc {
namespace d2 {
//*************************** UDPCallback ***********************
UDPCallback::UDPCallback (RawBufferPtr buffer, size_t buf_size,
UDPEndpointPtr data_source,
const UDPCompletionHandler& handler)
: handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
if (handler.empty()) {
isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
}
if (!buffer) {
isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
}
}
void
UDPCallback::operator ()(const asio::error_code error_code,
const size_t bytes_transferred) {
// Save the result state and number of bytes transferred.
setErrorCode(error_code);
setBytesTransferred(bytes_transferred);
// Invoke the NameChangeRequest layer completion handler.
// First argument is a boolean indicating success or failure.
// The second is a pointer to "this" callback object. By passing
// ourself in, we make all of the service related data available
// to the completion handler.
handler_(!error_code, this);
}
void
UDPCallback::putData(const uint8_t* src, size_t len) {
if (!src) {
isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
}
if (len > data_->buf_size_) {
isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
}
memcpy (data_->buffer_.get(), src, len);
data_->put_len_ = len;
}
//*************************** NameChangeUDPListener ***********************
NameChangeUDPListener::NameChangeUDPListener(
const isc::asiolink::IOAddress& ip_address, const uint32_t port,
NameChangeFormat format,
const RequestReceiveHandler* ncr_recv_handler,
const bool reuse_address)
: NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
port_(port), format_(format), reuse_address_(reuse_address) {
// Instantiate the receive callback. This gets passed into each receive.
// Note that the callback constructor is passed an instance method
// pointer to our recv_completion_handler.
recv_callback_.reset(new UDPCallback(
RawBufferPtr(new uint8_t[RECV_BUF_MAX]),
RECV_BUF_MAX,
UDPEndpointPtr(new asiolink::
UDPEndpoint()),
boost::bind(&NameChangeUDPListener::
recv_completion_handler, this,
_1, _2)));
}
NameChangeUDPListener::~NameChangeUDPListener() {
// Clean up.
stopListening();
}
void
NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
// create our endpoint and bind the the low level socket to it.
isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
// Create the low level socket.
try {
asio_socket_.reset(new asio::ip::udp::
socket(io_service.get_io_service(),
(ip_address_.isV4() ? asio::ip::udp::v4() :
asio::ip::udp::v6())));
// If in test mode, enable address reuse.
if (reuse_address_) {
asio_socket_->set_option(asio::socket_base::reuse_address(true));
}
// Bind the low leve socket to our endpoint.
asio_socket_->bind(endpoint.getASIOEndpoint());
} catch (asio::system_error& ex) {
isc_throw (NcrUDPError, ex.code().message());
}
// Create the asiolink socket from the low level socket.
socket_.reset(new NameChangeUDPSocket(*asio_socket_));
}
void
NameChangeUDPListener::doReceive() {
// Call the socket's asychronous receiving, passing ourself in as callback.
RawBufferPtr recv_buffer = recv_callback_->getBuffer();
socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
0, recv_callback_->getDataSource().get(),
*recv_callback_);
}
void
NameChangeUDPListener::close() {
// Whether we think we are listening or not, make sure we aren't.
// Since we are managing our own socket, we need to cancel and close
// it ourselves.
if (asio_socket_) {
try {
asio_socket_->cancel();
asio_socket_->close();
} catch (asio::system_error& ex) {
// It is really unlikely that this will occur.
// If we do reopen later it will be with a new socket instance.
// Repackage exception as one that is conformant with the interface.
isc_throw (NcrUDPError, ex.code().message());
}
}
}
void
NameChangeUDPListener::recv_completion_handler(bool successful,
const UDPCallback *callback) {
NameChangeRequestPtr ncr;
Result result = SUCCESS;
if (successful) {
// Make an InputBuffer from our internal array
isc::util::InputBuffer input_buffer(callback->getData(),
callback->getBytesTransferred());
try {
ncr = NameChangeRequest::fromFormat(format_, input_buffer);
} catch (const NcrMessageError& ex) {
// log it and go back to listening
LOG_ERROR(dctl_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
// Queue up the next recieve.
doReceive();
return;
}
} else {
asio::error_code error_code = callback->getErrorCode();
LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
.arg(error_code.message());
result = ERROR;
}
// Call the application's registered request receive handler.
invokeRecvHandler(result, ncr);
}
//*************************** NameChangeUDPSender ***********************
NameChangeUDPSender::NameChangeUDPSender(
const isc::asiolink::IOAddress& ip_address, const uint32_t port,
const isc::asiolink::IOAddress& server_address,
const uint32_t server_port, const NameChangeFormat format,
RequestSendHandler* ncr_send_handler, const size_t send_que_max,
const bool reuse_address)
: NameChangeSender(ncr_send_handler, send_que_max),
ip_address_(ip_address), port_(port), server_address_(server_address),
server_port_(server_port), format_(format),
reuse_address_(reuse_address) {
// Instantiate the send callback. This gets passed into each send.
// Note that the callback constructor is passed the an instance method
// pointer to our send_completion_handler.
send_callback_.reset(new UDPCallback(
RawBufferPtr(new uint8_t[SEND_BUF_MAX]),
SEND_BUF_MAX,
UDPEndpointPtr(new asiolink::
UDPEndpoint()),
boost::bind(&NameChangeUDPSender::
send_completion_handler, this,
_1, _2)));
}
NameChangeUDPSender::~NameChangeUDPSender() {
// Clean up.
stopSending();
}
void
NameChangeUDPSender::open(isc::asiolink::IOService & io_service) {
// create our endpoint and bind the the low level socket to it.
isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
// Create the low level socket.
try {
asio_socket_.reset(new asio::ip::udp::
socket(io_service.get_io_service(),
(ip_address_.isV4() ? asio::ip::udp::v4() :
asio::ip::udp::v6())));
// If in test mode, enable address reuse.
if (reuse_address_) {
asio_socket_->set_option(asio::socket_base::reuse_address(true));
}
// Bind the low leve socket to our endpoint.
asio_socket_->bind(endpoint.getASIOEndpoint());
} catch (asio::system_error& ex) {
isc_throw (NcrUDPError, ex.code().message());
}
// Create the asiolink socket from the low level socket.
socket_.reset(new NameChangeUDPSocket(*asio_socket_));
// Create the server endpoint
server_endpoint_.reset(new isc::asiolink::
UDPEndpoint(server_address_.getAddress(),
server_port_));
send_callback_->setDataSource(server_endpoint_);
}
void
NameChangeUDPSender::close() {
// Whether we think we are sending or not, make sure we aren't.
// Since we are managing our own socket, we need to cancel and close
// it ourselves.
if (asio_socket_) {
try {
asio_socket_->cancel();
asio_socket_->close();
} catch (asio::system_error& ex) {
// It is really unlikely that this will occur.
// If we do reopen later it will be with a new socket instance.
// Repackage exception as one that is conformant with the interface.
isc_throw (NcrUDPError, ex.code().message());
}
}
}
void
NameChangeUDPSender::doSend(NameChangeRequestPtr ncr) {
// Now use the NCR to write JSON to an output buffer.
isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
ncr->toFormat(format_, ncr_buffer);
// Copy the wire-ized request to callback. This way we know after
// send completes what we sent (or attempted to send).
send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
ncr_buffer.getLength());
// Call the socket's asychronous send, passing our callback
socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
send_callback_->getDataSource().get(), *send_callback_);
}
void
NameChangeUDPSender::send_completion_handler(const bool successful,
const UDPCallback *send_callback) {
Result result;
if (successful) {
result = SUCCESS;
}
else {
// On a failure, log the error and set the result to ERROR.
asio::error_code error_code = send_callback->getErrorCode();
LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
.arg(error_code.message());
result = ERROR;
}
// Call the application's registered request send handler.
invokeSendHandler(result</