1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
151
152
153
154
155
156
157
158
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
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
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
|
// Copyright (C) 2014-2022 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef D2_CLIENT_MGR_H
#define D2_CLIENT_MGR_H
/// @file d2_client_mgr.h Defines the D2ClientMgr class.
/// This file defines the class Kea uses to act as a client of the
/// kea-dhcp-ddns module (aka D2).
///
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_io.h>
#include <dhcpsrv/d2_client_cfg.h>
#include <dhcpsrv/srv_config.h>
#include <exceptions/exceptions.h>
#include <boost/algorithm/string.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <stdint.h>
#include <string>
#include <vector>
#include <sstream>
namespace isc {
namespace dhcp {
/// @brief Defines the type for D2 IO error handler.
/// This callback is invoked when a send to kea-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 handler does
/// throw invoking code logs the exception and then swallows it.
typedef
std::function<void(const dhcp_ddns::NameChangeSender::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
///
/// Provides services for managing the current dhcp-ddns configuration and
/// as well as communications with kea-dhcp-ddns. Regarding configuration it
/// provides services to store, update, and access the current dhcp-ddns
/// configuration. As for kea-dhcp-ddns communications, D2ClientMgr creates
/// maintains a NameChangeSender appropriate to the current configuration and
/// provides services to start, stop, and post NCRs to the sender. Additionally
/// there are methods to examine the queue of requests currently waiting for
/// 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,
/// 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.
///
/// 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.
///
class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler,
boost::noncopyable {
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);
/// @brief Convenience method for checking if DHCP-DDNS is enabled.
///
/// @return True if the D2 configuration is enabled.
bool ddnsEnabled();
/// @brief Fetches the DHCP-DDNS configuration pointer.
///
/// @return a reference to the current configuration pointer.
const D2ClientConfigPtr& getD2ClientConfig() const;
/// @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.
/// 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
///
/// @param client_s S Flag from the client's FQDN
/// @param client_n N Flag from the client's FQDN
/// @param server_s [out] S Flag for the server's FQDN
/// @param server_n [out] N Flag for the server's FQDN
/// @param ddns_params DDNS behavioral configuration parameters
///
/// @throw isc::BadValue if client_s and client_n are both 1 as this is
/// an invalid combination per RFCs.
void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
bool& server_n, const DdnsParams& ddns_params) 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)
/// @param ddns_params DDNS behavioral configuration parameters
/// @param trailing_dot A boolean value which indicates whether trailing
/// dot should be appended (if true) or not (false).
///
/// @return std::string containing the generated name.
std::string generateFqdn(const asiolink::IOAddress& address,
const DdnsParams& ddns_params,
const bool trailing_dot = true) 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 that the qualifying suffix will only be appended if the
/// input name does not already end with that suffix.
///
/// @param partial_name domain name to qualify
/// @param ddns_params DDNS behavioral configuration parameters
/// @param trailing_dot A boolean value which when true guarantees the
/// result will end with a "." and when false that the result will not
/// end with a "." Note that this rule is applied even if the qualifying
/// suffix itself is empty (i.e. "").
///
/// @return std::string containing the qualified name.
std::string qualifyName(const std::string& partial_name,
const DdnsParams& ddns_params,
const bool trailing_dot) const;
/// @brief Set server FQDN flags based on configuration and a given FQDN
///
/// Templated wrapper around the analyzeFqdn() allowing that method to
/// 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
/// flags. Any other flags are the responsibility of the invoking layer.
///
/// @param fqdn FQDN option from which to read client (inbound) flags
/// @param fqdn_resp FQDN option to update with the server (outbound) flags
/// @param ddns_params DDNS behavioral configuration parameters
/// @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,
const DdnsParams& ddns_params);
/// @brief Get directional 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_resp FQDN option from which to read server (outbound) flags
/// @param [out] forward bool value will be set to true if forward updates
/// should be done, false if not.
/// @param [out] reverse bool value will be set to true if reverse updates
/// 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);
/// @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.
///
/// If hostname-char-set is not empty, the inbound name will be
/// sanitized. This is done by iterating over the domain name labels,
/// sanitizing each individually, and then concatenating them into a
/// new sanitized name. It is done this way to guard against the case
/// where the hostname-char-set does not protect dots from replacement.
///
/// @param fqdn FQDN option from which to get client (inbound) name
/// @param fqdn_resp FQDN option to update with the adjusted name
/// @param ddns_params DDNS behavioral configuration parameters
/// @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,
const DdnsParams& ddns_params);
/// @brief Enables sending NameChangeRequests to kea-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
/// @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 explicitly by calling 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.
///
/// @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 kea-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 kea-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 kea-dhcp-ddns
///
/// Passes NameChangeRequests to the NCR sender for transmission to
/// kea-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.
///
/// @param ncr NameChangeRequest to send
///
/// @throw D2ClientError if sender instance is null or not in send
/// mode. Either of these represents a programmatic error.
void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
/// @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);
/// @brief Returns the number of NCRs queued for transmission.
size_t getQueueSize() const;
/// @brief Returns the maximum number of NCRs allowed in the queue.
size_t getQueueMaxSize() 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
///
/// 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 std::bind, as
/// NameChangeSender is abstract.
void runReadyIO();
/// @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();
protected:
/// @brief Function operator implementing the NCR sender callback.
///
/// This method is invoked each time the NameChangeSender completes
/// an asynchronous send.
///
/// @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();
/// @brief Fetches the select-fd that is currently registered.
///
/// @return The currently registered select-fd or
/// util::WatchSocket::SOCKET_NOT_VALID.
///
/// @note This is only exposed for testing purposes.
int getRegisteredSelectFd();
private:
/// @brief Container class for DHCP-DDNS configuration parameters.
D2ClientConfigPtr d2_client_config_;
/// @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_;
/// @brief Remembers the select-fd registered with IfaceMgr.
int registered_select_fd_;
};
template <class T>
void
D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp, const DdnsParams& ddns_params) {
bool server_s = false;
bool server_n = false;
analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
server_s, server_n, ddns_params);
// Reset the flags to zero to avoid triggering N and S both 1 check.
fqdn_resp.resetFlags();
// Set S and N flags.
fqdn_resp.setFlag(T::FLAG_S, server_s);
fqdn_resp.setFlag(T::FLAG_N, server_n);
// Set O flag true if server S overrides client S.
fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
}
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));
}
template <class T>
void
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddns_params) {
// If we're configured to replace it or the supplied name is blank
// set the response name to blank.
D2ClientConfig::ReplaceClientNameMode mode = ddns_params.getReplaceClientNameMode();
if ((mode == D2ClientConfig::RCM_ALWAYS || mode == D2ClientConfig::RCM_WHEN_PRESENT) ||
fqdn.getDomainName().empty()) {
fqdn_resp.setDomainName("", T::PARTIAL);
} else {
// Sanitize the name the client sent us, if we're configured to do so.
std::string client_name = fqdn.getDomainName();
isc::util::str::StringSanitizerPtr sanitizer = ddns_params.getHostnameSanitizer();
if (sanitizer) {
// We need the raw text form, so we can replace escaped chars
dns::Name tmp(client_name);
std::string raw_name = tmp.toRawText();
// We do not know if the sanitizer's regexp preserves dots, so
// we'll scrub it label by label. Yeah, lucky us.
// Using boost::split is simpler than using dns::Name::split() as
// that returns Names which have trailing dots etc.
std::vector<std::string> labels;
boost::algorithm::split(labels, raw_name, boost::is_any_of("."));
std::stringstream ss;
for (auto label = labels.begin(); label != labels.end(); ++label ) {
if (label != labels.begin()) {
ss << ".";
}
ss << sanitizer->scrub(*label);
}
client_name = ss.str();
}
// If the supplied name is partial, qualify it by adding the suffix.
if (fqdn.getDomainNameType() == T::PARTIAL) {
fqdn_resp.setDomainName(qualifyName(client_name, ddns_params, true), T::FULL);
} else {
fqdn_resp.setDomainName(client_name, T::FULL);
}
}
}
/// @brief Defines a pointer for D2ClientMgr instances.
typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
} // namespace isc
} // namespace dhcp
#endif
|