d2_client_mgr.h 20.1 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
/// b10-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 38 39 40 41 42 43 44
/// @brief Defines the type for D2 IO error handler.
/// This callback is invoked when a send to b10-dhcp-ddns completes with a
/// 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 54 55 56 57
/// Provides services for managing the current dhcp-ddns configuration and
/// as well as communications with b10-dhcp-ddns.  Regarding configuration it
/// provides services to store, update, and access the current dhcp-ddns
/// configuration.  As for b10-dhcp-ddns communications, D2ClientMgr creates
/// 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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    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)
    ///
    /// @return std::string containing the generated name.
    std::string generateFqdn(const asiolink::IOAddress& address) const;

    /// @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>.
    /// Note it will add a trailing '.' should qualifying-suffix not end with
    /// one.
    ///
    /// @param partial_name domain name to qualify
    ///
    /// @return std::string containing the qualified name.
    std::string qualifyName(const std::string& partial_name) const;

    /// @brief Set server FQDN flags based on configuration and a given FQDN
    ///
    /// Templated wrapper around the analyzeFqdn() allowing that method to
196 197
    /// 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
198
    /// flags.  Any other flags are the responsibility of the invoking layer.
199 200 201
    ///
    /// @param fqdn FQDN option from which to read client (inbound) flags
    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
202 203 204 205
    /// @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);
206

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
    /// @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
    /// @param forward[out] bool value will be set to true if forward udpates
    /// should be done, false if not.
    /// @param reverse[out] bool value will be set to true if reverse udpates
    /// 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);

227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    /// @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
245 246 247 248
    /// @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);
249

250 251 252 253 254 255 256 257 258 259 260
    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
    ///
    /// 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
261 262 263 264 265 266
    /// @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.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void startSender(D2ClientErrorHandler error_handler,
                     isc::asiolink::IOService& io_service);

    /// @brief Enables sending NameChangeRequests to b10-dhcp-ddns
    ///
    /// 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;

    /// @brief Disables sending NameChangeRequests to b10-dhcp-ddns
    ///
    /// 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();

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

319 320 321 322 323 324 325 326 327 328 329 330 331 332
    /// @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);

333 334 335
    /// @brief Returns the number of NCRs queued for transmission.
    size_t getQueueSize() const;

336 337 338
    /// @brief Returns the maximum number of NCRs allowed in the queue.
    size_t getQueueMaxSize() const;

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    /// @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
    ///
360 361 362 363
    /// 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.
364 365
    void runReadyIO();

366 367 368 369 370 371 372 373 374 375 376
    /// @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();

377 378 379 380
protected:
    /// @brief Function operator implementing the NCR sender callback.
    ///
    /// This method is invoked each time the NameChangeSender completes
381
    /// an asynchronous send.
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
    ///
    /// @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();

403 404 405 406 407 408 409 410
    /// @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();

411 412 413
private:
    /// @brief Container class for DHCP-DDNS configuration parameters.
    D2ClientConfigPtr d2_client_config_;
414 415 416 417 418 419 420 421 422 423 424 425

    /// @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_;

426 427
    /// @brief Remembers the select-fd registered with IfaceMgr.
    int registered_select_fd_;
428 429
};

430
template <class T>
431
void
432
D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
433 434
    bool server_s = false;
    bool server_n = false;
435
    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
436 437 438 439 440 441
                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.
442 443
    fqdn_resp.setFlag(T::FLAG_S, server_s);
    fqdn_resp.setFlag(T::FLAG_N, server_n);
444 445

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

449 450 451 452 453 454 455
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));
}
456

457
template <class T>
458
void
459
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
460 461 462 463
    // 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()) {
464
        fqdn_resp.setDomainName("", T::PARTIAL);
465 466
    } else {
        // If the supplied name is partial, qualify it by adding the suffix.
467 468
        if (fqdn.getDomainNameType() == T::PARTIAL) {
            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
469 470 471 472
        }
    }
}

473 474 475 476 477 478 479 480
/// @brief Defines a pointer for D2ClientMgr instances.
typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;


} // namespace isc
} // namespace dhcp

#endif