d2_client_mgr.h 20.4 KB
Newer Older
1
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15 16
#ifndef D2_CLIENT_MGR_H
#define D2_CLIENT_MGR_H
17

18
/// @file d2_client_mgr.h Defines the D2ClientMgr class.
19
/// This file defines the class Kea uses to act as a client of the
20
/// kea-dhcp-ddns module (aka D2).
21 22 23
///
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_io.h>
24
#include <dhcpsrv/d2_client_cfg.h>
25 26 27
#include <exceptions/exceptions.h>

#include <boost/shared_ptr.hpp>
28
#include <boost/noncopyable.hpp>
29 30 31 32 33 34 35 36

#include <stdint.h>
#include <string>
#include <vector>

namespace isc {
namespace dhcp {

37
/// @brief Defines the type for D2 IO error handler.
38
/// This callback is invoked when a send to kea-dhcp-ddns completes with a
39 40 41 42 43 44
/// failed status.  This provides the application layer (Kea) with a means to
/// handle the error appropriately.
///
/// @param result Result code of the send operation.
/// @param ncr NameChangeRequest which failed to send.
///
45
/// @note Handlers are expected not to throw. In the event a handler does
46 47 48 49 50
/// throw invoking code logs the exception and then swallows it.
typedef
boost::function<void(const dhcp_ddns::NameChangeSender::Result result,
                     dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;

51 52
/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
///
53
/// Provides services for managing the current dhcp-ddns configuration and
54
/// as well as communications with kea-dhcp-ddns.  Regarding configuration it
55
/// provides services to store, update, and access the current dhcp-ddns
56
/// configuration.  As for kea-dhcp-ddns communications, D2ClientMgr creates
57
/// maintains a NameChangeSender appropriate to the current configuration and
58 59
/// provides services to start, stop, and post NCRs to the sender.  Additionally
/// there are methods to examine the queue of requests currently waiting for
60 61 62 63 64 65
/// transmission.
///
/// The manager also provides the mechanics to integrate the ASIO-based IO
/// used by the NCR IPC with the select-driven IO used by Kea.  Senders expose
/// a file descriptor, the "select-fd" that can monitored for read-readiness
/// with the select() function (or variants).  D2ClientMgr provides a method,
66 67 68 69 70 71 72
/// runReadyIO(), that will instructs the sender to process the next ready
/// ready IO handler on the sender's IOservice.  Track# 3315 extended
/// Kea's IfaceMgr to support the registration of multiple external sockets
/// with callbacks that are then monitored with IO readiness via select().
/// D2ClientMgr registers the sender's select-fd and runReadyIO() with
/// IfaceMgr when entering the send mode and unregisters it when exiting send
/// mode.
73 74 75 76 77 78 79 80 81 82
///
/// To place the manager in send mode, the calling layer must supply an error
/// handler and optionally an IOService instance.  The error handler is invoked
/// if a send completes with a failed status. This provides the calling layer
/// an opportunity act upon the error.
///
/// If the caller supplies an IOService, that service will be used to process
/// the sender's IO.  If not supplied, D2ClientMgr pass a private IOService
/// into the sender.  Using a private service isolates the sender's IO from
/// any other services.
83
///
84 85
class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler,
                    boost::noncopyable {
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
public:
    /// @brief Constructor
    ///
    /// Default constructor which constructs an instance which has DHCP-DDNS
    /// updates disabled.
    D2ClientMgr();

    /// @brief Destructor.
    ~D2ClientMgr();

    /// @brief Updates the DHCP-DDNS client configuration to the given value.
    ///
    /// @param new_config pointer to the new client configuration.
    /// @throw D2ClientError if passed an empty pointer.
    void setD2ClientConfig(D2ClientConfigPtr& new_config);

102
    /// @brief Convenience method for checking if DHCP-DDNS is enabled.
103 104
    ///
    /// @return True if the D2 configuration is enabled.
105
    bool ddnsEnabled();
106 107 108 109 110 111

    /// @brief Fetches the DHCP-DDNS configuration pointer.
    ///
    /// @return a reference to the current configuration pointer.
    const D2ClientConfigPtr& getD2ClientConfig() const;

112 113 114 115 116 117
    /// @brief Determines server flags based on configuration and  client flags.
    ///
    /// This method uses input values for the client's FQDN S and N flags, in
    /// conjunction with the configuration parameters updates-enabled, override-
    /// no-updates, and override-client-updates to determine the values that
    /// should be used for the server's FQDN S and N flags.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    /// The logic in this method is based upon RFCs 4702 and 4704, and is
    /// shown in the following truth table:
    ///
    /// @code
    ///
    /// When Updates are enabled:
    ///
    ///  ON = Override No Updates, OC = Override Client Updates
    ///
    ///  | Client |--------   Server Response Flags   ------------|
    ///  | Flags  | ON=F,OC=F | ON=F,OC=T | ON=T,OC=F | ON=T,OC=T |
    ///  |  N-S   |  N-S-O    |   N-S-O   |   N-S-O   |   N-S-O   |
    ///  ----------------------------------------------------------
    ///  |  0-0   |  0-0-0    |   0-1-1   |   0-0-0   |   0-1-1   |
    ///  |  0-1   |  0-1-0    |   0-1-0   |   0-1-0   |   0-1-0   |
    ///  |  1-0   |  1-0-0    |   1-0-0   |   0-1-1   |   0-1-1   |
    ///
    /// One can then use the server response flags to know when forward and
    /// reverse updates should be performed:
    ///
    ///  - Forward updates should be done when the Server S-Flag is true.
    ///  - Reverse updates should be done when the Server N-Flag is false.
    ///
    /// When Updates are disabled:
    ///
    /// | Client  | Server |
    /// |  N-S    |  N-S-O |
    /// --------------------
    /// |  0-0    | 1-0-0  |
    /// |  0-1    | 1-0-1  |
    /// |  1-0    | 1-0-0  |
    ///
    /// @endcode
151 152 153
    ///
    /// @param client_s  S Flag from the client's FQDN
    /// @param client_n  N Flag from the client's FQDN
154 155 156 157 158
    /// @param server_s [out] S Flag for the server's FQDN
    /// @param server_n [out] N Flag for the server's FQDN
    ///
    /// @throw isc::BadValue if client_s and client_n are both 1 as this is
    /// an invalid combination per RFCs.
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
    void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
                     bool& server_n) const;

    /// @brief Builds a FQDN based on the configuration and given IP address.
    ///
    /// Using the current values for generated-prefix, qualifying-suffix and
    /// an IP address, this method constructs a fully qualified domain name.
    /// It supports both IPv4 and IPv6 addresses.  The format of the name
    /// is as follows:
    ///
    ///     <generated-prefix>-<ip address>.<qualifying-suffix>.
    ///
    /// <ip-address> is the result of IOAddress.toText() with the delimiters
    /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
    ///
    /// @param address IP address from which to derive the name (IPv4 or IPv6)
175 176
    /// @param trailing_dot A boolean value which indicates whether trailing
    /// dot should be appended (if true) or not (false).
177 178
    ///
    /// @return std::string containing the generated name.
179 180
    std::string generateFqdn(const asiolink::IOAddress& address,
                             const bool trailing_dot = true) const;
181 182 183 184 185 186 187 188 189

    /// @brief Adds a qualifying suffix to a given domain name
    ///
    /// Constructs a FQDN based on the configured qualifying-suffix and
    /// a partial domain name as follows:
    ///
    ///     <partial_name>.<qualifying-suffix>.
    ///
    /// @param partial_name domain name to qualify
190 191
    /// @param trailing_dot A boolean value which indicates whether trailing
    /// dot should be appended (if true) or not (false).
192 193
    ///
    /// @return std::string containing the qualified name.
194 195
    std::string qualifyName(const std::string& partial_name,
                            const bool trailing_dot) const;
196 197 198 199

    /// @brief Set server FQDN flags based on configuration and a given FQDN
    ///
    /// Templated wrapper around the analyzeFqdn() allowing that method to
200 201
    /// be used for either IPv4 or IPv6 processing.  This methods resets all
    /// of the flags in the response to zero and then sets the S,N, and O
202
    /// flags.  Any other flags are the responsibility of the invoking layer.
203 204 205
    ///
    /// @param fqdn FQDN option from which to read client (inbound) flags
    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
206 207 208 209
    /// @tparam T FQDN Option class containing the FQDN data such as
    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
    template <class T>
    void adjustFqdnFlags(const T& fqdn, T& fqdn_resp);
210

211 212 213 214 215 216 217 218 219 220 221
    /// @brief Get direcional update flags based on server FQDN flags
    ///
    /// Templated convenience method which determines whether forward and
    /// reverse updates should be performed based on a server response version
    /// of the FQDN flags. The logic is straight forward and currently not
    /// dependent upon configuration specific values:
    ///
    /// * forward will be true if S_FLAG is true
    /// * reverse will be true if N_FLAG is false
    ///
    /// @param fqdn FQDN option from which to read server (outbound) flags
222
    /// @param [out] forward bool value will be set to true if forward udpates
223
    /// should be done, false if not.
224
    /// @param [out] reverse bool value will be set to true if reverse udpates
225 226 227 228 229 230
    /// should be done, false if not.
    /// @tparam T FQDN Option class containing the FQDN data such as
    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
    template <class T>
    void getUpdateDirections(const T& fqdn_resp, bool& forward, bool& reverse);

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
    /// @brief Set server FQDN name based on configuration and a given FQDN
    ///
    /// Templated method which adjusts the domain name value and type in
    /// a server FQDN from a client (inbound) FQDN and the current
    /// configuration.  The logic is as follows:
    ///
    /// If replace-client-name is true or the supplied name is empty, the
    /// server FQDN is set to ""/PARTIAL.
    ///
    /// If replace-client-name is false and the supplied name is a partial
    /// name the server FQDN is set to the supplied name qualified by
    /// appending the qualifying-suffix.
    ///
    /// If replace-client-name is false and the supplied name is a fully
    /// qualified name, set the server FQDN to the supplied name.
    ///
    /// @param fqdn FQDN option from which to get client (inbound) name
    /// @param fqdn_resp FQDN option to update with the adjusted name
249 250 251 252
    /// @tparam T  FQDN Option class containing the FQDN data such as
    /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
    template <class T>
    void adjustDomainName(const T& fqdn, T& fqdn_resp);
253

254
    /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
255 256 257 258 259 260 261 262 263 264
    ///
    /// Places the NameChangeSender into send mode. This instructs the
    /// sender to begin dequeuing and transmitting requests and to accept
    /// additional requests via the sendRequest() method.
    ///
    /// @param error_handler application level error handler to cope with
    /// sends that complete with a failed status.  A valid function must be
    /// supplied as the manager cannot know how an application should deal
    /// with send failures.
    /// @param io_service IOService to be used for sender IO event processing
265 266 267 268 269 270
    /// @warning It is up to the invoking layer to ensure the io_service
    /// instance used outlives the D2ClientMgr send mode. When the send mode
    /// is exited, either expliclity by callind stopSender() or implicitly
    /// through D2CLientMgr destruction, any ASIO objects such as sockets or
    /// timers will be closed and released.  If the io_service goes out of scope
    /// first this behavior could be unpredictable.
271 272 273 274 275 276
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void startSender(D2ClientErrorHandler error_handler,
                     isc::asiolink::IOService& io_service);

277
    /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
    ///
    /// Places the NameChangeSender into send mode. This instructs the
    /// sender to begin dequeuing and transmitting requests and to accept
    /// additional requests via the sendRequest() method.  The manager
    /// will create a new, private instance of an IOService for the sender
    /// to use for IO event processing.
    ///
    /// @param error_handler application level error handler to cope with
    /// sends that complete with a failed status.  A valid function must be
    /// supplied as the manager cannot know how an application should deal
    /// with send failures.
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void startSender(D2ClientErrorHandler error_handler);

    /// @brief Returns true if the sender is in send mode, false otherwise.
    ///
    /// A true value indicates that the sender is present and in accepting
    /// messages for transmission, false otherwise.
    bool amSending() const;

300
    /// @brief Disables sending NameChangeRequests to kea-dhcp-ddns
301 302 303 304 305 306 307 308 309
    ///
    /// Takes the NameChangeSender out of send mode.  The sender will stop
    /// transmitting requests, though any queued requests remain queued.
    /// Attempts to queue additional requests via sendRequest will fail.
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void stopSender();

310
    /// @brief Send the given NameChangeRequests to kea-dhcp-ddns
311 312
    ///
    /// Passes NameChangeRequests to the NCR sender for transmission to
313
    /// kea-dhcp-ddns. If the sender rejects the message, the client's error
314 315
    /// handler will be invoked.  The most likely cause for rejection is
    /// the senders' queue has reached maximum capacity.
316 317 318
    ///
    /// @param ncr NameChangeRequest to send
    ///
319 320
    /// @throw D2ClientError if sender instance is null or not in send
    /// mode.  Either of these represents a programmatic error.
321 322
    void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);

323 324 325 326 327 328 329 330 331 332 333 334 335 336
    /// @brief Calls the client's error handler.
    ///
    /// Calls the error handler method set by startSender() when an
    /// error occurs attempting to send a method.  If the error handler
    /// throws an exception it will be caught and logged.
    ///
    /// @param result contains that send outcome status.
    /// @param ncr is a pointer to the NameChangeRequest that was attempted.
    ///
    /// This method is exception safe.
    void invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
                                  Result result,
                                  dhcp_ddns::NameChangeRequestPtr& ncr);

337 338 339
    /// @brief Returns the number of NCRs queued for transmission.
    size_t getQueueSize() const;

340 341 342
    /// @brief Returns the maximum number of NCRs allowed in the queue.
    size_t getQueueMaxSize() const;

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
    /// @brief Returns the nth NCR queued for transmission.
    ///
    /// Note that the entry is not removed from the queue.
    /// @param index the index of the entry in the queue to fetch.
    /// Valid values are 0 (front of the queue) to (queue size - 1).
    /// @note This method is for test purposes only.
    ///
    /// @return Pointer reference to the queue entry.
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;

    /// @brief Removes all NCRs queued for transmission.
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void clearQueue();

    /// @brief Processes sender IO events
    ///
364 365 366 367
    /// Serves as callback registered for the sender's select-fd with IfaceMgr.
    /// It instructs the sender to execute the next ready IO handler.
    /// It provides an instance method that can be bound via boost::bind, as
    /// NameChangeSender is abstract.
368 369
    void runReadyIO();

370 371 372 373 374 375 376 377 378 379 380
    /// @brief Suspends sending requests.
    ///
    /// This method is intended to be used when IO errors occur.  It toggles
    /// the enable-updates configuration flag to off, and takes the sender
    /// out of send mode.  Messages in the sender's queue will remain in the
    /// queue.
    /// @todo This logic may change in NameChangeSender is altered allow
    /// queuing while stopped.  Currently when a sender is not in send mode
    /// it will not accept additional messages.
    void suspendUpdates();

381 382 383 384
protected:
    /// @brief Function operator implementing the NCR sender callback.
    ///
    /// This method is invoked each time the NameChangeSender completes
385
    /// an asynchronous send.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
    ///
    /// @param result contains that send outcome status.
    /// @param ncr is a pointer to the NameChangeRequest that was
    /// delivered (or attempted).
    ///
    /// @throw This method MUST NOT throw.
    virtual void operator ()(const dhcp_ddns::NameChangeSender::Result result,
                             dhcp_ddns::NameChangeRequestPtr& ncr);

    /// @brief Fetches the sender's select-fd.
    ///
    /// The select-fd may be used with select() or poll().  If the sender has
    /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
    /// @note This is only exposed for testing purposes.
    ///
    /// @return The sender's select-fd
    ///
    /// @throw D2ClientError if the sender does not exist or is not in send
    /// mode.
    int getSelectFd();

407 408 409 410 411 412 413 414
    /// @brief Fetches the select-fd that is currently registered.
    ///
    /// @return The currently registered select-fd or
    /// dhcp_ddns::WatchSocket::INVALID_SOCKET.
    ///
    /// @note This is only exposed for testing purposes.
    int getRegisteredSelectFd();

415 416 417
private:
    /// @brief Container class for DHCP-DDNS configuration parameters.
    D2ClientConfigPtr d2_client_config_;
418 419 420 421 422 423 424 425 426 427 428 429

    /// @brief Pointer to the current interface to DHCP-DDNS.
    dhcp_ddns::NameChangeSenderPtr name_change_sender_;

    /// @brief Private IOService to use if calling layer doesn't wish to
    /// supply one.
    boost::shared_ptr<asiolink::IOService> private_io_service_;

    /// @brief Application supplied error handler invoked when a send
    /// completes with a failed status.
    D2ClientErrorHandler client_error_handler_;

430 431
    /// @brief Remembers the select-fd registered with IfaceMgr.
    int registered_select_fd_;
432 433
};

434
template <class T>
435
void
436
D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
437 438
    bool server_s = false;
    bool server_n = false;
439
    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
440 441 442 443 444 445
                server_s, server_n);

    // Reset the flags to zero to avoid triggering N and S both 1 check.
    fqdn_resp.resetFlags();

    // Set S and N flags.
446 447
    fqdn_resp.setFlag(T::FLAG_S, server_s);
    fqdn_resp.setFlag(T::FLAG_N, server_n);
448 449

    // Set O flag true if server S overrides client S.
450
    fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
451 452
}

453 454 455 456 457 458 459
template <class T>
void
D2ClientMgr::getUpdateDirections(const T& fqdn_resp,
                                 bool& forward, bool& reverse) {
    forward = fqdn_resp.getFlag(T::FLAG_S);
    reverse = !(fqdn_resp.getFlag(T::FLAG_N));
}
460

461
template <class T>
462
void
463
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
464 465 466 467
    // If we're configured to replace it or the supplied name is blank
    // set the response name to blank.
    if (d2_client_config_->getReplaceClientName() ||
        fqdn.getDomainName().empty()) {
468
        fqdn_resp.setDomainName("", T::PARTIAL);
469 470
    } else {
        // If the supplied name is partial, qualify it by adding the suffix.
471
        if (fqdn.getDomainNameType() == T::PARTIAL) {
472
            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName(),true), T::FULL);
473 474 475 476
        }
    }
}

477 478 479 480 481 482 483 484
/// @brief Defines a pointer for D2ClientMgr instances.
typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;


} // namespace isc
} // namespace dhcp

#endif