Kea 2.7.8
d2_client_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dhcp/iface_mgr.h>
10#include <dhcp_ddns/ncr_udp.h>
12#include <dhcpsrv/dhcpsrv_log.h>
13
14#include <functional>
15#include <string>
16
17using namespace std;
18
19namespace isc {
20namespace dhcp {
21
22D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
23 name_change_sender_(), private_io_service_(),
24 registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) {
25 // Default constructor initializes with a disabled configuration.
26}
27
32
33void
35 if (ddnsEnabled()) {
39 d2_client_config_->enableUpdates(false);
40 if (name_change_sender_) {
41 stopSender();
42 }
43 }
44}
45
46void
48 if (!new_config) {
50 "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
51 }
52
53 // Don't do anything unless configuration values are actually different.
54 if (*d2_client_config_ != *new_config) {
55 // Make sure we stop sending first.
56 stopSender();
57 if (!new_config->getEnableUpdates()) {
58 // Updating has been turned off.
59 // Destroy current sender (any queued requests are tossed).
60 name_change_sender_.reset();
61 } else {
63 switch (new_config->getNcrProtocol()) {
64 case dhcp_ddns::NCR_UDP: {
65 // Instantiate a new sender.
66 new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
67 new_config->getSenderIp(),
68 new_config->getSenderPort(),
69 new_config->getServerIp(),
70 new_config->getServerPort(),
71 new_config->getNcrFormat(),
72 shared_from_this(),
73 new_config->getMaxQueueSize()));
74 break;
75 }
76 default:
77 // In theory you can't get here.
78 isc_throw(D2ClientError, "Invalid sender Protocol: "
79 << new_config->getNcrProtocol());
80 break;
81 }
82
83 // Transfer queued requests from previous sender to the new one.
89 if (name_change_sender_) {
90 new_sender->assumeQueue(*name_change_sender_);
91 }
92
93 // Replace the old sender with the new one.
94 name_change_sender_ = new_sender;
95 }
96 }
97
98 // Update the configuration.
99 d2_client_config_ = new_config;
101 .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
102 "DHCP_DDNS updates enabled");
103}
104
105bool
107 return (d2_client_config_->getEnableUpdates());
108}
109
112 return (d2_client_config_);
113}
114
115void
116D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
117 bool& server_s, bool& server_n,
118 const DdnsParams& ddns_params) const {
119 // Per RFC 4702 & 4704, the client N and S flags allow the client to
120 // request one of three options:
121 //
122 // N flag S flag Option
123 // ------------------------------------------------------------------
124 // 0 0 client wants to do forward updates (section 3.2)
125 // 0 1 client wants server to do forward updates (section 3.3)
126 // 1 0 client wants no one to do updates (section 3.4)
127 // 1 1 invalid combination
128 // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
129 //
130 // Make a bit mask from the client's flags and use it to set the response
131 // flags accordingly.
132 const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
133
134 switch (mask) {
135 case 0:
136 if (!ddns_params.getEnableUpdates()) {
137 server_s = false;
138 server_n = true;
139 } else {
140 // If updates are enabled and we are overriding client delegation
141 // then S flag should be true. N-flag should be false.
142 server_s = ddns_params.getOverrideClientUpdate();
143 server_n = false;
144 }
145 break;
146
147 case 1:
148 server_s = ddns_params.getEnableUpdates();
149 server_n = !server_s;
150 break;
151
152 case 2:
153 // If updates are enabled and we are overriding "no updates" then
154 // S flag should be true.
155 server_s = (ddns_params.getEnableUpdates() &&
156 ddns_params.getOverrideNoUpdate());
157 server_n = !server_s;
158 break;
159
160 default:
161 // RFCs declare this an invalid combination.
163 "Invalid client FQDN - N and S cannot both be 1");
164 break;
165 }
166}
167
168std::string
170 const DdnsParams& ddns_params,
171 const bool trailing_dot) const {
172 std::string hostname = address.toText();
173 std::replace(hostname.begin(), hostname.end(),
174 (address.isV4() ? '.' : ':'), '-');
175
176 if (*(hostname.rbegin()) == '-') {
177 hostname.append("0");
178 }
179
180 std::ostringstream gen_name;
181 gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname;
182 return (qualifyName(gen_name.str(), ddns_params, trailing_dot));
183}
184
185std::string
186D2ClientMgr::qualifyName(const std::string& partial_name,
187 const DdnsParams& ddns_params,
188 const bool trailing_dot) const {
189 std::ostringstream gen_name;
190
191 gen_name << partial_name;
192 std::string suffix = ddns_params.getQualifyingSuffix();
193 bool suffix_present = true;
194 if (!suffix.empty()) {
195 std::string str = gen_name.str();
196 auto suffix_rit = suffix.rbegin();
197 if (*suffix_rit == '.') {
198 ++suffix_rit;
199 }
200
201 auto gen_rit = str.rbegin();
202 if (*gen_rit == '.') {
203 ++gen_rit;
204 }
205
206 while (suffix_rit != suffix.rend()) {
207 if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) {
208 // They don't match.
209 suffix_present = false;
210 break;
211 }
212
213 ++suffix_rit;
214 ++gen_rit;
215 }
216
217 // Catch the case where name has suffix embedded.
218 // input: foo.barexample.com suffix: example.com
219 if ((suffix_present) && (suffix_rit == suffix.rend())) {
220 if ((gen_rit != str.rend()) && (*gen_rit != '.')) {
221 suffix_present = false;
222 }
223 }
224
225 if (!suffix_present) {
226 size_t len = str.length();
227 if ((len > 0) && (str[len - 1] != '.')) {
228 gen_name << ".";
229 }
230
231 gen_name << suffix;
232 }
233 }
234
235 std::string str = gen_name.str();
236 size_t len = str.length();
237
238 if (trailing_dot) {
239 // If trailing dot should be added but there is no trailing dot,
240 // append it.
241 if ((len > 0) && (str[len - 1] != '.')) {
242 gen_name << ".";
243 }
244
245 } else {
246 // If the trailing dot should not be appended but it is present,
247 // remove it.
248 if ((len > 0) && (str[len - 1] == '.')) {
249 gen_name.str(str.substr(0,len-1));
250 }
251
252 }
253
254 return (gen_name.str());
255}
256
257void
259 if (amSending()) {
260 return;
261 }
262
263 // Create a our own service instance when we are not being multiplexed
264 // into an external service..
265 private_io_service_.reset(new asiolink::IOService());
266 startSender(error_handler, private_io_service_);
268 .arg(d2_client_config_->toText());
269}
270
271void
273 const isc::asiolink::IOServicePtr& io_service) {
274 if (amSending()) {
275 return;
276 }
277
278 if (!name_change_sender_) {
279 isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
280 }
281
282 if (!error_handler) {
283 isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
284 }
285
286 // Set the error handler.
287 client_error_handler_ = error_handler;
288
289 // Start the sender on the given service.
290 name_change_sender_->startSending(io_service);
291
292 // Register sender's select-fd with IfaceMgr.
293 // We need to remember the fd that is registered so we can unregister later.
294 // IO error handling in the sender may alter its select-fd.
295 registered_select_fd_ = name_change_sender_->getSelectFd();
296 IfaceMgr::instance().addExternalSocket(registered_select_fd_,
297 std::bind(&D2ClientMgr::runReadyIO,
298 this));
299}
300
301bool
303 return (name_change_sender_ && name_change_sender_->amSending());
304}
305
306void
309 if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
310 IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
311 registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
312 }
313
314 // If its not null, call stop.
315 if (amSending()) {
316 name_change_sender_->stopSending();
318 }
319
320 if (private_io_service_) {
321 private_io_service_->stopAndPoll();
322 }
323}
324
325void
327 if (!amSending()) {
328 // This is programmatic error so bust them for it.
329 isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
330 }
331
332 try {
333 name_change_sender_->sendRequest(ncr);
334 } catch (const std::exception& ex) {
336 .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
338 }
339}
340
341void
343 Result result,
345 // Handler is mandatory to enter send mode but test it just to be safe.
346 if (!client_error_handler_) {
348 } else {
349 // Handler is not supposed to throw, but catch just in case.
350 try {
351 (client_error_handler_)(result, ncr);
352 } catch (const std::exception& ex) {
354 .arg(ex.what());
355 }
356 }
357}
358
359size_t
361 if (!name_change_sender_) {
362 isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
363 }
364
365 return (name_change_sender_->getQueueSize());
366}
367
368size_t
370 if (!name_change_sender_) {
371 isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
372 }
373
374 return (name_change_sender_->getQueueMaxSize());
375}
376
378D2ClientMgr::peekAt(const size_t index) const {
379 if (!name_change_sender_) {
380 isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
381 }
382
383 return (name_change_sender_->peekAt(index));
384}
385
386void
388 if (!name_change_sender_) {
389 isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
390 }
391
392 name_change_sender_->clearSendQueue();
393}
394
395void
405
406void
408 name_change_sender_.reset();
409}
410
411int
413 if (!amSending()) {
414 isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
415 " not in send mode");
416 }
417
418 return (name_change_sender_->getSelectFd());
419}
420
421void
423 if (!name_change_sender_) {
424 // This should never happen.
425 isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
426 " name_change_sender is null");
427 }
428
429 name_change_sender_->runReadyIO();
430}
431
432} // namespace dhcp
433} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Acts as a storage vault for D2 client configuration.
An exception that is thrown if an error occurs while configuring the D2 DHCP DDNS client.
void invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::Result result, dhcp_ddns::NameChangeRequestPtr &ncr)
Calls the client's error handler.
std::string generateFqdn(const asiolink::IOAddress &address, const DdnsParams &ddns_params, const bool trailing_dot=true) const
Builds a FQDN based on the configuration and given IP address.
void analyzeFqdn(const bool client_s, const bool client_n, bool &server_s, bool &server_n, const DdnsParams &ddns_params) const
Determines server flags based on configuration and client flags.
bool ddnsEnabled()
Convenience method for checking if DHCP-DDNS is enabled.
const D2ClientConfigPtr & getD2ClientConfig() const
Fetches the DHCP-DDNS configuration pointer.
void stop()
Stop the sender.
void suspendUpdates()
Suspends sending requests.
void sendRequest(dhcp_ddns::NameChangeRequestPtr &ncr)
Send the given NameChangeRequests to kea-dhcp-ddns.
size_t getQueueSize() const
Returns the number of NCRs queued for transmission.
void clearQueue()
Removes all NCRs queued for transmission.
void stopSender()
Disables sending NameChangeRequests to kea-dhcp-ddns.
void setD2ClientConfig(D2ClientConfigPtr &new_config)
Updates the DHCP-DDNS client configuration to the given value.
bool amSending() const
Returns true if the sender is in send mode, false otherwise.
int getSelectFd()
Fetches the sender's select-fd.
void runReadyIO()
Processes sender IO events.
D2ClientMgr()
Constructor.
size_t getQueueMaxSize() const
Returns the maximum number of NCRs allowed in the queue.
virtual void operator()(const dhcp_ddns::NameChangeSender::Result result, dhcp_ddns::NameChangeRequestPtr &ncr)
Function operator implementing the NCR sender callback.
const dhcp_ddns::NameChangeRequestPtr & peekAt(const size_t index) const
Returns the nth NCR queued for transmission.
std::string qualifyName(const std::string &partial_name, const DdnsParams &ddns_params, const bool trailing_dot) const
Adds a qualifying suffix to a given domain name.
void startSender(D2ClientErrorHandler error_handler, const isc::asiolink::IOServicePtr &io_service)
Enables sending NameChangeRequests to kea-dhcp-ddns.
Convenience container for conveying DDNS behavioral parameters It is intended to be created per Packe...
Definition ddns_params.h:23
std::string getGeneratedPrefix() const
Returns the Prefix Kea should use when generating domain-names.
std::string getQualifyingSuffix() const
Returns the suffix Kea should use when to qualify partial domain-names.
bool getOverrideNoUpdate() const
Returns whether or not Kea should perform updates, even if client requested no updates.
bool getEnableUpdates() const
Returns whether or not DHCP DDNS updating is enabled.
bool getOverrideClientUpdate() const
Returns whether or not Kea should perform updates, even if client requested delegation.
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition iface_mgr.cc:352
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:54
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition iface_mgr.cc:329
Abstract interface for sending NameChangeRequests.
Definition ncr_io.h:468
Result
Defines the outcome of an asynchronous NCR send.
Definition ncr_io.h:478
Provides the ability to send NameChangeRequests via UDP socket.
Definition ncr_udp.h:442
Provides an IO "ready" semaphore for use with select() or poll() WatchSocket exposes a single open fi...
static const int SOCKET_NOT_VALID
Value used to signify an invalid descriptor.
Defines the D2ClientMgr class.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
boost::shared_ptr< NameChangeSender > NameChangeSenderPtr
Defines a smart pointer to an instance of a sender.
Definition ncr_io.h:857
boost::shared_ptr< NameChangeRequest > NameChangeRequestPtr
Defines a pointer to a NameChangeRequest.
Definition ncr_msg.h:241
isc::log::Logger dhcpsrv_logger("dhcpsrv")
DHCP server library Logger.
Definition dhcpsrv_log.h:56
const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_SENT
boost::shared_ptr< D2ClientConfig > D2ClientConfigPtr
Defines a pointer for D2ClientConfig instances.
const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STOPPED
const isc::log::MessageID DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES
const isc::log::MessageID DHCPSRV_CFGMGR_CFG_DHCP_DDNS
const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STARTED
const int DHCPSRV_DBG_TRACE_DETAIL
Additional information.
Definition dhcpsrv_log.h:38
const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_REJECTED
std::function< void(const dhcp_ddns::NameChangeSender::Result result, dhcp_ddns::NameChangeRequestPtr &ncr)> D2ClientErrorHandler
Defines the type for D2 IO error handler.
const int DHCPSRV_DBG_TRACE
DHCP server library logging levels.
Definition dhcpsrv_log.h:26
const isc::log::MessageID DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION
const isc::log::MessageID DHCPSRV_DHCP_DDNS_HANDLER_NULL
Defines the logger used by the top-level component of kea-lfc.
This file provides UDP socket based implementation for sending and receiving NameChangeRequests.