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
// Copyright (C) 2014-2024 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/.

#include <config.h>

#include <dhcp/iface_mgr.h>
#include <dhcp_ddns/ncr_udp.h>
#include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/dhcpsrv_log.h>

#include <functional><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <string><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace std;

namespace isc {
namespace dhcp {

D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
    name_change_sender_(), private_io_service_(),
    registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) {
    // Default constructor initializes with a disabled configuration.
}

D2ClientMgr::~D2ClientMgr() {
    stop();
    stopSender();
}

void
D2ClientMgr::suspendUpdates() {
    if (ddnsEnabled()) {
        /// @todo For now we will disable updates and stop sending.
        /// This at least provides a means to shut it off if there are errors.
        LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES);
        d2_client_config_->enableUpdates(false);
        if (name_change_sender_) {
            stopSender();
        }
    }
}

void
D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
    if (!new_config) {
        isc_throw(D2ClientError,
                  "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
    }

    // Don't do anything unless configuration values are actually different.
    if (*d2_client_config_ != *new_config) {
        // Make sure we stop sending first.
        stopSender();
        if (!new_config->getEnableUpdates()) {
            // Updating has been turned off.
            // Destroy current sender (any queued requests are tossed).
            name_change_sender_.reset();
        } else {
            dhcp_ddns::NameChangeSenderPtr new_sender;
            switch (new_config->getNcrProtocol()) {
            case dhcp_ddns::NCR_UDP: {
                // Instantiate a new sender.
                new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
                                                new_config->getSenderIp(),
                                                new_config->getSenderPort(),
                                                new_config->getServerIp(),
                                                new_config->getServerPort(),
                                                new_config->getNcrFormat(),
                                                shared_from_this(),
                                                new_config->getMaxQueueSize()));
                break;
                }
            default:
                // In theory you can't get here.
                isc_throw(D2ClientError, "Invalid sender Protocol: "
                          << new_config->getNcrProtocol());
                break;
            }

            // Transfer queued requests from previous sender to the new one.
            /// @todo - Should we consider anything queued to be wrong?
            /// If only server values changed content might still be right but
            /// if content values changed (e.g. suffix or an override flag)
            /// then the queued contents might now be invalid.  There is
            /// no way to regenerate them if they are wrong.
            if (name_change_sender_) {
                new_sender->assumeQueue(*name_change_sender_);
            }

            // Replace the old sender with the new one.
            name_change_sender_ = new_sender;
        }
    }

    // Update the configuration.
    d2_client_config_ = new_config;
    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
              .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
                   "DHCP_DDNS updates enabled");
}

bool
D2ClientMgr::ddnsEnabled() {
    return (d2_client_config_->getEnableUpdates());
}

const D2ClientConfigPtr&
D2ClientMgr::getD2ClientConfig() const {
    return (d2_client_config_);
}

void
D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
                         bool& server_s, bool& server_n,
                         const DdnsParams& ddns_params) const {
    // Per RFC 4702 & 4704, the client N and S flags allow the client to
    // request one of three options:
    //
    //  N flag  S flag   Option
    // ------------------------------------------------------------------
    //    0       0      client wants to do forward updates (section 3.2)
    //    0       1      client wants server to do forward updates (section 3.3)
    //    1       0      client wants no one to do updates (section 3.4)
    //    1       1      invalid combination
    // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
    //
    // Make a bit mask from the client's flags and use it to set the response
    // flags accordingly.
    const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));

    switch (mask) {
    case 0:
        if (!ddns_params.getEnableUpdates()) {
            server_s = false;
            server_n = true;
        } else {
            // If updates are enabled and we are overriding client delegation
            // then S flag should be true.  N-flag should be false.
            server_s = ddns_params.getOverrideClientUpdate();
            server_n = false;
        }
        break;

    case 1:
        server_s = ddns_params.getEnableUpdates();
        server_n = !server_s;
        break;

    case 2:
        // If updates are enabled and we are overriding "no updates" then
        // S flag should be true.
        server_s = (ddns_params.getEnableUpdates() &&
                    ddns_params.getOverrideNoUpdate());
        server_n = !server_s;
        break;

    default:
        // RFCs declare this an invalid combination.
        isc_throw(isc::BadValue,
                  "Invalid client FQDN - N and S cannot both be 1");
        break;
    }
}

std::string
D2ClientMgr::generateFqdn(const asiolink::IOAddress& address,
                          const DdnsParams& ddns_params,
                          const bool trailing_dot) const {
    std::string hostname = address.toText();
    std::replace(hostname.begin(), hostname.end(),
                 (address.isV4() ? '.' : ':'), '-');

    std::ostringstream gen_name;
    gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname;
    return (qualifyName(gen_name.str(), ddns_params, trailing_dot));
}

std::string
D2ClientMgr::qualifyName(const std::string& partial_name,
                         const DdnsParams& ddns_params,
                         const bool trailing_dot) const {
    std::ostringstream gen_name;

    gen_name << partial_name;
    std::string suffix = ddns_params.getQualifyingSuffix();
    bool suffix_present = true;
    if (!suffix.empty()) {
        std::string str = gen_name.str();
        auto suffix_rit = suffix.rbegin();
        if (*suffix_rit == '.') {
            ++suffix_rit;
        }

        auto gen_rit = str.rbegin();
        if (*gen_rit == '.') {
            ++gen_rit;
        }

        while (suffix_rit != suffix.rend()) {
            if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) {
                // They don't match.
                suffix_present = false;
                break;
            }

            ++suffix_rit;
            ++gen_rit;
        }

        // Catch the case where name has suffix embedded.
        // input: foo.barexample.com   suffix: example.com
        if ((suffix_present) && (suffix_rit == suffix.rend())) {
            if ((gen_rit != str.rend()) && (*gen_rit != '.')) {
                suffix_present = false;
            }
        }

        if (!suffix_present) {
            size_t len = str.length();
            if ((len > 0) && (str[len - 1] != '.')) {
                gen_name << ".";
            }

            gen_name << suffix;
        }
    }

    std::string str = gen_name.str();
    size_t len = str.length();

    if (trailing_dot) {
        // If trailing dot should be added but there is no trailing dot,
        // append it.
        if ((len > 0) && (str[len - 1] != '.')) {
            gen_name << ".";
        }

    } else {
        // If the trailing dot should not be appended but it is present,
        // remove it.
        if ((len > 0) && (str[len - 1] == '.')) {
            gen_name.str(str.substr(0,len-1));
        }

    }

    return (gen_name.str());
}

void
D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
    if (amSending()) {
        return;
    }

    // Create a our own service instance when we are not being multiplexed
    // into an external service..
    private_io_service_.reset(new asiolink::IOService());
    startSender(error_handler, private_io_service_);
    LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STARTED)
             .arg(d2_client_config_->toText());
}

void
D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
                         const isc::asiolink::IOServicePtr& io_service) {
    if (amSending()) {
        return;
    }

    if (!name_change_sender_)  {
        isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
    }

    if (!error_handler) {
        isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
    }

    // Set the error handler.
    client_error_handler_ = error_handler;

    // Start the sender on the given service.
    name_change_sender_->startSending(io_service);

    // Register sender's select-fd with IfaceMgr.
    // We need to remember the fd that is registered so we can unregister later.
    // IO error handling in the sender may alter its select-fd.
    registered_select_fd_ = name_change_sender_->getSelectFd();
    IfaceMgr::instance().addExternalSocket(registered_select_fd_,
                                           std::bind(&D2ClientMgr::runReadyIO,
                                                     this));
}

bool
D2ClientMgr::amSending() const {
    return (name_change_sender_ && name_change_sender_->amSending());
}

void
D2ClientMgr::stopSender() {
    /// Unregister sender's select-fd.
    if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
        IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
        registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
    }

    // If its not null, call stop.
    if (amSending()) {
        name_change_sender_->stopSending();
        LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STOPPED);
    }

    if (private_io_service_) {
        private_io_service_->stopAndPoll();
    }
}

void
D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
    if (!amSending()) {
        // This is programmatic error so bust them for it.
        isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
    }

    try {
        name_change_sender_->sendRequest(ncr);
    } catch (const std::exception& ex) {
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_NCR_REJECTED)
                  .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
        invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR, ncr);
    }
}

void
D2ClientMgr::invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
                                      Result result,
                                      dhcp_ddns::NameChangeRequestPtr& ncr) {
    // Handler is mandatory to enter send mode but test it just to be safe.
    if (!client_error_handler_) {
        LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
    } else {
        // Handler is not supposed to throw, but catch just in case.
        try {
            (client_error_handler_)(result, ncr);
        } catch (const std::exception& ex) {
            LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
                      .arg(ex.what());
        }
    }
}

size_t
D2ClientMgr::getQueueSize() const {
    if (!name_change_sender_) {
        isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
    }

    return (name_change_sender_->getQueueSize());
}

size_t
D2ClientMgr::getQueueMaxSize() const {
    if (!name_change_sender_) {
        isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
    }

    return (name_change_sender_->getQueueMaxSize());
}

const dhcp_ddns::NameChangeRequestPtr&
D2ClientMgr::peekAt(const size_t index) const {
    if (!name_change_sender_) {
        isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
    }

    return (name_change_sender_->peekAt(index));
}

void
D2ClientMgr::clearQueue() {
    if (!name_change_sender_) {
        isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
    }

    name_change_sender_->clearSendQueue();
}

void
D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
                        dhcp_ddns::NameChangeRequestPtr& ncr) {
    if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
        LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
                  DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
    } else {
        invokeClientErrorHandler(result, ncr);
    }
}

void
D2ClientMgr::stop() {
    name_change_sender_.reset();
}

int
D2ClientMgr::getSelectFd() {
    if (!amSending()) {
        isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
                   " not in send mode");
    }

    return (name_change_sender_->getSelectFd());
}

void
D2ClientMgr::runReadyIO() {
    if (!name_change_sender_) {
        // This should never happen.
        isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
                  " name_change_sender is null");
    }

    name_change_sender_->runReadyIO();
}

}  // namespace dhcp
}  // namespace isc