1 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, v. 2.0. If a copy of the MPL was not distributed with this 5 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 #ifndef NC_TRANS_H 8 #define NC_TRANS_H 9 10 /// @file nc_trans.h This file defines the class NameChangeTransaction. 11 12 #include <asiolink/io_service.h> 13 #include <d2srv/dns_client.h> 14 #include <d2srv/d2_cfg_mgr.h> 15 #include <d2srv/d2_tsig_key.h> 16 #include <dhcp_ddns/ncr_msg.h> 17 #include <exceptions/exceptions.h> 18 #include <util/state_model.h> 19 20 #include <boost/shared_ptr.hpp> 21 #include <map> 22 23 namespace isc { 24 namespace d2 { 25 26 /// @brief Thrown if the transaction encounters a general error. 27 class NameChangeTransactionError : public isc::Exception { 28 public: NameChangeTransactionError(const char * file,size_t line,const char * what)29 NameChangeTransactionError(const char* file, size_t line, 30 const char* what) : 31 isc::Exception(file, line, what) { }; 32 }; 33 34 /// @brief Defines the type used as the unique key for transactions. 35 typedef isc::dhcp_ddns::D2Dhcid TransactionKey; 36 37 /// @brief Embodies the "life-cycle" required to carry out a DDNS update. 38 /// 39 /// NameChangeTransaction is the base class that provides the common state 40 /// model mechanics and services performing the DNS updates needed to carry out 41 /// a DHCP_DDNS request as described by a NameChangeRequest. It is derived 42 /// from StateModel which supplies a simple, general purpose FSM implementation. 43 /// 44 /// Upon construction, each transaction has all of the information and 45 /// resources required to carry out its assigned request, including the list(s) 46 /// of DNS server(s) needed. It is responsible for knowing what conversations 47 /// it must have with which servers and in the order necessary to fulfill the 48 /// request. Upon fulfillment of the request, the transaction's work is complete 49 /// and it is destroyed. 50 /// 51 /// Fulfillment of the request is carried out through the performance of the 52 /// transaction's state model. Using a state driven implementation accounts 53 /// for the conditional processing flow necessary to meet the DDNS RFCs as well 54 /// as the asynchronous nature of IO with DNS servers. 55 /// 56 /// Derivations of the class are responsible for defining the state model and 57 /// conversations necessary to carry out the specific of request. 58 /// 59 /// Conversations with DNS servers are done through the use of the DNSClient 60 /// class. The DNSClient provides a IOService-based means a service which 61 /// performs a single, packet exchange with a given DNS server. It sends a 62 /// single update to the server and returns the response, asynchronously, 63 /// through a callback. At each point in a transaction's state model, where 64 /// an update is to be sent, the model "suspends" until notified by the 65 /// DNSClient via the callback. Suspension is done by posting a 66 /// StateModel::NOP_EVT as the next event, stopping the state model execution. 67 /// 68 /// Resuming state model execution when a DNS update completes is done by a 69 /// call to StateModel::runStateModel() from within the DNSClient callback, 70 /// with an event value of IO_COMPLETED_EVT (described below). 71 /// 72 /// This class defines a set of events and states that are a common to all 73 /// transactions. Each derivation may add define additional states and events 74 /// as needed, but it must support the common set. NameChangeTransaction 75 /// does not supply any state handlers. These are the sole responsibility of 76 /// derivations. 77 class NameChangeTransaction : public DNSClient::Callback, public util::StateModel { 78 public: 79 80 //@{ States common to all transactions. 81 82 /// @brief State from which a transaction is started. 83 static const int READY_ST = SM_DERIVED_STATE_MIN + 1; 84 85 /// @brief State in which forward DNS server selection is done. 86 /// 87 /// Within this state, the actual selection of the next forward server 88 /// to use is conducted. Upon conclusion of this state the next server 89 /// is either selected or it should transition out with NO_MORE_SERVERS_EVT 90 /// event. 91 static const int SELECTING_FWD_SERVER_ST = SM_DERIVED_STATE_MIN + 2; 92 93 /// @brief State in which reverse DNS server selection is done. 94 /// 95 /// Within this state, the actual selection of the next reverse server 96 /// to use is conducted. Upon conclusion of this state the next server 97 /// is either selected or it should transition out with NO_MORE_SERVERS_EVT 98 /// event. 99 static const int SELECTING_REV_SERVER_ST = SM_DERIVED_STATE_MIN + 3; 100 101 /// @brief State which processes successful transaction conclusion. 102 static const int PROCESS_TRANS_OK_ST = SM_DERIVED_STATE_MIN + 4; 103 104 /// @brief State which processes an unsuccessful transaction conclusion. 105 static const int PROCESS_TRANS_FAILED_ST = SM_DERIVED_STATE_MIN + 5; 106 107 /// @brief Value at which custom states in a derived class should begin. 108 static const int NCT_DERIVED_STATE_MIN = SM_DERIVED_STATE_MIN + 101; 109 //@} 110 111 //@{ Events common to all transactions. 112 /// @brief Issued when a server needs to be selected. 113 static const int SELECT_SERVER_EVT = SM_DERIVED_EVENT_MIN + 1; 114 115 /// @brief Issued when a server has been selected. 116 static const int SERVER_SELECTED_EVT = SM_DERIVED_EVENT_MIN + 2; 117 118 /// @brief Issued when an update fails due to an IO error. 119 static const int SERVER_IO_ERROR_EVT = SM_DERIVED_EVENT_MIN + 3; 120 121 /// @brief Issued when there are no more servers from which to select. 122 /// This occurs when none of the servers in the list can be reached to 123 /// perform the update. 124 125 static const int NO_MORE_SERVERS_EVT =SM_DERIVED_EVENT_MIN + 4; 126 /// @brief Issued when a DNS update packet exchange has completed. 127 /// This occurs whenever the DNSClient callback is invoked whether the 128 /// exchange was successful or not. 129 130 static const int IO_COMPLETED_EVT = SM_DERIVED_EVENT_MIN + 5; 131 /// @brief Issued when the attempted update successfully completed. 132 /// This occurs when an DNS update packet was successfully processed 133 /// by the server. 134 135 static const int UPDATE_OK_EVT = SM_DERIVED_EVENT_MIN + 6; 136 137 /// @brief Issued when the attempted update fails to complete. 138 /// This occurs when an DNS update packet fails to process. The nature of 139 /// the failure is given by the DNSClient return status and the response 140 /// packet (if one was received). 141 static const int UPDATE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 7; 142 143 /// @brief Value at which custom events in a derived class should begin. 144 static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101; 145 //@} 146 147 /// @brief Default time to assign to a single DNS update. 148 /// @todo This value will be made configurable in the very near future 149 /// under trac3268. For now we will define it to 100 milliseconds 150 /// so unit tests will run within a reasonable amount of time. 151 static const unsigned int DNS_UPDATE_DEFAULT_TIMEOUT = 100; 152 153 /// @brief Maximum times to attempt a single update on a given server. 154 static const unsigned int MAX_UPDATE_TRIES_PER_SERVER = 3; 155 156 /// @brief Constructor 157 /// 158 /// Instantiates a transaction that is ready to be started. 159 /// 160 /// @param io_service IO service to be used for IO processing 161 /// @param ncr is the NameChangeRequest to fulfill 162 /// @param forward_domain is the domain to use for forward DNS updates 163 /// @param reverse_domain is the domain to use for reverse DNS updates 164 /// @param cfg_mgr reference to the current configuration manager 165 /// 166 /// @throw NameChangeTransactionError if given an null request, 167 /// if forward change is enabled but forward domain is null, if 168 /// reverse change is enabled but reverse domain is null. 169 NameChangeTransaction(asiolink::IOServicePtr& io_service, 170 dhcp_ddns::NameChangeRequestPtr& ncr, 171 DdnsDomainPtr& forward_domain, 172 DdnsDomainPtr& reverse_domain, 173 D2CfgMgrPtr& cfg_mgr); 174 175 /// @brief Destructor 176 virtual ~NameChangeTransaction(); 177 178 /// @brief Begins execution of the transaction. 179 /// 180 /// This method invokes StateModel::startModel() with a value of READY_ST. 181 /// This causes transaction's state model to attempt to begin execution 182 /// with the state handler for READY_ST. 183 void startTransaction(); 184 185 /// @brief Serves as the DNSClient IO completion event handler. 186 /// 187 /// This is the implementation of the method inherited by our derivation 188 /// from DNSClient::Callback. When the DNSClient completes an update it 189 /// invokes this method as the completion handler. This method stores 190 /// the given status and invokes runStateModel() with an event value of 191 /// IO_COMPLETED_EVT. 192 /// 193 /// @param status is the outcome of the DNS update packet exchange. 194 /// This method is exception safe. 195 virtual void operator()(DNSClient::Status status); 196 197 protected: 198 /// @brief Send the update request to the current server. 199 /// 200 /// This method increments the update attempt count and then passes the 201 /// current update request to the DNSClient instance to be sent to the 202 /// currently selected server. Since the send is asynchronous, the method 203 /// posts NOP_EVT as the next event and then returns. 204 /// 205 /// If tsig_key_ is not NULL, then the update will be conducted using 206 /// the key to sign the request and verify the response, otherwise it 207 /// will be conducted without TSIG. 208 /// 209 /// @param comment text to include in log detail 210 /// 211 /// If an exception occurs it will be logged and and the transaction will 212 /// be failed. 213 virtual void sendUpdate(const std::string& comment = ""); 214 215 /// @brief Adds events defined by NameChangeTransaction to the event set. 216 /// 217 /// This method adds the events common to NCR transaction processing to 218 /// the set of define events. It invokes the superclass's implementation 219 /// first to maintain the hierarchical chain of event definition. 220 /// Derivations of NameChangeTransaction must invoke its implementation 221 /// in like fashion. 222 /// 223 /// @throw StateModelError if an event definition is invalid or a duplicate. 224 virtual void defineEvents(); 225 226 /// @brief Validates the contents of the set of events. 227 /// 228 /// This method verifies that the events defined by both the superclass and 229 /// this class are defined. As with defineEvents, this method calls the 230 /// superclass's implementation first, to verify events defined by it and 231 /// then this implementation to verify events defined by 232 /// NameChangeTransaction. 233 /// 234 /// @throw StateModelError if an event value is undefined. 235 virtual void verifyEvents(); 236 237 /// @brief Adds states defined by NameChangeTransaction to the state set. 238 /// 239 /// This method adds the states common to NCR transaction processing to 240 /// the dictionary of states. It invokes the superclass's implementation 241 /// first to maintain the hierarchical chain of state definition. 242 /// Derivations of NameChangeTransaction must invoke its implementation 243 /// in like fashion. 244 /// 245 /// @throw StateModelError if an state definition is invalid or a duplicate. 246 virtual void defineStates(); 247 248 /// @brief Validates the contents of the set of states. 249 /// 250 /// This method verifies that the states defined by both the superclass and 251 /// this class are defined. As with defineStates, this method calls the 252 /// superclass's implementation first, to verify states defined by it and 253 /// then this implementation to verify states defined by 254 /// NameChangeTransaction. 255 /// 256 /// @throw StateModelError if an event value is undefined. 257 virtual void verifyStates(); 258 259 /// @brief Handler for fatal model execution errors. 260 /// 261 /// This handler is called by the StateModel implementation when the model 262 /// execution encounters a model violation: attempt to call an unmapped 263 /// state, an event not valid for the current state, or an uncaught 264 /// exception thrown during a state handler invocation. When such an 265 /// error occurs the transaction is deemed inoperable, and further model 266 /// execution cannot be performed. It marks the transaction as failed by 267 /// setting the NCR status to dhcp_ddns::ST_FAILED 268 /// 269 /// @param explanation is text detailing the error 270 virtual void onModelFailure(const std::string& explanation); 271 272 /// @brief Determines the state and next event based on update attempts. 273 /// 274 /// This method will post a next event of SERVER_SELECTED_EVT to the 275 /// current state if the number of update attempts has not reached the 276 /// maximum allowed. 277 /// 278 /// If the maximum number of attempts has been reached, it will transition 279 /// to the given state with a next event of SERVER_IO_ERROR_EVT. 280 /// 281 /// @param fail_to_state State to transition to if maximum attempts 282 /// have been tried. 283 /// 284 void retryTransition(const int fail_to_state); 285 286 /// @brief Sets the update request packet to the given packet. 287 /// 288 /// @param request is the new request packet to assign. 289 void setDnsUpdateRequest(D2UpdateMessagePtr& request); 290 291 /// @brief Destroys the current update request packet. 292 void clearDnsUpdateRequest(); 293 294 /// @brief Resets the update attempts count. 295 void clearUpdateAttempts(); 296 297 /// @brief Sets the update status to the given status value. 298 /// 299 /// @param status is the new value for the update status. 300 void setDnsUpdateStatus(const DNSClient::Status& status); 301 302 /// @brief Sets the update response packet to the given packet. 303 /// 304 /// @param response is the new response packet to assign. 305 void setDnsUpdateResponse(D2UpdateMessagePtr& response); 306 307 /// @brief Destroys the current update response packet. 308 void clearDnsUpdateResponse(); 309 310 /// @brief Sets the forward change completion flag to the given value. 311 /// 312 /// @param value is the new value to assign to the flag. 313 void setForwardChangeCompleted(const bool value); 314 315 /// @brief Sets the reverse change completion flag to the given value. 316 /// 317 /// @param value is the new value to assign to the flag. 318 void setReverseChangeCompleted(const bool value); 319 320 /// @brief Sets the status of the transaction's NameChangeRequest 321 /// 322 /// @param status is the new value to assign to the NCR status. 323 void setNcrStatus(const dhcp_ddns::NameChangeStatus& status); 324 325 /// @brief Initializes server selection from the given DDNS domain. 326 /// 327 /// Method prepares internal data to conduct server selection from the 328 /// list of servers supplied by the given domain. This method should be 329 /// called when a transaction is ready to begin selecting servers from 330 /// a new list. Typically this will be prior to starting the updates for 331 /// a given DNS direction. 332 /// 333 /// @param domain is the domain from which server selection is to be 334 /// conducted. 335 void initServerSelection(const DdnsDomainPtr& domain); 336 337 /// @brief Selects the next server in the current server list. 338 /// 339 /// This method is used to iterate over the list of servers. If there are 340 /// no more servers in the list, it returns false. Otherwise it sets the 341 /// current server to the next server and creates a new DNSClient 342 /// instance. 343 /// 344 /// @return True if a server has been selected, false if there are no more 345 /// servers from which to select. 346 bool selectNextServer(); 347 348 /// @brief Selects the TSIG key. 349 /// 350 /// This method uses the current server and the select_key callout. 351 /// When no TSIG key is selected the tsig_key_ pointer is null. 352 /// 353 /// @return False when the current server should be skipped. 354 bool selectTSIGKey(); 355 356 /// @brief Sets the update attempt count to the given value. 357 /// 358 /// @param value is the new value to assign. 359 void setUpdateAttempts(const size_t value); 360 361 /// @brief Fetches the IOService the transaction uses for IO processing. 362 /// 363 /// @return returns a const pointer to the IOService. getIOService()364 const asiolink::IOServicePtr& getIOService() { 365 return (io_service_); 366 } 367 368 /// @brief Creates a new DNS update request based on the given domain. 369 /// 370 /// Constructs a new "empty", OUTBOUND, request with the message id set 371 /// and zone section populated based on the given domain. 372 /// It is declared virtual for test purposes. 373 /// 374 /// @return A D2UpdateMessagePtr to the new request. 375 /// 376 /// @throw NameChangeTransactionError if request cannot be constructed. 377 virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain); 378 379 /// @brief Adds an RData for the lease address to the given RRset. 380 /// 381 /// Creates an in::A() or in:AAAA() RData instance from the NCR 382 /// lease address and adds it to the given RRset. 383 /// 384 /// @param rrset RRset to which to add the RData 385 /// 386 /// @throw NameChangeTransactionError if RData cannot be constructed or 387 /// the RData cannot be added to the given RRset. 388 void addLeaseAddressRdata(dns::RRsetPtr& rrset); 389 390 /// @brief Adds an RData for the lease client's DHCID to the given RRset. 391 /// 392 /// Creates an in::DHCID() RData instance from the NCR DHCID and adds 393 /// it to the given RRset. 394 /// 395 /// @param rrset RRset to which to add the RData 396 /// 397 /// @throw NameChangeTransactionError if RData cannot be constructed or 398 /// the RData cannot be added to the given RRset. 399 void addDhcidRdata(dns::RRsetPtr& rrset); 400 401 /// @brief Adds an RData for the lease FQDN to the given RRset. 402 /// 403 /// Creates an in::PTR() RData instance from the NCR FQDN and adds 404 /// it to the given RRset. 405 /// 406 /// @param rrset RRset to which to add the RData 407 /// 408 /// @throw NameChangeTransactionError if RData cannot be constructed or 409 /// the RData cannot be added to the given RRset. 410 void addPtrRdata(dns::RRsetPtr& rrset); 411 412 /// @brief Returns a string version of the current response status and rcode 413 /// 414 /// Renders a string containing the text label of current DNS update status 415 /// and RCODE (if status is DNSClient::SUCCESS) 416 /// 417 /// @return std::string containing constructed text 418 std::string responseString() const; 419 420 /// @brief Returns a string version of transaction outcome. 421 /// 422 /// Renders a string containing summarizes the outcome of the 423 /// transaction. The information includes the overall status, 424 /// the last event, whether not forward and reverse changes were 425 /// done, as well as the NCR serviced. 426 /// 427 /// @return std::string containing constructed text 428 std::string transactionOutcomeString() const; 429 430 public: 431 /// @brief Fetches the NameChangeRequest for this transaction. 432 /// 433 /// @return A const pointer reference to the NameChangeRequest. 434 const dhcp_ddns::NameChangeRequestPtr& getNcr() const; 435 436 /// @brief Fetches the unique key that identifies this transaction. 437 /// 438 /// Transactions are uniquely identified by a TransactionKey. Currently 439 /// this is wrapper around a D2Dhcid. 440 /// 441 /// @return A const reference to the TransactionKey. 442 const TransactionKey& getTransactionKey() const; 443 444 /// @brief Fetches the request id that identifies this transaction. 445 /// 446 /// This is a wrapper around getRequestId from the NCR which currently 447 /// returns DHCID. In the future we may include a distinct request id. 448 /// The primary purpose of this function is to provide a consistent way 449 /// to identify requests for logging purposes. 450 /// 451 /// @return a string with the request's request ID (currently DHCID) 452 std::string getRequestId() const; 453 454 /// @brief Fetches the NameChangeRequest status of the transaction. 455 /// 456 /// This is the current status of the NameChangeRequest, not to 457 /// be confused with the state of the transaction. Once the transaction 458 /// is reached its conclusion, the request will end up with a final 459 /// status. 460 /// 461 /// @return A dhcp_ddns::NameChangeStatus representing the current 462 /// status of the transaction. 463 dhcp_ddns::NameChangeStatus getNcrStatus() const; 464 465 /// @brief Fetches the forward DdnsDomain. 466 /// 467 /// @return A pointer reference to the forward DdnsDomain. If 468 /// the request does not include a forward change, the pointer will empty. 469 DdnsDomainPtr& getForwardDomain(); 470 471 /// @brief Fetches the reverse DdnsDomain. 472 /// 473 /// @return A pointer reference to the reverse DdnsDomain. If 474 /// the request does not include a reverse change, the pointer will empty. 475 DdnsDomainPtr& getReverseDomain(); 476 477 /// @brief Fetches the currently selected server. 478 /// 479 /// @return A const pointer reference to the DnsServerInfo of the current 480 /// server. 481 const DnsServerInfoPtr& getCurrentServer() const; 482 483 /// @brief Fetches the DNSClient instance 484 /// 485 /// @return A const pointer reference to the DNSClient 486 const DNSClientPtr& getDNSClient() const; 487 488 /// @brief Fetches the current DNS update request packet. 489 /// 490 /// @return A const pointer reference to the current D2UpdateMessage 491 /// request. 492 const D2UpdateMessagePtr& getDnsUpdateRequest() const; 493 494 /// @brief Fetches the most recent DNS update status. 495 /// 496 /// @return A DNSClient::Status indicating the result of the most recent 497 /// DNS update to complete. 498 DNSClient::Status getDnsUpdateStatus() const; 499 500 /// @brief Fetches the most recent DNS update response packet. 501 /// 502 /// @return A const pointer reference to the D2UpdateMessage most recently 503 /// received. 504 const D2UpdateMessagePtr& getDnsUpdateResponse() const; 505 506 /// @brief Returns whether the forward change has completed or not. 507 /// 508 /// The value returned is only meaningful if the NameChangeRequest calls 509 /// for a forward change to be done. The value returned indicates if 510 /// forward change has been completed successfully. 511 /// 512 /// @return True if the forward change has been completed, false otherwise. 513 bool getForwardChangeCompleted() const; 514 515 /// @brief Returns whether the reverse change has completed or not. 516 /// 517 /// The value returned is only meaningful if the NameChangeRequest calls 518 /// for a reverse change to be done. The value returned indicates if 519 /// reverse change has been completed successfully. 520 /// 521 /// @return True if the reverse change has been completed, false otherwise. 522 bool getReverseChangeCompleted() const; 523 524 /// @brief Fetches the update attempt count for the current update. 525 /// 526 /// @return size_t which is the number of times the current request has 527 /// been attempted against the current server. 528 size_t getUpdateAttempts() const; 529 530 /// @brief Returns the DHCP data type for the lease address 531 /// 532 /// @return constant reference to dns::RRType::A() if the lease address 533 /// is IPv4 or dns::RRType::AAAA() if the lease address is IPv6. 534 const dns::RRType& getAddressRRType() const; 535 536 private: 537 /// @brief The IOService which should be used to for IO processing. 538 asiolink::IOServicePtr io_service_; 539 540 /// @brief The NameChangeRequest that the transaction is to fulfill. 541 dhcp_ddns::NameChangeRequestPtr ncr_; 542 543 /// @brief The forward domain that matches the request. 544 /// 545 /// The forward "domain" is DdnsDomain which contains all of the information 546 /// necessary, including the list of DNS servers to be used for a forward 547 /// change. 548 DdnsDomainPtr forward_domain_; 549 550 /// @brief The reverse domain that matches the request. 551 /// 552 /// The reverse "domain" is DdnsDomain which contains all of the information 553 /// necessary, including the list of DNS servers to be used for a reverse 554 /// change. 555 DdnsDomainPtr reverse_domain_; 556 557 /// @brief The DNSClient instance that will carry out DNS packet exchanges. 558 DNSClientPtr dns_client_; 559 560 /// @brief The DNS current update request packet. 561 D2UpdateMessagePtr dns_update_request_; 562 563 /// @brief The outcome of the most recently completed DNS packet exchange. 564 DNSClient::Status dns_update_status_; 565 566 /// @brief The DNS update response packet most recently received. 567 D2UpdateMessagePtr dns_update_response_; 568 569 /// @brief Indicator for whether or not the forward change completed ok. 570 bool forward_change_completed_; 571 572 /// @brief Indicator for whether or not the reverse change completed ok. 573 bool reverse_change_completed_; 574 575 /// @brief Pointer to the current server selection list. 576 DnsServerInfoStoragePtr current_server_list_; 577 578 /// @brief Pointer to the currently selected server. 579 DnsServerInfoPtr current_server_; 580 581 /// @brief Next server position in the list. 582 /// 583 /// This value is always the position of the next selection in the server 584 /// list, which may be beyond the end of the list. 585 size_t next_server_pos_; 586 587 /// @brief Number of transmit attempts for the current request. 588 size_t update_attempts_; 589 590 /// @brief Pointer to the configuration manager. 591 D2CfgMgrPtr cfg_mgr_; 592 593 /// @brief Pointer to the TSIG key which should be used (if any). 594 D2TsigKeyPtr tsig_key_; 595 }; 596 597 /// @brief Defines a pointer to a NameChangeTransaction. 598 typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr; 599 600 } // namespace isc::d2 601 } // namespace isc 602 #endif 603