d2_client_mgr.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. // Copyright (C) 2014-2017 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. #ifndef D2_CLIENT_MGR_H
  7. #define D2_CLIENT_MGR_H
  8. /// @file d2_client_mgr.h Defines the D2ClientMgr class.
  9. /// This file defines the class Kea uses to act as a client of the
  10. /// kea-dhcp-ddns module (aka D2).
  11. ///
  12. #include <asiolink/io_address.h>
  13. #include <dhcp_ddns/ncr_io.h>
  14. #include <dhcpsrv/d2_client_cfg.h>
  15. #include <exceptions/exceptions.h>
  16. #include <boost/shared_ptr.hpp>
  17. #include <boost/noncopyable.hpp>
  18. #include <stdint.h>
  19. #include <string>
  20. #include <vector>
  21. namespace isc {
  22. namespace dhcp {
  23. /// @brief Defines the type for D2 IO error handler.
  24. /// This callback is invoked when a send to kea-dhcp-ddns completes with a
  25. /// failed status. This provides the application layer (Kea) with a means to
  26. /// handle the error appropriately.
  27. ///
  28. /// @param result Result code of the send operation.
  29. /// @param ncr NameChangeRequest which failed to send.
  30. ///
  31. /// @note Handlers are expected not to throw. In the event a handler does
  32. /// throw invoking code logs the exception and then swallows it.
  33. typedef
  34. boost::function<void(const dhcp_ddns::NameChangeSender::Result result,
  35. dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
  36. /// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
  37. ///
  38. /// Provides services for managing the current dhcp-ddns configuration and
  39. /// as well as communications with kea-dhcp-ddns. Regarding configuration it
  40. /// provides services to store, update, and access the current dhcp-ddns
  41. /// configuration. As for kea-dhcp-ddns communications, D2ClientMgr creates
  42. /// maintains a NameChangeSender appropriate to the current configuration and
  43. /// provides services to start, stop, and post NCRs to the sender. Additionally
  44. /// there are methods to examine the queue of requests currently waiting for
  45. /// transmission.
  46. ///
  47. /// The manager also provides the mechanics to integrate the ASIO-based IO
  48. /// used by the NCR IPC with the select-driven IO used by Kea. Senders expose
  49. /// a file descriptor, the "select-fd" that can monitored for read-readiness
  50. /// with the select() function (or variants). D2ClientMgr provides a method,
  51. /// runReadyIO(), that will instructs the sender to process the next ready
  52. /// ready IO handler on the sender's IOservice. Track# 3315 extended
  53. /// Kea's IfaceMgr to support the registration of multiple external sockets
  54. /// with callbacks that are then monitored with IO readiness via select().
  55. /// D2ClientMgr registers the sender's select-fd and runReadyIO() with
  56. /// IfaceMgr when entering the send mode and unregisters it when exiting send
  57. /// mode.
  58. ///
  59. /// To place the manager in send mode, the calling layer must supply an error
  60. /// handler and optionally an IOService instance. The error handler is invoked
  61. /// if a send completes with a failed status. This provides the calling layer
  62. /// an opportunity act upon the error.
  63. ///
  64. /// If the caller supplies an IOService, that service will be used to process
  65. /// the sender's IO. If not supplied, D2ClientMgr pass a private IOService
  66. /// into the sender. Using a private service isolates the sender's IO from
  67. /// any other services.
  68. ///
  69. class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler,
  70. boost::noncopyable {
  71. public:
  72. /// @brief Constructor
  73. ///
  74. /// Default constructor which constructs an instance which has DHCP-DDNS
  75. /// updates disabled.
  76. D2ClientMgr();
  77. /// @brief Destructor.
  78. ~D2ClientMgr();
  79. /// @brief Updates the DHCP-DDNS client configuration to the given value.
  80. ///
  81. /// @param new_config pointer to the new client configuration.
  82. /// @throw D2ClientError if passed an empty pointer.
  83. void setD2ClientConfig(D2ClientConfigPtr& new_config);
  84. /// @brief Convenience method for checking if DHCP-DDNS is enabled.
  85. ///
  86. /// @return True if the D2 configuration is enabled.
  87. bool ddnsEnabled();
  88. /// @brief Fetches the DHCP-DDNS configuration pointer.
  89. ///
  90. /// @return a reference to the current configuration pointer.
  91. const D2ClientConfigPtr& getD2ClientConfig() const;
  92. /// @brief Determines server flags based on configuration and client flags.
  93. ///
  94. /// This method uses input values for the client's FQDN S and N flags, in
  95. /// conjunction with the configuration parameters updates-enabled, override-
  96. /// no-updates, and override-client-updates to determine the values that
  97. /// should be used for the server's FQDN S and N flags.
  98. /// The logic in this method is based upon RFCs 4702 and 4704, and is
  99. /// shown in the following truth table:
  100. ///
  101. /// @code
  102. ///
  103. /// When Updates are enabled:
  104. ///
  105. /// ON = Override No Updates, OC = Override Client Updates
  106. ///
  107. /// | Client |-------- Server Response Flags ------------|
  108. /// | Flags | ON=F,OC=F | ON=F,OC=T | ON=T,OC=F | ON=T,OC=T |
  109. /// | N-S | N-S-O | N-S-O | N-S-O | N-S-O |
  110. /// ----------------------------------------------------------
  111. /// | 0-0 | 0-0-0 | 0-1-1 | 0-0-0 | 0-1-1 |
  112. /// | 0-1 | 0-1-0 | 0-1-0 | 0-1-0 | 0-1-0 |
  113. /// | 1-0 | 1-0-0 | 1-0-0 | 0-1-1 | 0-1-1 |
  114. ///
  115. /// One can then use the server response flags to know when forward and
  116. /// reverse updates should be performed:
  117. ///
  118. /// - Forward updates should be done when the Server S-Flag is true.
  119. /// - Reverse updates should be done when the Server N-Flag is false.
  120. ///
  121. /// When Updates are disabled:
  122. ///
  123. /// | Client | Server |
  124. /// | N-S | N-S-O |
  125. /// --------------------
  126. /// | 0-0 | 1-0-0 |
  127. /// | 0-1 | 1-0-1 |
  128. /// | 1-0 | 1-0-0 |
  129. ///
  130. /// @endcode
  131. ///
  132. /// @param client_s S Flag from the client's FQDN
  133. /// @param client_n N Flag from the client's FQDN
  134. /// @param server_s [out] S Flag for the server's FQDN
  135. /// @param server_n [out] N Flag for the server's FQDN
  136. ///
  137. /// @throw isc::BadValue if client_s and client_n are both 1 as this is
  138. /// an invalid combination per RFCs.
  139. void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
  140. bool& server_n) const;
  141. /// @brief Builds a FQDN based on the configuration and given IP address.
  142. ///
  143. /// Using the current values for generated-prefix, qualifying-suffix and
  144. /// an IP address, this method constructs a fully qualified domain name.
  145. /// It supports both IPv4 and IPv6 addresses. The format of the name
  146. /// is as follows:
  147. ///
  148. /// <generated-prefix>-<ip address>.<qualifying-suffix>.
  149. ///
  150. /// <ip-address> is the result of IOAddress.toText() with the delimiters
  151. /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
  152. ///
  153. /// @param address IP address from which to derive the name (IPv4 or IPv6)
  154. /// @param trailing_dot A boolean value which indicates whether trailing
  155. /// dot should be appended (if true) or not (false).
  156. ///
  157. /// @return std::string containing the generated name.
  158. std::string generateFqdn(const asiolink::IOAddress& address,
  159. const bool trailing_dot = true) const;
  160. /// @brief Adds a qualifying suffix to a given domain name
  161. ///
  162. /// Constructs a FQDN based on the configured qualifying-suffix and
  163. /// a partial domain name as follows:
  164. ///
  165. /// <partial_name>.<qualifying-suffix>.
  166. ///
  167. /// @param partial_name domain name to qualify
  168. /// @param trailing_dot A boolean value which when true guarantees the
  169. /// result will end with a "." and when false that the result will not
  170. /// end with a "." Note that this rule is applied even if the qualifying
  171. /// suffix itself is empty (i.e. "").
  172. ///
  173. /// @return std::string containing the qualified name.
  174. std::string qualifyName(const std::string& partial_name,
  175. const bool trailing_dot) const;
  176. /// @brief Set server FQDN flags based on configuration and a given FQDN
  177. ///
  178. /// Templated wrapper around the analyzeFqdn() allowing that method to
  179. /// be used for either IPv4 or IPv6 processing. This methods resets all
  180. /// of the flags in the response to zero and then sets the S,N, and O
  181. /// flags. Any other flags are the responsibility of the invoking layer.
  182. ///
  183. /// @param fqdn FQDN option from which to read client (inbound) flags
  184. /// @param fqdn_resp FQDN option to update with the server (outbound) flags
  185. /// @tparam T FQDN Option class containing the FQDN data such as
  186. /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
  187. template <class T>
  188. void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
  189. /// @brief Get directional update flags based on server FQDN flags
  190. ///
  191. /// Templated convenience method which determines whether forward and
  192. /// reverse updates should be performed based on a server response version
  193. /// of the FQDN flags. The logic is straight forward and currently not
  194. /// dependent upon configuration specific values:
  195. ///
  196. /// * forward will be true if S_FLAG is true
  197. /// * reverse will be true if N_FLAG is false
  198. ///
  199. /// @param fqdn_resp FQDN option from which to read server (outbound) flags
  200. /// @param [out] forward bool value will be set to true if forward updates
  201. /// should be done, false if not.
  202. /// @param [out] reverse bool value will be set to true if reverse updates
  203. /// should be done, false if not.
  204. /// @tparam T FQDN Option class containing the FQDN data such as
  205. /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
  206. template <class T>
  207. void getUpdateDirections(const T& fqdn_resp, bool& forward, bool& reverse);
  208. /// @brief Set server FQDN name based on configuration and a given FQDN
  209. ///
  210. /// Templated method which adjusts the domain name value and type in
  211. /// a server FQDN from a client (inbound) FQDN and the current
  212. /// configuration. The logic is as follows:
  213. ///
  214. /// If replace-client-name is true or the supplied name is empty, the
  215. /// server FQDN is set to ""/PARTIAL.
  216. ///
  217. /// If replace-client-name is false and the supplied name is a partial
  218. /// name the server FQDN is set to the supplied name qualified by
  219. /// appending the qualifying-suffix.
  220. ///
  221. /// If replace-client-name is false and the supplied name is a fully
  222. /// qualified name, set the server FQDN to the supplied name.
  223. ///
  224. /// @param fqdn FQDN option from which to get client (inbound) name
  225. /// @param fqdn_resp FQDN option to update with the adjusted name
  226. /// @tparam T FQDN Option class containing the FQDN data such as
  227. /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
  228. template <class T>
  229. void adjustDomainName(const T& fqdn, T& fqdn_resp);
  230. /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
  231. ///
  232. /// Places the NameChangeSender into send mode. This instructs the
  233. /// sender to begin dequeuing and transmitting requests and to accept
  234. /// additional requests via the sendRequest() method.
  235. ///
  236. /// @param error_handler application level error handler to cope with
  237. /// sends that complete with a failed status. A valid function must be
  238. /// supplied as the manager cannot know how an application should deal
  239. /// with send failures.
  240. /// @param io_service IOService to be used for sender IO event processing
  241. /// @warning It is up to the invoking layer to ensure the io_service
  242. /// instance used outlives the D2ClientMgr send mode. When the send mode
  243. /// is exited, either explicitly by callind stopSender() or implicitly
  244. /// through D2CLientMgr destruction, any ASIO objects such as sockets or
  245. /// timers will be closed and released. If the io_service goes out of scope
  246. /// first this behavior could be unpredictable.
  247. ///
  248. /// @throw D2ClientError if sender instance is null. Underlying layer
  249. /// may throw NCRSenderExceptions exceptions.
  250. void startSender(D2ClientErrorHandler error_handler,
  251. isc::asiolink::IOService& io_service);
  252. /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
  253. ///
  254. /// Places the NameChangeSender into send mode. This instructs the
  255. /// sender to begin dequeuing and transmitting requests and to accept
  256. /// additional requests via the sendRequest() method. The manager
  257. /// will create a new, private instance of an IOService for the sender
  258. /// to use for IO event processing.
  259. ///
  260. /// @param error_handler application level error handler to cope with
  261. /// sends that complete with a failed status. A valid function must be
  262. /// supplied as the manager cannot know how an application should deal
  263. /// with send failures.
  264. ///
  265. /// @throw D2ClientError if sender instance is null. Underlying layer
  266. /// may throw NCRSenderExceptions exceptions.
  267. void startSender(D2ClientErrorHandler error_handler);
  268. /// @brief Returns true if the sender is in send mode, false otherwise.
  269. ///
  270. /// A true value indicates that the sender is present and in accepting
  271. /// messages for transmission, false otherwise.
  272. bool amSending() const;
  273. /// @brief Disables sending NameChangeRequests to kea-dhcp-ddns
  274. ///
  275. /// Takes the NameChangeSender out of send mode. The sender will stop
  276. /// transmitting requests, though any queued requests remain queued.
  277. /// Attempts to queue additional requests via sendRequest will fail.
  278. ///
  279. /// @throw D2ClientError if sender instance is null. Underlying layer
  280. /// may throw NCRSenderExceptions exceptions.
  281. void stopSender();
  282. /// @brief Send the given NameChangeRequests to kea-dhcp-ddns
  283. ///
  284. /// Passes NameChangeRequests to the NCR sender for transmission to
  285. /// kea-dhcp-ddns. If the sender rejects the message, the client's error
  286. /// handler will be invoked. The most likely cause for rejection is
  287. /// the senders' queue has reached maximum capacity.
  288. ///
  289. /// @param ncr NameChangeRequest to send
  290. ///
  291. /// @throw D2ClientError if sender instance is null or not in send
  292. /// mode. Either of these represents a programmatic error.
  293. void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
  294. /// @brief Calls the client's error handler.
  295. ///
  296. /// Calls the error handler method set by startSender() when an
  297. /// error occurs attempting to send a method. If the error handler
  298. /// throws an exception it will be caught and logged.
  299. ///
  300. /// @param result contains that send outcome status.
  301. /// @param ncr is a pointer to the NameChangeRequest that was attempted.
  302. ///
  303. /// This method is exception safe.
  304. void invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
  305. Result result,
  306. dhcp_ddns::NameChangeRequestPtr& ncr);
  307. /// @brief Returns the number of NCRs queued for transmission.
  308. size_t getQueueSize() const;
  309. /// @brief Returns the maximum number of NCRs allowed in the queue.
  310. size_t getQueueMaxSize() const;
  311. /// @brief Returns the nth NCR queued for transmission.
  312. ///
  313. /// Note that the entry is not removed from the queue.
  314. /// @param index the index of the entry in the queue to fetch.
  315. /// Valid values are 0 (front of the queue) to (queue size - 1).
  316. /// @note This method is for test purposes only.
  317. ///
  318. /// @return Pointer reference to the queue entry.
  319. ///
  320. /// @throw D2ClientError if sender instance is null. Underlying layer
  321. /// may throw NCRSenderExceptions exceptions.
  322. const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
  323. /// @brief Removes all NCRs queued for transmission.
  324. ///
  325. /// @throw D2ClientError if sender instance is null. Underlying layer
  326. /// may throw NCRSenderExceptions exceptions.
  327. void clearQueue();
  328. /// @brief Processes sender IO events
  329. ///
  330. /// Serves as callback registered for the sender's select-fd with IfaceMgr.
  331. /// It instructs the sender to execute the next ready IO handler.
  332. /// It provides an instance method that can be bound via boost::bind, as
  333. /// NameChangeSender is abstract.
  334. void runReadyIO();
  335. /// @brief Suspends sending requests.
  336. ///
  337. /// This method is intended to be used when IO errors occur. It toggles
  338. /// the enable-updates configuration flag to off, and takes the sender
  339. /// out of send mode. Messages in the sender's queue will remain in the
  340. /// queue.
  341. /// @todo This logic may change in NameChangeSender is altered allow
  342. /// queuing while stopped. Currently when a sender is not in send mode
  343. /// it will not accept additional messages.
  344. void suspendUpdates();
  345. protected:
  346. /// @brief Function operator implementing the NCR sender callback.
  347. ///
  348. /// This method is invoked each time the NameChangeSender completes
  349. /// an asynchronous send.
  350. ///
  351. /// @param result contains that send outcome status.
  352. /// @param ncr is a pointer to the NameChangeRequest that was
  353. /// delivered (or attempted).
  354. ///
  355. /// @throw This method MUST NOT throw.
  356. virtual void operator ()(const dhcp_ddns::NameChangeSender::Result result,
  357. dhcp_ddns::NameChangeRequestPtr& ncr);
  358. /// @brief Fetches the sender's select-fd.
  359. ///
  360. /// The select-fd may be used with select() or poll(). If the sender has
  361. /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
  362. /// @note This is only exposed for testing purposes.
  363. ///
  364. /// @return The sender's select-fd
  365. ///
  366. /// @throw D2ClientError if the sender does not exist or is not in send
  367. /// mode.
  368. int getSelectFd();
  369. /// @brief Fetches the select-fd that is currently registered.
  370. ///
  371. /// @return The currently registered select-fd or
  372. /// util::WatchSocket::SOCKET_NOT_VALID.
  373. ///
  374. /// @note This is only exposed for testing purposes.
  375. int getRegisteredSelectFd();
  376. private:
  377. /// @brief Container class for DHCP-DDNS configuration parameters.
  378. D2ClientConfigPtr d2_client_config_;
  379. /// @brief Pointer to the current interface to DHCP-DDNS.
  380. dhcp_ddns::NameChangeSenderPtr name_change_sender_;
  381. /// @brief Private IOService to use if calling layer doesn't wish to
  382. /// supply one.
  383. boost::shared_ptr<asiolink::IOService> private_io_service_;
  384. /// @brief Application supplied error handler invoked when a send
  385. /// completes with a failed status.
  386. D2ClientErrorHandler client_error_handler_;
  387. /// @brief Remembers the select-fd registered with IfaceMgr.
  388. int registered_select_fd_;
  389. };
  390. template <class T>
  391. void
  392. D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
  393. bool server_s = false;
  394. bool server_n = false;
  395. analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
  396. server_s, server_n);
  397. // Reset the flags to zero to avoid triggering N and S both 1 check.
  398. fqdn_resp.resetFlags();
  399. // Set S and N flags.
  400. fqdn_resp.setFlag(T::FLAG_S, server_s);
  401. fqdn_resp.setFlag(T::FLAG_N, server_n);
  402. // Set O flag true if server S overrides client S.
  403. fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
  404. }
  405. template <class T>
  406. void
  407. D2ClientMgr::getUpdateDirections(const T& fqdn_resp,
  408. bool& forward, bool& reverse) {
  409. forward = fqdn_resp.getFlag(T::FLAG_S);
  410. reverse = !(fqdn_resp.getFlag(T::FLAG_N));
  411. }
  412. template <class T>
  413. void
  414. D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
  415. // If we're configured to replace it or the supplied name is blank
  416. // set the response name to blank.
  417. if ((d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_ALWAYS ||
  418. d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_WHEN_PRESENT) ||
  419. fqdn.getDomainName().empty()) {
  420. fqdn_resp.setDomainName("", T::PARTIAL);
  421. } else {
  422. // If the supplied name is partial, qualify it by adding the suffix.
  423. if (fqdn.getDomainNameType() == T::PARTIAL) {
  424. fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName(),true), T::FULL);
  425. }
  426. }
  427. }
  428. /// @brief Defines a pointer for D2ClientMgr instances.
  429. typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
  430. } // namespace isc
  431. } // namespace dhcp
  432. #endif