diff options
Diffstat (limited to 'src/lib/dhcpsrv/d2_client_mgr.cc')
-rw-r--r-- | src/lib/dhcpsrv/d2_client_mgr.cc | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc new file mode 100644 index 0000000..807f728 --- /dev/null +++ b/src/lib/dhcpsrv/d2_client_mgr.cc @@ -0,0 +1,423 @@ +// Copyright (C) 2014-2021 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/iface_mgr.h> +#include <dhcp_ddns/ncr_udp.h> +#include <dhcpsrv/d2_client_mgr.h> +#include <dhcpsrv/dhcpsrv_log.h> + +#include <functional> +#include <string> + +using namespace std; + +namespace isc { +namespace dhcp { + +D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()), + name_change_sender_(), private_io_service_(), + registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) { + // Default constructor initializes with a disabled configuration. +} + +D2ClientMgr::~D2ClientMgr(){ + stopSender(); +} + +void +D2ClientMgr::suspendUpdates() { + if (ddnsEnabled()) { + /// @todo For now we will disable updates and stop sending. + /// This at least provides a means to shut it off if there are errors. + LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES); + d2_client_config_->enableUpdates(false); + if (name_change_sender_) { + stopSender(); + } + } +} + +void +D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) { + if (!new_config) { + isc_throw(D2ClientError, + "D2ClientMgr cannot set DHCP-DDNS configuration to NULL."); + } + + // Don't do anything unless configuration values are actually different. + if (*d2_client_config_ != *new_config) { + // Make sure we stop sending first. + stopSender(); + if (!new_config->getEnableUpdates()) { + // Updating has been turned off. + // Destroy current sender (any queued requests are tossed). + name_change_sender_.reset(); + } else { + dhcp_ddns::NameChangeSenderPtr new_sender; + switch (new_config->getNcrProtocol()) { + case dhcp_ddns::NCR_UDP: { + // Instantiate a new sender. + new_sender.reset(new dhcp_ddns::NameChangeUDPSender( + new_config->getSenderIp(), + new_config->getSenderPort(), + new_config->getServerIp(), + new_config->getServerPort(), + new_config->getNcrFormat(), + *this, + new_config->getMaxQueueSize())); + break; + } + default: + // In theory you can't get here. + isc_throw(D2ClientError, "Invalid sender Protocol: " + << new_config->getNcrProtocol()); + break; + } + + // Transfer queued requests from previous sender to the new one. + /// @todo - Should we consider anything queued to be wrong? + /// If only server values changed content might still be right but + /// if content values changed (e.g. suffix or an override flag) + /// then the queued contents might now be invalid. There is + /// no way to regenerate them if they are wrong. + if (name_change_sender_) { + new_sender->assumeQueue(*name_change_sender_); + } + + // Replace the old sender with the new one. + name_change_sender_ = new_sender; + } + } + + // Update the configuration. + d2_client_config_ = new_config; + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS) + .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" : + "DHCP_DDNS updates enabled"); +} + +bool +D2ClientMgr::ddnsEnabled() { + return (d2_client_config_->getEnableUpdates()); +} + +const D2ClientConfigPtr& +D2ClientMgr::getD2ClientConfig() const { + return (d2_client_config_); +} + +void +D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n, + bool& server_s, bool& server_n, + const DdnsParams& ddns_params) const { + // Per RFC 4702 & 4704, the client N and S flags allow the client to + // request one of three options: + // + // N flag S flag Option + // ------------------------------------------------------------------ + // 0 0 client wants to do forward updates (section 3.2) + // 0 1 client wants server to do forward updates (section 3.3) + // 1 0 client wants no one to do updates (section 3.4) + // 1 1 invalid combination + // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3) + // + // Make a bit mask from the client's flags and use it to set the response + // flags accordingly. + const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0)); + + switch (mask) { + case 0: + if (!ddns_params.getEnableUpdates()) { + server_s = false; + server_n = true; + } else { + // If updates are enabled and we are overriding client delegation + // then S flag should be true. N-flag should be false. + server_s = ddns_params.getOverrideClientUpdate(); + server_n = false; + } + break; + + case 1: + server_s = ddns_params.getEnableUpdates(); + server_n = !server_s; + break; + + case 2: + // If updates are enabled and we are overriding "no updates" then + // S flag should be true. + server_s = (ddns_params.getEnableUpdates() && + ddns_params.getOverrideNoUpdate()); + server_n = !server_s; + break; + + default: + // RFCs declare this an invalid combination. + isc_throw(isc::BadValue, + "Invalid client FQDN - N and S cannot both be 1"); + break; + } +} + +std::string +D2ClientMgr::generateFqdn(const asiolink::IOAddress& address, + const DdnsParams& ddns_params, + const bool trailing_dot) const { + std::string hostname = address.toText(); + std::replace(hostname.begin(), hostname.end(), + (address.isV4() ? '.' : ':'), '-'); + + std::ostringstream gen_name; + gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname; + return (qualifyName(gen_name.str(), ddns_params, trailing_dot)); +} + + +std::string +D2ClientMgr::qualifyName(const std::string& partial_name, + const DdnsParams& ddns_params, + const bool trailing_dot) const { + std::ostringstream gen_name; + + gen_name << partial_name; + std::string suffix = ddns_params.getQualifyingSuffix(); + bool suffix_present = true; + if (!suffix.empty()) { + std::string str = gen_name.str(); + auto suffix_rit = suffix.rbegin(); + if (*suffix_rit == '.') { + ++suffix_rit; + } + + auto gen_rit = str.rbegin(); + if (*gen_rit == '.') { + ++gen_rit; + } + + while (suffix_rit != suffix.rend()) { + if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) { + // They don't match. + suffix_present = false; + break; + } + + ++suffix_rit; + ++gen_rit; + } + + // Catch the case where name has suffix embedded. + // input: foo.barexample.com suffix: example.com + if ((suffix_present) && (suffix_rit == suffix.rend())) { + if ((gen_rit != str.rend()) && (*gen_rit != '.')) { + suffix_present = false; + } + } + + if (!suffix_present) { + size_t len = str.length(); + if ((len > 0) && (str[len - 1] != '.')) { + gen_name << "."; + } + + gen_name << suffix; + } + } + + std::string str = gen_name.str(); + size_t len = str.length(); + + if (trailing_dot) { + // If trailing dot should be added but there is no trailing dot, + // append it. + if ((len > 0) && (str[len - 1] != '.')) { + gen_name << "."; + } + + } else { + // If the trailing dot should not be appended but it is present, + // remove it. + if ((len > 0) && (str[len - 1] == '.')) { + gen_name.str(str.substr(0,len-1)); + } + + } + + return (gen_name.str()); +} + +void +D2ClientMgr::startSender(D2ClientErrorHandler error_handler) { + if (amSending()) { + return; + } + + // Create a our own service instance when we are not being multiplexed + // into an external service.. + private_io_service_.reset(new asiolink::IOService()); + startSender(error_handler, *private_io_service_); + LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STARTED) + .arg(d2_client_config_->toText()); +} + +void +D2ClientMgr::startSender(D2ClientErrorHandler error_handler, + isc::asiolink::IOService& io_service) { + if (amSending()) { + return; + } + + if (!name_change_sender_) { + isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null"); + } + + if (!error_handler) { + isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null"); + } + + // Set the error handler. + client_error_handler_ = error_handler; + + // Start the sender on the given service. + name_change_sender_->startSending(io_service); + + // Register sender's select-fd with IfaceMgr. + // We need to remember the fd that is registered so we can unregister later. + // IO error handling in the sender may alter its select-fd. + registered_select_fd_ = name_change_sender_->getSelectFd(); + IfaceMgr::instance().addExternalSocket(registered_select_fd_, + std::bind(&D2ClientMgr::runReadyIO, + this)); +} + +bool +D2ClientMgr::amSending() const { + return (name_change_sender_ && name_change_sender_->amSending()); +} + +void +D2ClientMgr::stopSender() { + /// Unregister sender's select-fd. + if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) { + IfaceMgr::instance().deleteExternalSocket(registered_select_fd_); + registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID; + } + + // If its not null, call stop. + if (amSending()) { + name_change_sender_->stopSending(); + LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STOPPED); + } +} + +void +D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) { + if (!amSending()) { + // This is programmatic error so bust them for it. + isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode"); + } + + try { + name_change_sender_->sendRequest(ncr); + } catch (const std::exception& ex) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_NCR_REJECTED) + .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL ")); + invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR, ncr); + } +} + +void +D2ClientMgr::invokeClientErrorHandler(const dhcp_ddns::NameChangeSender:: + Result result, + dhcp_ddns::NameChangeRequestPtr& ncr) { + // Handler is mandatory to enter send mode but test it just to be safe. + if (!client_error_handler_) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL); + } else { + // Handler is not supposed to throw, but catch just in case. + try { + (client_error_handler_)(result, ncr); + } catch (const std::exception& ex) { + LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION) + .arg(ex.what()); + } + } +} + +size_t +D2ClientMgr::getQueueSize() const { + if (!name_change_sender_) { + isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null"); + } + + return(name_change_sender_->getQueueSize()); +} + +size_t +D2ClientMgr::getQueueMaxSize() const { + if (!name_change_sender_) { + isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null"); + } + + return(name_change_sender_->getQueueMaxSize()); +} + + + +const dhcp_ddns::NameChangeRequestPtr& +D2ClientMgr::peekAt(const size_t index) const { + if (!name_change_sender_) { + isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null"); + } + + return (name_change_sender_->peekAt(index)); +} + +void +D2ClientMgr::clearQueue() { + if (!name_change_sender_) { + isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null"); + } + + name_change_sender_->clearSendQueue(); +} + +void +D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result, + dhcp_ddns::NameChangeRequestPtr& ncr) { + if (result == dhcp_ddns::NameChangeSender::SUCCESS) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, + DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText()); + } else { + invokeClientErrorHandler(result, ncr); + } +} + +int +D2ClientMgr::getSelectFd() { + if (!amSending()) { + isc_throw (D2ClientError, "D2ClientMgr::getSelectFd " + " not in send mode"); + } + + return (name_change_sender_->getSelectFd()); +} + +void +D2ClientMgr::runReadyIO() { + if (!name_change_sender_) { + // This should never happen. + isc_throw(D2ClientError, "D2ClientMgr::runReadyIO" + " name_change_sender is null"); + } + + name_change_sender_->runReadyIO(); +} + +}; // namespace dhcp + +}; // namespace isc |