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