d2_client_mgr.h 20 KB

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