Kea 2.5.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
29 stop();
30 stopSender();
31}
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 std::ostringstream gen_name;
177 gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname;
178 return (qualifyName(gen_name.str(), ddns_params, trailing_dot));
179}
180
181std::string
182D2ClientMgr::qualifyName(const std::string& partial_name,
183 const DdnsParams& ddns_params,
184 const bool trailing_dot) const {
185 std::ostringstream gen_name;
186
187 gen_name << partial_name;
188 std::string suffix = ddns_params.getQualifyingSuffix();
189 bool suffix_present = true;
190 if (!suffix.empty()) {
191 std::string str = gen_name.str();
192 auto suffix_rit = suffix.rbegin();
193 if (*suffix_rit == '.') {
194 ++suffix_rit;
195 }
196
197 auto gen_rit = str.rbegin();
198 if (*gen_rit == '.') {
199 ++gen_rit;
200 }
201
202 while (suffix_rit != suffix.rend()) {
203 if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) {
204 // They don't match.
205 suffix_present = false;
206 break;
207 }
208
209 ++suffix_rit;
210 ++gen_rit;
211 }
212
213 // Catch the case where name has suffix embedded.
214 // input: foo.barexample.com suffix: example.com
215 if ((suffix_present) && (suffix_rit == suffix.rend())) {
216 if ((gen_rit != str.rend()) && (*gen_rit != '.')) {
217 suffix_present = false;
218 }
219 }
220
221 if (!suffix_present) {
222 size_t len = str.length();
223 if ((len > 0) && (str[len - 1] != '.')) {
224 gen_name << ".";
225 }
226
227 gen_name << suffix;
228 }
229 }
230
231 std::string str = gen_name.str();
232 size_t len = str.length();
233
234 if (trailing_dot) {
235 // If trailing dot should be added but there is no trailing dot,
236 // append it.
237 if ((len > 0) && (str[len - 1] != '.')) {
238 gen_name << ".";
239 }
240
241 } else {
242 // If the trailing dot should not be appended but it is present,
243 // remove it.
244 if ((len > 0) && (str[len - 1] == '.')) {
245 gen_name.str(str.substr(0,len-1));
246 }
247
248 }
249
250 return (gen_name.str());
251}
252
253void
255 if (amSending()) {
256 return;
257 }
258
259 // Create a our own service instance when we are not being multiplexed
260 // into an external service..
261 private_io_service_.reset(new asiolink::IOService());
262 startSender(error_handler, private_io_service_);
264 .arg(d2_client_config_->toText());
265}
266
267void
269 const isc::asiolink::IOServicePtr& io_service) {
270 if (amSending()) {
271 return;
272 }
273
274 if (!name_change_sender_) {
275 isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
276 }
277
278 if (!error_handler) {
279 isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
280 }
281
282 // Set the error handler.
283 client_error_handler_ = error_handler;
284
285 // Start the sender on the given service.
286 name_change_sender_->startSending(io_service);
287
288 // Register sender's select-fd with IfaceMgr.
289 // We need to remember the fd that is registered so we can unregister later.
290 // IO error handling in the sender may alter its select-fd.
291 registered_select_fd_ = name_change_sender_->getSelectFd();
292 IfaceMgr::instance().addExternalSocket(registered_select_fd_,
293 std::bind(&D2ClientMgr::runReadyIO,
294 this));
295}
296
297bool
299 return (name_change_sender_ && name_change_sender_->amSending());
300}
301
302void
305 if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
306 IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
307 registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
308 }
309
310 // If its not null, call stop.
311 if (amSending()) {
312 name_change_sender_->stopSending();
314 }
315
316 if (private_io_service_) {
317 private_io_service_->stop();
318 private_io_service_->restart();
319 try {
320 private_io_service_->poll();
321 } catch (...) {
322 }
323 }
324}
325
326void
328 if (!amSending()) {
329 // This is programmatic error so bust them for it.
330 isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
331 }
332
333 try {
334 name_change_sender_->sendRequest(ncr);
335 } catch (const std::exception& ex) {
337 .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
339 }
340}
341
342void
344 Result result,
346 // Handler is mandatory to enter send mode but test it just to be safe.
347 if (!client_error_handler_) {
349 } else {
350 // Handler is not supposed to throw, but catch just in case.
351 try {
352 (client_error_handler_)(result, ncr);
353 } catch (const std::exception& ex) {
355 .arg(ex.what());
356 }
357 }
358}
359
360size_t
362 if (!name_change_sender_) {
363 isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
364 }
365
366 return (name_change_sender_->getQueueSize());
367}
368
369size_t
371 if (!name_change_sender_) {
372 isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
373 }
374
375 return (name_change_sender_->getQueueMaxSize());
376}
377
379D2ClientMgr::peekAt(const size_t index) const {
380 if (!name_change_sender_) {
381 isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
382 }
383
384 return (name_change_sender_->peekAt(index));
385}
386
387void
389 if (!name_change_sender_) {
390 isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
391 }
392
393 name_change_sender_->clearSendQueue();
394}
395
396void
401 DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
402 } else {
403 invokeClientErrorHandler(result, ncr);
404 }
405}
406
407void
409 name_change_sender_.reset();
410}
411
412int
414 if (!amSending()) {
415 isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
416 " not in send mode");
417 }
418
419 return (name_change_sender_->getSelectFd());
420}
421
422void
424 if (!name_change_sender_) {
425 // This should never happen.
426 isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
427 " name_change_sender is null");
428 }
429
430 name_change_sender_->runReadyIO();
431}
432
433} // namespace dhcp
434} // 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.
Definition: d2_client_cfg.h:57
An exception that is thrown if an error occurs while configuring the D2 DHCP DDNS client.
Definition: d2_client_cfg.h:34
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()
Destructor.
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: srv_config.h:48
std::string getGeneratedPrefix() const
Returns the Prefix Kea should use when generating domain-names.
Definition: srv_config.cc:1066
std::string getQualifyingSuffix() const
Returns the suffix Kea should use when to qualify partial domain-names.
Definition: srv_config.cc:1075
bool getOverrideNoUpdate() const
Returns whether or not Kea should perform updates, even if client requested no updates.
Definition: srv_config.cc:1040
bool getEnableUpdates() const
Returns whether or not DHCP DDNS updating is enabled.
Definition: srv_config.cc:1031
bool getOverrideClientUpdate() const
Returns whether or not Kea should perform updates, even if client requested delegation.
Definition: srv_config.cc:1048
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
static const int SOCKET_NOT_VALID
Value used to signify an invalid descriptor.
Definition: watch_socket.h:50
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.
Definition: d2_client_mgr.h:45
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.