Coverage Report

Created: 2026-06-30 07:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kea/src/lib/dhcpsrv/d2_client_mgr.cc
Line
Count
Source
1
// Copyright (C) 2014-2025 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>
11
#include <dhcpsrv/d2_client_mgr.h>
12
#include <dhcpsrv/dhcpsrv_log.h>
13
14
#include <functional>
15
#include <string>
16
17
using namespace std;
18
19
namespace isc {
20
namespace dhcp {
21
22
20
D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
23
20
    name_change_sender_(), private_io_service_(),
24
20
    registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) {
25
    // Default constructor initializes with a disabled configuration.
26
20
}
27
28
20
D2ClientMgr::~D2ClientMgr() {
29
20
    stop();
30
20
    stopSender();
31
20
}
32
33
void
34
0
D2ClientMgr::suspendUpdates() {
35
0
    if (ddnsEnabled()) {
36
        /// @todo For now we will disable updates and stop sending.
37
        /// This at least provides a means to shut it off if there are errors.
38
0
        LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES);
39
0
        d2_client_config_->enableUpdates(false);
40
0
        if (name_change_sender_) {
41
0
            stopSender();
42
0
        }
43
0
    }
44
0
}
45
46
void
47
28.8k
D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
48
28.8k
    if (!new_config) {
49
0
        isc_throw(D2ClientError,
50
0
                  "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
51
0
    }
52
53
    // Don't do anything unless configuration values are actually different.
54
28.8k
    if (*d2_client_config_ != *new_config) {
55
        // Make sure we stop sending first.
56
0
        stopSender();
57
0
        if (!new_config->getEnableUpdates()) {
58
            // Updating has been turned off.
59
            // Destroy current sender (any queued requests are tossed).
60
0
            name_change_sender_.reset();
61
0
        } else {
62
0
            dhcp_ddns::NameChangeSenderPtr new_sender;
63
0
            switch (new_config->getNcrProtocol()) {
64
0
            case dhcp_ddns::NCR_UDP: {
65
                // Instantiate a new sender.
66
0
                new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
67
0
                                                new_config->getSenderIp(),
68
0
                                                new_config->getSenderPort(),
69
0
                                                new_config->getServerIp(),
70
0
                                                new_config->getServerPort(),
71
0
                                                new_config->getNcrFormat(),
72
0
                                                shared_from_this(),
73
0
                                                new_config->getMaxQueueSize()));
74
0
                break;
75
0
                }
76
0
            default:
77
                // In theory you can't get here.
78
0
                isc_throw(D2ClientError, "Invalid sender Protocol: "
79
0
                          << new_config->getNcrProtocol());
80
0
                break;
81
0
            }
82
83
            // Transfer queued requests from previous sender to the new one.
84
            /// @todo - Should we consider anything queued to be wrong?
85
            /// If only server values changed content might still be right but
86
            /// if content values changed (e.g. suffix or an override flag)
87
            /// then the queued contents might now be invalid.  There is
88
            /// no way to regenerate them if they are wrong.
89
0
            if (name_change_sender_) {
90
0
                new_sender->assumeQueue(*name_change_sender_);
91
0
            }
92
93
            // Replace the old sender with the new one.
94
0
            name_change_sender_ = new_sender;
95
0
        }
96
0
    }
97
98
    // Update the configuration.
99
28.8k
    d2_client_config_ = new_config;
100
28.8k
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
101
0
              .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
102
0
                   "DHCP_DDNS updates enabled");
103
28.8k
}
104
105
bool
106
74.1k
D2ClientMgr::ddnsEnabled() {
107
74.1k
    return (d2_client_config_->getEnableUpdates());
108
74.1k
}
109
110
const D2ClientConfigPtr&
111
0
D2ClientMgr::getD2ClientConfig() const {
112
0
    return (d2_client_config_);
113
0
}
114
115
void
116
D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
117
                         bool& server_s, bool& server_n,
118
30
                         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
30
    const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
133
134
30
    switch (mask) {
135
20
    case 0:
136
20
        if (!ddns_params.getEnableUpdates()) {
137
20
            server_s = false;
138
20
            server_n = true;
139
20
        } 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
0
            server_s = ddns_params.getOverrideClientUpdate();
143
0
            server_n = false;
144
0
        }
145
20
        break;
146
147
10
    case 1:
148
10
        server_s = ddns_params.getEnableUpdates();
149
10
        server_n = !server_s;
150
10
        break;
151
152
0
    case 2:
153
        // If updates are enabled and we are overriding "no updates" then
154
        // S flag should be true.
155
0
        server_s = (ddns_params.getEnableUpdates() &&
156
0
                    ddns_params.getOverrideNoUpdate());
157
0
        server_n = !server_s;
158
0
        break;
159
160
0
    default:
161
        // RFCs declare this an invalid combination.
162
0
        isc_throw(isc::BadValue,
163
0
                  "Invalid client FQDN - N and S cannot both be 1");
164
0
        break;
165
30
    }
166
30
}
167
168
std::string
169
D2ClientMgr::generateFqdn(const asiolink::IOAddress& address,
170
                          const DdnsParams& ddns_params,
171
0
                          const bool trailing_dot) const {
172
0
    std::string hostname = address.toText();
173
0
    std::replace(hostname.begin(), hostname.end(),
174
0
                 (address.isV4() ? '.' : ':'), '-');
175
176
0
    if (*(hostname.rbegin()) == '-') {
177
0
        hostname.append("0");
178
0
    }
179
180
0
    std::ostringstream gen_name;
181
0
    gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname;
182
0
    return (qualifyName(gen_name.str(), ddns_params, trailing_dot));
183
0
}
184
185
std::string
186
D2ClientMgr::qualifyName(const std::string& partial_name,
187
                         const DdnsParams& ddns_params,
188
14
                         const bool trailing_dot) const {
189
14
    if (partial_name.empty()) {
190
8
        isc_throw(BadValue, "D2ClientMgr::qualifyName"
191
8
                            " - partial_name cannot be an empty string");
192
8
    }
193
194
6
    std::ostringstream gen_name;
195
6
    gen_name << partial_name;
196
6
    std::string suffix = ddns_params.getQualifyingSuffix();
197
6
    if (!suffix.empty() && (partial_name.back() != '.')) {
198
0
        bool suffix_present = true;
199
0
        std::string str = gen_name.str();
200
0
        auto suffix_rit = suffix.rbegin();
201
0
        if (*suffix_rit == '.') {
202
0
            ++suffix_rit;
203
0
        }
204
205
0
        auto gen_rit = str.rbegin();
206
0
        while (suffix_rit != suffix.rend()) {
207
0
            if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) {
208
                // They don't match.
209
0
                suffix_present = false;
210
0
                break;
211
0
            }
212
213
0
            ++suffix_rit;
214
0
            ++gen_rit;
215
0
        }
216
217
        // Catch the case where name has suffix embedded.
218
        // input: foo.barexample.com   suffix: example.com
219
0
        if ((suffix_present) && (suffix_rit == suffix.rend())) {
220
0
            if ((gen_rit != str.rend()) && (*gen_rit != '.')) {
221
0
                suffix_present = false;
222
0
            }
223
0
        }
224
225
0
        if (!suffix_present) {
226
0
            size_t len = str.length();
227
0
            if ((len > 0) && (str[len - 1] != '.')) {
228
0
                gen_name << ".";
229
0
            }
230
231
0
            gen_name << suffix;
232
0
        }
233
0
    }
234
235
6
    std::string str = gen_name.str();
236
6
    size_t len = str.length();
237
238
6
    if (trailing_dot) {
239
        // If trailing dot should be added but there is no trailing dot,
240
        // append it.
241
6
        if ((len > 0) && (str[len - 1] != '.')) {
242
6
            gen_name << ".";
243
6
        }
244
245
6
    } else {
246
        // If the trailing dot should not be appended but it is present,
247
        // remove it.
248
0
        if ((len > 0) && (str[len - 1] == '.')) {
249
0
            gen_name.str(str.substr(0, len-1));
250
0
        }
251
252
0
    }
253
254
6
    return (gen_name.str());
255
14
}
256
257
void
258
0
D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
259
0
    if (amSending()) {
260
0
        return;
261
0
    }
262
263
    // Create a our own service instance when we are not being multiplexed
264
    // into an external service..
265
0
    private_io_service_.reset(new asiolink::IOService());
266
0
    startSender(error_handler, private_io_service_);
267
0
    LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STARTED)
268
0
             .arg(d2_client_config_->toText());
269
0
}
270
271
void
272
D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
273
0
                         const isc::asiolink::IOServicePtr& io_service) {
274
0
    if (amSending()) {
275
0
        return;
276
0
    }
277
278
0
    if (!name_change_sender_)  {
279
0
        isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
280
0
    }
281
282
0
    if (!error_handler) {
283
0
        isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
284
0
    }
285
286
    // Set the error handler.
287
0
    client_error_handler_ = error_handler;
288
289
    // Start the sender on the given service.
290
0
    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
0
    registered_select_fd_ = name_change_sender_->getSelectFd();
296
0
    IfaceMgr::instance().addExternalSocket(registered_select_fd_,
297
0
                                           std::bind(&D2ClientMgr::runReadyIO,
298
0
                                                     this));
299
0
}
300
301
bool
302
20
D2ClientMgr::amSending() const {
303
20
    return (name_change_sender_ && name_change_sender_->amSending());
304
20
}
305
306
void
307
20
D2ClientMgr::stopSender() {
308
    /// Unregister sender's select-fd.
309
20
    if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
310
0
        IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
311
0
        registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
312
0
    }
313
314
    // If its not null, call stop.
315
20
    if (amSending()) {
316
0
        name_change_sender_->stopSending();
317
0
        LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STOPPED);
318
0
    }
319
320
20
    if (private_io_service_) {
321
0
        private_io_service_->stopAndPoll();
322
0
    }
323
20
}
324
325
void
326
0
D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
327
0
    if (!amSending()) {
328
        // This is programmatic error so bust them for it.
329
0
        isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
330
0
    }
331
332
0
    try {
333
0
        name_change_sender_->sendRequest(ncr);
334
0
    } catch (const std::exception& ex) {
335
0
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_NCR_REJECTED)
336
0
                  .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
337
0
        invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR, ncr);
338
0
    }
339
0
}
340
341
void
342
D2ClientMgr::invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
343
                                      Result result,
344
0
                                      dhcp_ddns::NameChangeRequestPtr& ncr) {
345
    // Handler is mandatory to enter send mode but test it just to be safe.
346
0
    if (!client_error_handler_) {
347
0
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
348
0
    } else {
349
        // Handler is not supposed to throw, but catch just in case.
350
0
        try {
351
0
            (client_error_handler_)(result, ncr);
352
0
        } catch (const std::exception& ex) {
353
0
            LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
354
0
                      .arg(ex.what());
355
0
        }
356
0
    }
357
0
}
358
359
size_t
360
0
D2ClientMgr::getQueueSize() const {
361
0
    if (!name_change_sender_) {
362
0
        isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
363
0
    }
364
365
0
    return (name_change_sender_->getQueueSize());
366
0
}
367
368
size_t
369
0
D2ClientMgr::getQueueMaxSize() const {
370
0
    if (!name_change_sender_) {
371
0
        isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
372
0
    }
373
374
0
    return (name_change_sender_->getQueueMaxSize());
375
0
}
376
377
const dhcp_ddns::NameChangeRequestPtr&
378
0
D2ClientMgr::peekAt(const size_t index) const {
379
0
    if (!name_change_sender_) {
380
0
        isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
381
0
    }
382
383
0
    return (name_change_sender_->peekAt(index));
384
0
}
385
386
void
387
0
D2ClientMgr::clearQueue() {
388
0
    if (!name_change_sender_) {
389
0
        isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
390
0
    }
391
392
0
    name_change_sender_->clearSendQueue();
393
0
}
394
395
void
396
D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
397
0
                        dhcp_ddns::NameChangeRequestPtr& ncr) {
398
0
    if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
399
0
        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
400
0
                  DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
401
0
    } else {
402
0
        invokeClientErrorHandler(result, ncr);
403
0
    }
404
0
}
405
406
void
407
20
D2ClientMgr::stop() {
408
20
    name_change_sender_.reset();
409
20
}
410
411
int
412
0
D2ClientMgr::getSelectFd() {
413
0
    if (!amSending()) {
414
0
        isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
415
0
                   " not in send mode");
416
0
    }
417
418
0
    return (name_change_sender_->getSelectFd());
419
0
}
420
421
void
422
0
D2ClientMgr::runReadyIO() {
423
0
    if (!name_change_sender_) {
424
        // This should never happen.
425
0
        isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
426
0
                  " name_change_sender is null");
427
0
    }
428
429
0
    name_change_sender_->runReadyIO();
430
0
}
431
432
}  // namespace dhcp
433
}  // namespace isc