summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp_ddns/ncr_udp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp_ddns/ncr_udp.cc')
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.cc386
1 files changed, 386 insertions, 0 deletions
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
new file mode 100644
index 0000000..ae37793
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <stats/stats_mgr.h>
+
+#include <functional>
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp_ddns {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler)
+ : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+ if (!handler) {
+ isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+ }
+
+ if (!buffer) {
+ isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+ }
+}
+
+void
+UDPCallback::operator ()(const boost::system::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, const NameChangeFormat format,
+ 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 completion handler method, receiveCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ recv_callback_.reset(new UDPCallback(buffer, RECV_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPListener::receiveCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+ // Clean up.
+ stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::system_error& ex) {
+ asio_socket_.reset();
+ 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 asynchronous 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 close it ourselves.
+ // NOTE that if there is a pending receive, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::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());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const 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);
+ isc::stats::StatsMgr::instance().addValue("ncr-received",
+ static_cast<int64_t>(1));
+ } catch (const NcrMessageError& ex) {
+ // log it and go back to listening
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+ isc::stats::StatsMgr::instance().addValue("ncr-invalid",
+ static_cast<int64_t>(1));
+
+ // Queue up the next receive.
+ // NOTE: We must call the base class, NEVER doReceive
+ receiveNext();
+ return;
+ }
+ } else {
+ boost::system::error_code error_code = callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ // A shutdown cancels all outstanding reads. For this reason,
+ // it can be an expected event, so log it as a debug message.
+ LOG_DEBUG(dhcp_ddns_logger, isc::log::DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_NCR_UDP_RECV_CANCELED);
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+ .arg(error_code.message());
+ isc::stats::StatsMgr::instance().addValue("ncr-error",
+ static_cast<int64_t>(1));
+ 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 completion handler, sendCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPSender::sendCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+ // Clean up.
+ stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::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_, server_port_));
+
+ send_callback_->setDataSource(server_endpoint_);
+
+ closeWatchSocket();
+ watch_socket_.reset(new util::WatchSocket());
+}
+
+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 close it ourselves.
+ // NOTE that if there is a pending send, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::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());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+
+ closeWatchSocket();
+ watch_socket_.reset();
+}
+
+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 asynchronous send, passing our callback
+ socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+ send_callback_->getDataSource().get(), *send_callback_);
+
+ // Set IO ready marker so sender activity is visible to select() or poll().
+ // Note, if this call throws it will manifest itself as a throw from
+ // from sendRequest() which the application calls directly and is documented
+ // as throwing exceptions; or caught inside invokeSendHandler() which
+ // will invoke the application's send_handler with an error status.
+ watch_socket_->markReady();
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+ const UDPCallback *send_callback) {
+ // Clear the IO ready marker.
+ try {
+ watch_socket_->clearReady();
+ } catch (const std::exception& ex) {
+ // This can only happen if the WatchSocket's select_fd has been
+ // compromised which is a programmatic error. We'll log the error
+ // here, then continue on and process the IO result we were given.
+ // WatchSocket issue will resurface on the next send as a closed
+ // fd in markReady(). This allows application's handler to deal
+ // with watch errors more uniformly.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR)
+ .arg(ex.what());
+ }
+
+ Result result;
+ if (successful) {
+ result = SUCCESS;
+ } else {
+ // On a failure, log the error and set the result to ERROR.
+ boost::system::error_code error_code = send_callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_CANCELED)
+ .arg(error_code.message());
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_ERROR)
+ .arg(error_code.message());
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request send handler.
+ invokeSendHandler(result);
+}
+
+int
+NameChangeUDPSender::getSelectFd() {
+ if (!amSending()) {
+ isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
+ " not in send mode");
+ }
+
+ return(watch_socket_->getSelectFd());
+}
+
+bool
+NameChangeUDPSender::ioReady() {
+ if (watch_socket_) {
+ return (watch_socket_->isReady());
+ }
+
+ return (false);
+}
+
+void
+NameChangeUDPSender::closeWatchSocket() {
+ if (watch_socket_) {
+ std::string error_string;
+ watch_socket_->closeSocket(error_string);
+ if (!error_string.empty()) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(error_string);
+ }
+ }
+}
+
+} // end of isc::dhcp_ddns namespace
+} // end of isc namespace