diff options
Diffstat (limited to 'src/lib/d2srv/nc_trans.cc')
-rw-r--r-- | src/lib/d2srv/nc_trans.cc | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/src/lib/d2srv/nc_trans.cc b/src/lib/d2srv/nc_trans.cc new file mode 100644 index 0000000..e9534c2 --- /dev/null +++ b/src/lib/d2srv/nc_trans.cc @@ -0,0 +1,578 @@ +// Copyright (C) 2013-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 <d2srv/d2_log.h> +#include <d2srv/nc_trans.h> +#include <dns/qid_gen.h> +#include <dns/rdata.h> +#include <hooks/hooks.h> +#include <hooks/hooks_manager.h> + +#include <sstream> + +using namespace isc::hooks; +using namespace isc::util; + +namespace { + +/// Structure that holds registered hook indexes. +struct NcTransHooks { + int hooks_index_select_key_; + + /// Constructor that registers hook points for the D2 server. + NcTransHooks() { + hooks_index_select_key_ = HooksManager::registerHook("select_key"); + } +}; + +// Declare a Hooks object. As this is outside any function or method, it +// will be instantiated (and the constructor run) when the module is loaded. +// As a result, the hook indexes will be defined before any method in this +// module is called. + +NcTransHooks Hooks; + +} + +namespace isc { +namespace d2 { + +// Common transaction states +const int NameChangeTransaction::READY_ST; +const int NameChangeTransaction::SELECTING_FWD_SERVER_ST; +const int NameChangeTransaction::SELECTING_REV_SERVER_ST; +const int NameChangeTransaction::PROCESS_TRANS_OK_ST; +const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST; + +const int NameChangeTransaction::NCT_DERIVED_STATE_MIN; + +// Common transaction events +const int NameChangeTransaction::SELECT_SERVER_EVT; +const int NameChangeTransaction::SERVER_SELECTED_EVT; +const int NameChangeTransaction::SERVER_IO_ERROR_EVT; +const int NameChangeTransaction::NO_MORE_SERVERS_EVT; +const int NameChangeTransaction::IO_COMPLETED_EVT; +const int NameChangeTransaction::UPDATE_OK_EVT; +const int NameChangeTransaction::UPDATE_FAILED_EVT; + +const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN; + +const unsigned int NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER; + +NameChangeTransaction:: +NameChangeTransaction(asiolink::IOServicePtr& io_service, + dhcp_ddns::NameChangeRequestPtr& ncr, + DdnsDomainPtr& forward_domain, + DdnsDomainPtr& reverse_domain, + D2CfgMgrPtr& cfg_mgr) + : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain), + reverse_domain_(reverse_domain), dns_client_(), dns_update_request_(), + dns_update_status_(DNSClient::OTHER), dns_update_response_(), + forward_change_completed_(false), reverse_change_completed_(false), + current_server_list_(), current_server_(), next_server_pos_(0), + update_attempts_(0), cfg_mgr_(cfg_mgr), tsig_key_() { + /// @todo if io_service is NULL we are multi-threading and should + /// instantiate our own + if (!io_service_) { + isc_throw(NameChangeTransactionError, "IOServicePtr cannot be null"); + } + + if (!ncr_) { + isc_throw(NameChangeTransactionError, + "NameChangeRequest cannot be null"); + } + + if (ncr_->isForwardChange() && !(forward_domain_)) { + isc_throw(NameChangeTransactionError, + "Forward change must have a forward domain"); + } + + if (ncr_->isReverseChange() && !(reverse_domain_)) { + isc_throw(NameChangeTransactionError, + "Reverse change must have a reverse domain"); + } + + if (!cfg_mgr_) { + isc_throw(NameChangeTransactionError, + "Configuration manager cannot be null"); + } +} + +NameChangeTransaction::~NameChangeTransaction(){ +} + +void +NameChangeTransaction::startTransaction() { + LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL, + DHCP_DDNS_STARTING_TRANSACTION) + .arg(getRequestId()); + + setNcrStatus(dhcp_ddns::ST_PENDING); + startModel(READY_ST); +} + +void +NameChangeTransaction::operator()(DNSClient::Status status) { + // Stow the completion status and re-enter the run loop with the event + // set to indicate IO completed. + // runModel is exception safe so we are good to call it here. + // It won't exit until we hit the next IO wait or the state model ends. + setDnsUpdateStatus(status); + LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL, + DHCP_DDNS_UPDATE_RESPONSE_RECEIVED) + .arg(getRequestId()) + .arg(current_server_->toText()) + .arg(responseString()); + + runModel(IO_COMPLETED_EVT); +} + +std::string +NameChangeTransaction::responseString() const { + std::ostringstream stream; + switch (getDnsUpdateStatus()) { + case DNSClient::SUCCESS: + stream << "SUCCESS, rcode: "; + if (getDnsUpdateResponse()) { + stream << getDnsUpdateResponse()->getRcode().toText(); + } else { + stream << " update response is NULL"; + } + break; + case DNSClient::TIMEOUT: + stream << "TIMEOUT"; + break; + case DNSClient::IO_STOPPED: + stream << "IO_STOPPED"; + break; + case DNSClient::INVALID_RESPONSE: + stream << "INVALID_RESPONSE"; + break; + case DNSClient::OTHER: + stream << "OTHER"; + break; + default: + stream << "UNKNOWN(" + << static_cast<int>(getDnsUpdateStatus()) << ")"; + break; + + } + + return (stream.str()); +} + +std::string +NameChangeTransaction::transactionOutcomeString() const { + std::ostringstream stream; + stream << "Status: " << (getNcrStatus() == dhcp_ddns::ST_COMPLETED + ? "Completed, " : "Failed, ") + << "Event: " << getEventLabel(getNextEvent()) << ", "; + + if (ncr_->isForwardChange()) { + stream << " Forward change:" << (getForwardChangeCompleted() + ? " completed, " : " failed, "); + } + + if (ncr_->isReverseChange()) { + stream << " Reverse change:" << (getReverseChangeCompleted() + ? " completed, " : " failed, "); + } + + stream << " request: " << ncr_->toText(); + return (stream.str()); +} + + +void +NameChangeTransaction::sendUpdate(const std::string& comment) { + try { + ++update_attempts_; + // @todo add logic to add/replace TSIG key info in request if + // use_tsig_ is true. We should be able to navigate to the TSIG key + // for the current server. If not we would need to add that. + + D2ParamsPtr d2_params = cfg_mgr_->getD2Params(); + dns_client_->doUpdate(*io_service_, current_server_->getIpAddress(), + current_server_->getPort(), *dns_update_request_, + d2_params->getDnsServerTimeout(), tsig_key_); + // Message is on its way, so the next event should be NOP_EVT. + postNextEvent(NOP_EVT); + LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL, + DHCP_DDNS_UPDATE_REQUEST_SENT) + .arg(getRequestId()) + .arg(comment) + .arg(current_server_->toText()); + } catch (const std::exception& ex) { + // We were unable to initiate the send. + // It is presumed that any throw from doUpdate is due to a programmatic + // error, such as an unforeseen permutation of data, rather than an IO + // failure. IO errors should be caught by the underlying asiolink + // mechanisms and manifested as an unsuccessful IO status in the + // DNSClient callback. Any problem here most likely means the request + // is corrupt in some way and cannot be completed, therefore we will + // log it and transition it to failure. + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_TRANS_SEND_ERROR) + .arg(getRequestId()) + .arg(ex.what()); + transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); + } +} + +void +NameChangeTransaction::defineEvents() { + // Call superclass impl first. + StateModel::defineEvents(); + + // Define NCT events. + defineEvent(SELECT_SERVER_EVT, "SELECT_SERVER_EVT"); + defineEvent(SERVER_SELECTED_EVT, "SERVER_SELECTED_EVT"); + defineEvent(SERVER_IO_ERROR_EVT, "SERVER_IO_ERROR_EVT"); + defineEvent(NO_MORE_SERVERS_EVT, "NO_MORE_SERVERS_EVT"); + defineEvent(IO_COMPLETED_EVT, "IO_COMPLETED_EVT"); + defineEvent(UPDATE_OK_EVT, "UPDATE_OK_EVT"); + defineEvent(UPDATE_FAILED_EVT, "UPDATE_FAILED_EVT"); +} + +void +NameChangeTransaction::verifyEvents() { + // Call superclass impl first. + StateModel::verifyEvents(); + + // Verify NCT events. + getEvent(SELECT_SERVER_EVT); + getEvent(SERVER_SELECTED_EVT); + getEvent(SERVER_IO_ERROR_EVT); + getEvent(NO_MORE_SERVERS_EVT); + getEvent(IO_COMPLETED_EVT); + getEvent(UPDATE_OK_EVT); + getEvent(UPDATE_FAILED_EVT); +} + +void +NameChangeTransaction::defineStates() { + // Call superclass impl first. + StateModel::defineStates(); + // This class is "abstract" in that it does not supply handlers for its + // states, derivations must do that therefore they must define them. +} + +void +NameChangeTransaction::verifyStates() { + // Call superclass impl first. + StateModel::verifyStates(); + + // Verify NCT states. This ensures that derivations provide the handlers. + getStateInternal(READY_ST); + getStateInternal(SELECTING_FWD_SERVER_ST); + getStateInternal(SELECTING_REV_SERVER_ST); + getStateInternal(PROCESS_TRANS_OK_ST); + getStateInternal(PROCESS_TRANS_FAILED_ST); +} + +void +NameChangeTransaction::onModelFailure(const std::string& explanation) { + setNcrStatus(dhcp_ddns::ST_FAILED); + LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR) + .arg(getRequestId()) + .arg(explanation); +} + +void +NameChangeTransaction::retryTransition(const int fail_to_state) { + if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) { + // Re-enter the current state with same server selected. + transition(getCurrState(), SERVER_SELECTED_EVT); + } else { + // Transition to given fail_to_state state if we are out + // of retries. + transition(fail_to_state, SERVER_IO_ERROR_EVT); + } +} + +void +NameChangeTransaction::setDnsUpdateRequest(D2UpdateMessagePtr& request) { + dns_update_request_ = request; +} + +void +NameChangeTransaction::clearDnsUpdateRequest() { + dns_update_request_.reset(); +} + +void +NameChangeTransaction::clearUpdateAttempts() { + update_attempts_ = 0; +} + +void +NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) { + dns_update_status_ = status; +} + +void +NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) { + dns_update_response_ = response; +} + +void +NameChangeTransaction::clearDnsUpdateResponse() { + dns_update_response_.reset(); +} + +void +NameChangeTransaction::setForwardChangeCompleted(const bool value) { + forward_change_completed_ = value; +} + +void +NameChangeTransaction::setReverseChangeCompleted(const bool value) { + reverse_change_completed_ = value; +} + +void +NameChangeTransaction::setUpdateAttempts(const size_t value) { + update_attempts_ = value; +} + +D2UpdateMessagePtr +NameChangeTransaction::prepNewRequest(DdnsDomainPtr domain) { + if (!domain) { + isc_throw(NameChangeTransactionError, + "prepNewRequest - domain cannot be null"); + } + + try { + // Create a "blank" update request. + D2UpdateMessagePtr request(new D2UpdateMessage(D2UpdateMessage:: + OUTBOUND)); + // Set the query id + request->setId(dns::QidGenerator::getInstance().generateQid()); + // Construct the Zone Section. + dns::Name zone_name(domain->getName()); + request->setZone(zone_name, dns::RRClass::IN()); + return (request); + } catch (const std::exception& ex) { + isc_throw(NameChangeTransactionError, "Cannot create new request :" + << ex.what()); + } +} + +void +NameChangeTransaction::addLeaseAddressRdata(dns::RRsetPtr& rrset) { + if (!rrset) { + isc_throw(NameChangeTransactionError, + "addLeaseAddressRdata - RRset cannot cannot be null"); + } + + try { + // Manufacture an RData from the lease address then add it to the RR. + dns::rdata::ConstRdataPtr rdata; + if (ncr_->isV4()) { + rdata.reset(new dns::rdata::in::A(ncr_->getIpAddress())); + } else { + rdata.reset(new dns::rdata::in::AAAA(ncr_->getIpAddress())); + } + rrset->addRdata(rdata); + } catch (const std::exception& ex) { + isc_throw(NameChangeTransactionError, "Cannot add address rdata: " + << ex.what()); + } +} + +void +NameChangeTransaction::addDhcidRdata(dns::RRsetPtr& rrset) { + if (!rrset) { + isc_throw(NameChangeTransactionError, + "addDhcidRdata - RRset cannot cannot be null"); + } + + try { + const std::vector<uint8_t>& ncr_dhcid = ncr_->getDhcid().getBytes(); + util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size()); + dns::rdata::ConstRdataPtr rdata (new dns::rdata::in:: + DHCID(buffer, ncr_dhcid.size())); + rrset->addRdata(rdata); + } catch (const std::exception& ex) { + isc_throw(NameChangeTransactionError, "Cannot add DCHID rdata: " + << ex.what()); + } + +} + +void +NameChangeTransaction::addPtrRdata(dns::RRsetPtr& rrset) { + if (!rrset) { + isc_throw(NameChangeTransactionError, + "addPtrRdata - RRset cannot cannot be null"); + } + + try { + dns::rdata::ConstRdataPtr rdata(new dns::rdata::generic:: + PTR(getNcr()->getFqdn())); + rrset->addRdata(rdata); + } catch (const std::exception& ex) { + isc_throw(NameChangeTransactionError, "Cannot add PTR rdata: " + << ex.what()); + } +} + +const dhcp_ddns::NameChangeRequestPtr& +NameChangeTransaction::getNcr() const { + return (ncr_); +} + +const TransactionKey& +NameChangeTransaction::getTransactionKey() const { + return (ncr_->getDhcid()); +} + +std::string +NameChangeTransaction::getRequestId() const { + return (ncr_->getRequestId()); +} + +dhcp_ddns::NameChangeStatus +NameChangeTransaction::getNcrStatus() const { + return (ncr_->getStatus()); +} + +DdnsDomainPtr& +NameChangeTransaction::getForwardDomain() { + return (forward_domain_); +} + +DdnsDomainPtr& +NameChangeTransaction::getReverseDomain() { + return (reverse_domain_); +} + +void +NameChangeTransaction::initServerSelection(const DdnsDomainPtr& domain) { + if (!domain) { + isc_throw(NameChangeTransactionError, + "initServerSelection called with an empty domain"); + } + + current_server_list_ = domain->getServers(); + next_server_pos_ = 0; + current_server_.reset(); +} + +bool +NameChangeTransaction::selectNextServer() { + for (;;) { + if ((current_server_list_) && + (next_server_pos_ < current_server_list_->size())) { + current_server_ = (*current_server_list_)[next_server_pos_]; + // Toss out any previous response. + dns_update_response_.reset(); + + // Set the tsig_key to that of the current server. + if (!selectTSIGKey()) { + ++next_server_pos_; + continue; + } + + // @todo Protocol is set on DNSClient constructor. We need + // to propagate a configuration value downward, probably starting + // at global, then domain, finishing by server. + // Once that is supported we need to add it here. + dns_client_.reset(new DNSClient(dns_update_response_, this, + DNSClient::UDP)); + ++next_server_pos_; + return (true); + } + + return (false); + } +} + +bool +NameChangeTransaction::selectTSIGKey() { + TSIGKeyInfoPtr tsig_key_info = current_server_->getTSIGKeyInfo(); + if (tsig_key_info) { + tsig_key_ = tsig_key_info->getTSIGKey(); + } else { + tsig_key_.reset(); + } + + // This hook point allows hooks libraries to overwrite the TSIG key. + if (HooksManager::calloutsPresent(Hooks.hooks_index_select_key_)) { + CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); + + callout_handle->setArgument("current_server", current_server_); + callout_handle->setArgument("tsig_key", tsig_key_); + + HooksManager::callCallouts(Hooks.hooks_index_select_key_, + *callout_handle); + + // This server is skipped because the status is not NEXT_STEP_CONTINUE. + if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) { + return (false); + } + + // Reread the TSIG key. + callout_handle->getArgument("tsig_key", tsig_key_); + } + + return (true); +} + + +const DNSClientPtr& +NameChangeTransaction::getDNSClient() const { + return (dns_client_); +} + +const DnsServerInfoPtr& +NameChangeTransaction::getCurrentServer() const { + return (current_server_); +} + +void +NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) { + return (ncr_->setStatus(status)); +} + +const D2UpdateMessagePtr& +NameChangeTransaction::getDnsUpdateRequest() const { + return (dns_update_request_); +} + +DNSClient::Status +NameChangeTransaction::getDnsUpdateStatus() const { + return (dns_update_status_); +} + +const D2UpdateMessagePtr& +NameChangeTransaction::getDnsUpdateResponse() const { + return (dns_update_response_); +} + +bool +NameChangeTransaction::getForwardChangeCompleted() const { + return (forward_change_completed_); +} + +bool +NameChangeTransaction::getReverseChangeCompleted() const { + return (reverse_change_completed_); +} + +size_t +NameChangeTransaction::getUpdateAttempts() const { + return (update_attempts_); +} + +const dns::RRType& +NameChangeTransaction::getAddressRRType() const { + return (ncr_->isV4() ? dns::RRType::A() : dns::RRType::AAAA()); +} + +} // namespace isc::d2 +} // namespace isc |