d2_client_mgr.h 15.8 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 28 29 30 31 32 33 34 35
#include <exceptions/exceptions.h>

#include <boost/shared_ptr.hpp>

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

namespace isc {
namespace dhcp {

36 37 38 39 40 41 42 43 44 45 46 47 48 49
/// @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.
///
/// @note Handlers are expected not to throw. In the event a hanlder does
/// 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;

50 51
/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
///
52 53 54 55 56
/// 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
57 58
/// provides services to start, stop, and post NCRs to the sender.  Additionally
/// there are methods to examine the queue of requests currently waiting for
59 60 61 62 63 64
/// 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,
65 66 67 68 69 70 71
/// 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.
72 73 74 75 76 77 78 79 80 81
///
/// 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.
82
///
83
class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler {
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
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);

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

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

110 111 112 113 114 115
    /// @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.
116
    /// The logic in this method is based upon RFCs 4702 and 4704.
117 118 119
    ///
    /// @param client_s  S Flag from the client's FQDN
    /// @param client_n  N Flag from the client's FQDN
120 121 122 123 124
    /// @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.
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 151 152 153 154 155 156 157 158 159 160 161
    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
162 163
    /// 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
164
    /// flags.  Any other flags are the responsibility of the invoking layer.
165 166 167
    ///
    /// @param fqdn FQDN option from which to read client (inbound) flags
    /// @param fqdn_resp FQDN option to update with the server (outbound) flags
168 169 170 171
    /// @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);
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

    /// @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
191 192 193 194
    /// @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);
195

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    /// @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
    ///
    /// @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
    /// b10-dhcp-ddns.
    ///
    /// @param ncr NameChangeRequest to send
    ///
    /// @throw D2ClientError if sender instance is null. Underlying layer
    /// may throw NCRSenderExceptions exceptions.
    void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);

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

    /// @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
    ///
281 282 283 284
    /// 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.
285 286 287 288 289 290
    void runReadyIO();

protected:
    /// @brief Function operator implementing the NCR sender callback.
    ///
    /// This method is invoked each time the NameChangeSender completes
291
    /// an asynchronous send.
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    ///
    /// @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();

313 314 315 316 317 318 319 320
    /// @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();

321 322 323
private:
    /// @brief Container class for DHCP-DDNS configuration parameters.
    D2ClientConfigPtr d2_client_config_;
324 325 326 327 328 329 330 331 332 333 334 335

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

336 337
    /// @brief Remembers the select-fd registered with IfaceMgr.
    int registered_select_fd_;
338 339
};

340
template <class T>
341
void
342
D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp) {
343 344
    bool server_s = false;
    bool server_n = false;
345
    analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
346 347 348 349 350 351
                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.
352 353
    fqdn_resp.setFlag(T::FLAG_S, server_s);
    fqdn_resp.setFlag(T::FLAG_N, server_n);
354 355

    // Set O flag true if server S overrides client S.
356
    fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
357 358 359
}


360
template <class T>
361
void
362
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
363 364 365 366
    // 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()) {
367
        fqdn_resp.setDomainName("", T::PARTIAL);
368 369
    } else {
        // If the supplied name is partial, qualify it by adding the suffix.
370 371
        if (fqdn.getDomainNameType() == T::PARTIAL) {
            fqdn_resp.setDomainName(qualifyName(fqdn.getDomainName()), T::FULL);
372 373 374 375
        }
    }
}

376 377 378 379 380 381 382 383
/// @brief Defines a pointer for D2ClientMgr instances.
typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;


} // namespace isc
} // namespace dhcp

#endif