// 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 #include #include #include #include namespace isc { namespace d2 { // NameRemoveTransaction states const int NameRemoveTransaction::REMOVING_FWD_ADDRS_ST; const int NameRemoveTransaction::REMOVING_FWD_RRS_ST; const int NameRemoveTransaction::REMOVING_REV_PTRS_ST; // NameRemoveTransaction events // Currently NameRemoveTransaction does not define any events. NameRemoveTransaction:: NameRemoveTransaction(asiolink::IOServicePtr& io_service, dhcp_ddns::NameChangeRequestPtr& ncr, DdnsDomainPtr& forward_domain, DdnsDomainPtr& reverse_domain, D2CfgMgrPtr& cfg_mgr) : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain, cfg_mgr) { if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) { isc_throw (NameRemoveTransactionError, "NameRemoveTransaction, request type must be CHG_REMOVE"); } } NameRemoveTransaction::~NameRemoveTransaction(){ } void NameRemoveTransaction::defineEvents() { // Call superclass impl first. NameChangeTransaction::defineEvents(); // Define NameRemoveTransaction events. // Currently NameRemoveTransaction does not define any events. // defineEvent(TBD_EVENT, "TBD_EVT"); } void NameRemoveTransaction::verifyEvents() { // Call superclass implementation first to verify its events. These are // events common to all transactions, and they must be defined. // SELECT_SERVER_EVT // SERVER_SELECTED_EVT // SERVER_IO_ERROR_EVT // NO_MORE_SERVERS_EVT // IO_COMPLETED_EVT // UPDATE_OK_EVT // UPDATE_FAILED_EVT NameChangeTransaction::verifyEvents(); // Verify NameRemoveTransaction events by attempting to fetch them. // Currently NameRemoveTransaction does not define any events. // getEvent(TBD_EVENT); } void NameRemoveTransaction::defineStates() { // Call superclass impl first. NameChangeTransaction::defineStates(); // Define NameRemoveTransaction states. defineState(READY_ST, "READY_ST", std::bind(&NameRemoveTransaction::readyHandler, this)); defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST", std::bind(&NameRemoveTransaction::selectingFwdServerHandler, this)); defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST", std::bind(&NameRemoveTransaction::selectingRevServerHandler, this)); defineState(REMOVING_FWD_ADDRS_ST, "REMOVING_FWD_ADDRS_ST", std::bind(&NameRemoveTransaction::removingFwdAddrsHandler, this)); defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST", std::bind(&NameRemoveTransaction::removingFwdRRsHandler, this)); defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST", std::bind(&NameRemoveTransaction::removingRevPtrsHandler, this)); defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST", std::bind(&NameRemoveTransaction::processRemoveOkHandler, this)); defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST", std::bind(&NameRemoveTransaction::processRemoveFailedHandler, this)); } void NameRemoveTransaction::verifyStates() { // Call superclass implementation first to verify its states. These are // states common to all transactions, and they must be defined. // READY_ST // SELECTING_FWD_SERVER_ST // SELECTING_REV_SERVER_ST // PROCESS_TRANS_OK_ST // PROCESS_TRANS_FAILED_ST NameChangeTransaction::verifyStates(); // Verify NameRemoveTransaction states by attempting to fetch them. getStateInternal(REMOVING_FWD_ADDRS_ST); getStateInternal(REMOVING_FWD_RRS_ST); getStateInternal(REMOVING_REV_PTRS_ST); } void NameRemoveTransaction::readyHandler() { switch(getNextEvent()) { case START_EVT: if (getForwardDomain()) { // Request includes a forward change, do that first. transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT); } else { // Reverse change only, transition accordingly. transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); } break; default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::selectingFwdServerHandler() { switch(getNextEvent()) { case SELECT_SERVER_EVT: // First time through for this transaction, so initialize server // selection. initServerSelection(getForwardDomain()); break; case SERVER_IO_ERROR_EVT: // We failed to communicate with current server. Attempt to select // another one below. break; default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } // Select the next server from the list of forward servers. if (selectNextServer()) { // We have a server to try. transition(REMOVING_FWD_ADDRS_ST, SERVER_SELECTED_EVT); } else { // Server list is exhausted, so fail the transaction. transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); } } void NameRemoveTransaction::removingFwdAddrsHandler() { if (doOnEntry()) { // Clear the update attempts count on initial transition. clearUpdateAttempts(); } switch(getNextEvent()) { case SERVER_SELECTED_EVT: try { clearDnsUpdateRequest(); buildRemoveFwdAddressRequest(); } catch (const std::exception& ex) { // While unlikely, the build might fail if we have invalid // data. Should that be the case, we need to fail the // transaction. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE) .arg(getRequestId()) .arg(getNcr()->toText()) .arg(ex.what()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // Call sendUpdate() to initiate the async send. Note it also sets // next event to NOP_EVT. sendUpdate("Forward A/AAAA Remove"); break; case IO_COMPLETED_EVT: { switch (getDnsUpdateStatus()) { case DNSClient::SUCCESS: { // We successfully received a response packet from the server. // The RCODE will be based on a value-dependent RRset search, // see RFC 2136 section 3.2.3/3.2.4. const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); if ((rcode == dns::Rcode::NOERROR()) || (rcode == dns::Rcode::NXRRSET())) { // We were able to remove it or it wasn't there, now we // need to remove any other RRs for this FQDN. transition(REMOVING_FWD_RRS_ST, UPDATE_OK_EVT); } else { // Per RFC4703 any other value means cease. // If we get not authorized should we try the next server in // the list? @todo This needs some discussion perhaps. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()) .arg(rcode.getCode()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); } break; } case DNSClient::TIMEOUT: case DNSClient::OTHER: // We couldn't send to the current server, log it and set up // to select the next server for a retry. // @note For now we treat OTHER as an IO error like TIMEOUT. It // is not entirely clear if this is accurate. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR) .arg(getRequestId()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); retryTransition(SELECTING_FWD_SERVER_ST); break; case DNSClient::INVALID_RESPONSE: // A response was received but was corrupt. Retry it like an IO // error. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()); retryTransition(SELECTING_FWD_SERVER_ST); break; default: // Any other value and we will fail this transaction, something // bigger is wrong. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS) .arg(getRequestId()) .arg(getDnsUpdateStatus()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // end switch on dns_status break; } // end case IO_COMPLETE_EVT default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::removingFwdRRsHandler() { if (doOnEntry()) { // Clear the update attempts count on initial transition. clearUpdateAttempts(); } switch(getNextEvent()) { case UPDATE_OK_EVT: case SERVER_SELECTED_EVT: try { clearDnsUpdateRequest(); buildRemoveFwdRRsRequest(); } catch (const std::exception& ex) { // While unlikely, the build might fail if we have invalid // data. Should that be the case, we need to fail the // transaction. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE) .arg(getRequestId()) .arg(getNcr()->toText()) .arg(ex.what()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // Call sendUpdate() to initiate the async send. Note it also sets // next event to NOP_EVT. sendUpdate("Forward RR Remove"); break; case IO_COMPLETED_EVT: { switch (getDnsUpdateStatus()) { case DNSClient::SUCCESS: { // We successfully received a response packet from the server. // The RCODE will be based on a value-dependent RRset search, // see RFC 2136 section 3.2.3/3.2.4. const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); if ((rcode == dns::Rcode::NOERROR()) || (rcode == dns::Rcode::NXRRSET())) { // We were able to remove them or they were not there ( // Rcode of NXRRSET means there are no matching RRsets). // In either case, we consider it success and mark it as done. setForwardChangeCompleted(true); // If request calls for reverse update then do that next, // otherwise we can process ok. if (getReverseDomain()) { transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT); } else { transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); } } else { // Per RFC4703 any other value means cease. // If we get not authorized should try the next server in // the list? @todo This needs some discussion perhaps. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()) .arg(rcode.getCode()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); } break; } case DNSClient::TIMEOUT: case DNSClient::OTHER: // We couldn't send to the current server, log it and set up // to select the next server for a retry. // @note For now we treat OTHER as an IO error like TIMEOUT. It // is not entirely clear if this is accurate. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR) .arg(getRequestId()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); // @note If we exhaust the IO retries for the current server // due to IO failures, we will abort the remaining updates. // The rational is that we are only in this state, if the remove // of the forward address RR succeeded (removingFwdAddrsHandler) // on the current server. Therefore we should not attempt another // removal on a different server. This is perhaps a point // for discussion. // @todo Should we go ahead with the reverse remove? retryTransition(PROCESS_TRANS_FAILED_ST); break; case DNSClient::INVALID_RESPONSE: // A response was received but was corrupt. Retry it like an IO // error. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()); // If we are out of retries on this server abandon the transaction. // (Same logic as the case for TIMEOUT above). retryTransition(PROCESS_TRANS_FAILED_ST); break; default: // Any other value and we will fail this transaction, something // bigger is wrong. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS) .arg(getRequestId()) .arg(getDnsUpdateStatus()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // end switch on dns_status break; } // end case IO_COMPLETE_EVT default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::selectingRevServerHandler() { switch(getNextEvent()) { case SELECT_SERVER_EVT: // First time through for this transaction, so initialize server // selection. initServerSelection(getReverseDomain()); break; case SERVER_IO_ERROR_EVT: // We failed to communicate with current server. Attempt to select // another one below. break; default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } // Select the next server from the list of forward servers. if (selectNextServer()) { // We have a server to try. transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT); } else { // Server list is exhausted, so fail the transaction. transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT); } } void NameRemoveTransaction::removingRevPtrsHandler() { if (doOnEntry()) { // Clear the update attempts count on initial transition. clearUpdateAttempts(); } switch(getNextEvent()) { case SERVER_SELECTED_EVT: try { clearDnsUpdateRequest(); buildRemoveRevPtrsRequest(); } catch (const std::exception& ex) { // While unlikely, the build might fail if we have invalid // data. Should that be the case, we need to fail the // transaction. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE) .arg(getRequestId()) .arg(getNcr()->toText()) .arg(ex.what()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // Call sendUpdate() to initiate the async send. Note it also sets // next event to NOP_EVT. sendUpdate("Reverse Remove"); break; case IO_COMPLETED_EVT: { switch (getDnsUpdateStatus()) { case DNSClient::SUCCESS: { // We successfully received a response packet from the server. // The RCODE will be based on a value-dependent RRset search, // see RFC 2136 section 3.2.3/3.2.4. const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode(); if ((rcode == dns::Rcode::NOERROR()) || (rcode == dns::Rcode::NXRRSET())) { // We were able to remove the reverse mapping or they were // not there (Rcode of NXRRSET means there are no matching // RRsets). In either case, mark it as done. setReverseChangeCompleted(true); transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT); } else { // Per RFC4703 any other value means cease. // If we get not authorized should try the next server in // the list? @todo This needs some discussion perhaps. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()) .arg(rcode.getCode()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); } break; } case DNSClient::TIMEOUT: case DNSClient::OTHER: // We couldn't send to the current server, log it and set up // to select the next server for a retry. // @note For now we treat OTHER as an IO error like TIMEOUT. It // is not entirely clear if this is accurate. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR) .arg(getRequestId()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); // If we are out of retries on this server, we go back and start // all over on a new server. retryTransition(SELECTING_REV_SERVER_ST); break; case DNSClient::INVALID_RESPONSE: // A response was received but was corrupt. Retry it like an IO // error. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT) .arg(getRequestId()) .arg(getCurrentServer()->toText()) .arg(getNcr()->getFqdn()); // If we are out of retries on this server, we go back and start // all over on a new server. retryTransition(SELECTING_REV_SERVER_ST); break; default: // Any other value and we will fail this transaction, something // bigger is wrong. LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS) .arg(getRequestId()) .arg(getDnsUpdateStatus()) .arg(getNcr()->getFqdn()) .arg(getCurrentServer()->toText()); transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT); break; } // end switch on dns_status break; } // end case IO_COMPLETE_EVT default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::processRemoveOkHandler() { switch(getNextEvent()) { case UPDATE_OK_EVT: LOG_INFO(d2_to_dns_logger, DHCP_DDNS_REMOVE_SUCCEEDED) .arg(getRequestId()) .arg(getNcr()->toText()); setNcrStatus(dhcp_ddns::ST_COMPLETED); endModel(); break; default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::processRemoveFailedHandler() { switch(getNextEvent()) { case UPDATE_FAILED_EVT: case NO_MORE_SERVERS_EVT: case SERVER_IO_ERROR_EVT: setNcrStatus(dhcp_ddns::ST_FAILED); LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REMOVE_FAILED) .arg(getRequestId()) .arg(transactionOutcomeString()); endModel(); break; default: // Event is invalid. isc_throw(NameRemoveTransactionError, "Wrong event for context: " << getContextStr()); } } void NameRemoveTransaction::buildRemoveFwdAddressRequest() { // Construct an empty request. D2UpdateMessagePtr request = prepNewRequest(getForwardDomain()); // Content on this request is based on RFC 4703, section 5.5, paragraph 4. // Construct dns::Name from NCR fqdn. dns::Name fqdn(dns::Name(getNcr()->getFqdn())); // First build the Prerequisite Section // Create an DHCID matches prerequisite RR and add it to the // pre-requisite section // Based on RFC 2136, section 2.4.2. dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), dns::RRTTL(0))); addDhcidRdata(prereq); request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); // Next build the Update Section // Create the FQDN/IP 'delete' RR and add it to the update section. // Add the RR to update section. // Based on 2136 section 2.5.4 dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::NONE(), getAddressRRType(), dns::RRTTL(0))); addLeaseAddressRdata(update); request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); // Set the transaction's update request to the new request. setDnsUpdateRequest(request); } void NameRemoveTransaction::buildRemoveFwdRRsRequest() { // Construct an empty request. D2UpdateMessagePtr request = prepNewRequest(getForwardDomain()); // Construct dns::Name from NCR fqdn. dns::Name fqdn(dns::Name(getNcr()->getFqdn())); // Content on this request is based on RFC 4703, section 5.5, paragraph 5. // First build the Prerequisite Section. // Now create an DHCID matches prerequisite RR. // Set the RR's RData to DHCID. // Add it to the pre-requisite section. // Based on RFC 2136, section 2.4.2. dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), dns::RRTTL(0))); addDhcidRdata(prereq); request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); // Create an assertion that there are no A RRs for the FQDN. // Add it to the pre-reqs. // Based on RFC 2136, section 2.4.3. prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(), dns::RRType::A(), dns::RRTTL(0))); request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); // Create an assertion that there are no A RRs for the FQDN. // Add it to the pre-reqs. // Based on RFC 2136, section 2.4.3. prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(), dns::RRTTL(0))); request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); // Next build the Update Section. // Create the 'delete' of all RRs for FQDN. // Set the message RData to lease address. // Add the RR to update section. // Based on RFC 2136, section 2.5.3. dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(), dns::RRType::ANY(), dns::RRTTL(0))); request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); // Set the transaction's update request to the new request. setDnsUpdateRequest(request); } void NameRemoveTransaction::buildRemoveRevPtrsRequest() { // Construct an empty request. D2UpdateMessagePtr request = prepNewRequest(getReverseDomain()); // Create the reverse IP address "FQDN". std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress()); dns::Name rev_ip(rev_addr); // Content on this request is based on RFC 4703, section 5.5, paragraph 2. // First build the Prerequisite Section. // (Note that per RFC 4703, section 5.4, there is no need to validate // DHCID RR for PTR entries.) // Create an assertion that the PTRDNAME in the PTR record matches the // client's FQDN for the address that was released. // Based on RFC 2136, section 3.2.3 dns::RRsetPtr prereq(new dns::RRset(rev_ip, dns::RRClass::IN(), dns::RRType::PTR(), dns::RRTTL(0))); addPtrRdata(prereq); request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq); // Now, build the Update section. // Create a delete of any RRs for the FQDN and add it to update section. // Based on RFC 2136, section 3.4.2.3 dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(), dns::RRType::ANY(), dns::RRTTL(0))); request->addRRset(D2UpdateMessage::SECTION_UPDATE, update); // Set the transaction's update request to the new request. setDnsUpdateRequest(request); } } // namespace isc::d2 } // namespace isc