dhcp6_srv.cc 145 KB
Newer Older
1
// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6

7
#include <config.h>
8
#include <kea_version.h>
9

10
#include <asiolink/io_address.h>
11
#include <dhcp_ddns/ncr_msg.h>
12
#include <dhcp/dhcp6.h>
13
#include <dhcp/docsis3_option_defs.h>
14
#include <dhcp/duid.h>
15
#include <dhcp/duid_factory.h>
16
#include <dhcp/iface_mgr.h>
17
#include <dhcp/libdhcp++.h>
18
#include <dhcp/option6_addrlst.h>
19
#include <dhcp/option6_client_fqdn.h>
20
#include <dhcp/option6_ia.h>
21
#include <dhcp/option6_iaaddr.h>
22
#include <dhcp/option6_iaprefix.h>
23
#include <dhcp/option6_status_code.h>
24
#include <dhcp/option6_pdexclude.h>
25
#include <dhcp/option_custom.h>
26
#include <dhcp/option_vendor.h>
27
#include <dhcp/option_vendor_class.h>
28
#include <dhcp/option_int_array.h>
29
#include <dhcp/pkt6.h>
30
#include <dhcp6/dhcp6to4_ipc.h>
31 32
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
33
#include <dhcpsrv/cfg_host_operations.h>
34 35 36
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
37
#include <dhcpsrv/ncr_generator.h>
38
#include <dhcpsrv/subnet.h>
39
#include <dhcpsrv/subnet_selector.h>
40
#include <dhcpsrv/utils.h>
41 42
#include <eval/evaluate.h>
#include <eval/eval_messages.h>
43
#include <exceptions/exceptions.h>
44
#include <hooks/callout_handle.h>
45
#include <hooks/hooks_log.h>
46
#include <hooks/hooks_manager.h>
47
#include <stats/stats_mgr.h>
48

49
#include <util/encode/hex.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
50
#include <util/io_utilities.h>
51
#include <util/pointer_util.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
52
#include <util/range_utilities.h>
53 54
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
55
#include <cfgrpt/config_report.h>
56

57 58 59 60 61 62
#ifdef HAVE_MYSQL
#include <dhcpsrv/mysql_lease_mgr.h>
#endif
#ifdef HAVE_PGSQL
#include <dhcpsrv/pgsql_lease_mgr.h>
#endif
Tomek Mrugalski's avatar
Tomek Mrugalski committed
63 64
#ifdef HAVE_CQL
#include <dhcpsrv/cql_lease_mgr.h>
65
#endif
66 67
#include <dhcpsrv/memfile_lease_mgr.h>

68
#include <boost/bind.hpp>
69
#include <boost/foreach.hpp>
70 71
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
72 73
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
74

75
#include <algorithm>
76 77
#include <stdlib.h>
#include <time.h>
78 79
#include <iomanip>
#include <fstream>
80
#include <sstream>
81

82
using namespace isc;
83
using namespace isc::asiolink;
84
using namespace isc::cryptolink;
85
using namespace isc::dhcp;
86
using namespace isc::dhcp_ddns;
87
using namespace isc::hooks;
88
using namespace isc::log;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
89
using namespace isc::stats;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
90
using namespace isc::util;
91
using namespace std;
92

93 94 95 96
namespace {

/// Structure that holds registered hook indexes
struct Dhcp6Hooks {
97 98 99 100 101 102 103 104 105
    int hook_index_buffer6_receive_;  ///< index for "buffer6_receive" hook point
    int hook_index_pkt6_receive_;     ///< index for "pkt6_receive" hook point
    int hook_index_subnet6_select_;   ///< index for "subnet6_select" hook point
    int hook_index_leases6_committed_;///< index for "leases6_committed" hook point
    int hook_index_lease6_release_;   ///< index for "lease6_release" hook point
    int hook_index_pkt6_send_;        ///< index for "pkt6_send" hook point
    int hook_index_buffer6_send_;     ///< index for "buffer6_send" hook point
    int hook_index_lease6_decline_;   ///< index for "lease6_decline" hook point
    int hook_index_host6_identifier_; ///< index for "host6_identifier" hook point
106 107 108

    /// Constructor that registers hook points for DHCPv6 engine
    Dhcp6Hooks() {
109 110 111 112 113 114 115 116 117
        hook_index_buffer6_receive_   = HooksManager::registerHook("buffer6_receive");
        hook_index_pkt6_receive_      = HooksManager::registerHook("pkt6_receive");
        hook_index_subnet6_select_    = HooksManager::registerHook("subnet6_select");
        hook_index_leases6_committed_ = HooksManager::registerHook("leases6_committed");
        hook_index_lease6_release_    = HooksManager::registerHook("lease6_release");
        hook_index_pkt6_send_         = HooksManager::registerHook("pkt6_send");
        hook_index_buffer6_send_      = HooksManager::registerHook("buffer6_send");
        hook_index_lease6_decline_    = HooksManager::registerHook("lease6_decline");
        hook_index_host6_identifier_  = HooksManager::registerHook("host6_identifier");
118 119 120 121 122 123 124 125 126
    }
};

// Declare a Hooks object. As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
Dhcp6Hooks Hooks;

127
/// @brief Creates instance of the Status Code option.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
///
/// This variant of the function is used when the Status Code option
/// is added as a top-level option. It logs the debug message and
/// includes the information about the client and transaction.
///
/// @param pkt Reference to the client's message.
/// @param status_code Numeric status code.
/// @param status_message Status message.
///
/// @return Pointer to the Status Code option.
OptionPtr
createStatusCode(const Pkt6& pkt, const uint16_t status_code,
                 const std::string& status_message) {
    Option6StatusCodePtr option_status(new Option6StatusCode(status_code,
                                                             status_message));
143
    LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL, DHCP6_ADD_GLOBAL_STATUS_CODE)
144 145 146 147 148
        .arg(pkt.getLabel())
        .arg(option_status->dataToText());
    return (option_status);
}

149
/// @brief Creates instance of the Status Code option.
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
///
/// This variant of the function is used when the Status Code option
/// is added to one of the IA options. It logs the debug message and
/// includes the information about the client and transaction as well
/// as IAID of the IA option.
///
/// @param pkt Reference to the client's message.
/// param ia Reference to the IA option to which the Status Code is
/// being added.
/// @param status_code Numeric status code.
/// @param status_message Status message.
///
/// @return Pointer to the Status Code option.
OptionPtr
createStatusCode(const Pkt6& pkt, const Option6IA& ia, const uint16_t status_code,
                 const std::string& status_message) {
    Option6StatusCodePtr option_status(new Option6StatusCode(status_code,
                                                             status_message));
168
    LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL, DHCP6_ADD_STATUS_CODE_FOR_IA)
169 170 171 172 173 174
        .arg(pkt.getLabel())
        .arg(ia.getIAID())
        .arg(option_status->dataToText());
    return (option_status);
}

175 176
}; // anonymous namespace

177 178
namespace isc {
namespace dhcp {
179

180
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
181

182
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
183
    : io_service_(new IOService()), port_(port), serverid_(), shutdown_(true),
184 185
      alloc_engine_(), name_change_reqs_(),
      network_state_(new NetworkState(NetworkState::DHCPv6))
186
{
187

188
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
189

190
    // Initialize objects required for DHCP server operation.
191
    try {
192 193 194 195 196 197
        // Port 0 is used for testing purposes where in most cases we don't
        // rely on the physical interfaces. Therefore, it should be possible
        // to create an object even when there are no usable interfaces.
        if ((port > 0) && (IfaceMgr::instance().countIfaces() == 0)) {
            LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
            return;
198
        }
199

200 201
        // Create a DUID instance but do not store it into a file.
        DUIDFactory duid_factory;
202 203
        DuidPtr duid = duid_factory.get();
        serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
204

205 206 207 208
        // Instantiate allocation engine. The number of allocation attempts equal
        // to zero indicates that the allocation engine will use the number of
        // attempts depending on the pool size.
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 0));
209

210 211
        /// @todo call loadLibraries() when handling configuration changes

212
    } catch (const std::exception &e) {
213
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
214 215
        return;
    }
216

217 218
    // All done, so can proceed
    shutdown_ = false;
219 220
}

221
Dhcpv6Srv::~Dhcpv6Srv() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
222
    discardPackets();
223 224 225 226 227 228 229
    try {
        stopD2();
    } catch(const std::exception& ex) {
        // Highly unlikely, but lets Report it but go on
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_D2STOP_ERROR).arg(ex.what());
    }

230
    try {
231
        Dhcp6to4Ipc::instance().close();
232 233 234 235 236
    } catch(const std::exception& ex) {
        // Highly unlikely, but lets Report it but go on
        // LOG_ERROR(dhcp6_logger, DHCP6_SRV_DHCP4O6_ERROR).arg(ex.what());
    }

237
    IfaceMgr::instance().closeSockets();
238

239
    LeaseMgrFactory::destroy();
240 241 242

    // Explicitly unload hooks
    HooksManager::getHooksManager().unloadLibraries();
243 244
}

245
void Dhcpv6Srv::shutdown() {
246
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
247 248 249
    shutdown_ = true;
}

250 251 252 253
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

254 255 256 257
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

258
bool
259 260 261 262
Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt) {
    /// @todo Currently we always check server identifier regardless if
    /// it is allowed in the received message or not (per RFC3315).
    /// If the server identifier is not allowed in the message, the
263
    /// sanityCheck function should deal with it.
264 265 266
    OptionPtr server_id = pkt->getOption(D6O_SERVERID);
    if (server_id){
        // Let us test received ServerID if it is same as ServerID
Francis Dupont's avatar
Francis Dupont committed
267
        // which is being used by server
268
        if (getServerID()->getData() != server_id->getData()){
269
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL_DATA,
270 271 272 273
                      DHCP6_PACKET_DROP_SERVERID_MISMATCH)
                .arg(pkt->getLabel())
                .arg(duidToString(server_id))
                .arg(duidToString(getServerID()));
274 275 276
            return (false);
        }
    }
Francis Dupont's avatar
Francis Dupont committed
277
    // return True if: no serverid received or ServerIDs matching
278 279 280 281 282 283 284 285 286 287 288
    return (true);
}

bool
Dhcpv6Srv::testUnicast(const Pkt6Ptr& pkt) const {
    switch (pkt->getType()) {
    case DHCPV6_SOLICIT:
    case DHCPV6_CONFIRM:
    case DHCPV6_REBIND:
    case DHCPV6_INFORMATION_REQUEST:
        if (pkt->relay_info_.empty() && !pkt->getLocalAddr().isV6Multicast()) {
289
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_DROP_UNICAST)
290 291
                .arg(pkt->getLabel())
                .arg(pkt->getName());
292 293 294 295 296 297 298 299
            return (false);
        }
        break;
    default:
        // do nothing
        ;
    }
    return (true);
300 301
}

302
void
303 304 305 306
Dhcpv6Srv::initContext(const Pkt6Ptr& pkt,
                       AllocEngine::ClientContext6& ctx,
                       bool& drop) {
    ctx.subnet_ = selectSubnet(pkt, drop);
307 308 309 310
    ctx.duid_ = pkt->getClientId(),
    ctx.fwd_dns_update_ = false;
    ctx.rev_dns_update_ = false;
    ctx.hostname_ = "";
311
    ctx.query_ = pkt;
312
    ctx.callout_handle_ = getCalloutHandle(pkt);
313
    ctx.hwaddr_ = getMAC(pkt);
314

315 316 317 318 319
    if (drop) {
        // Caller will immediately drop the packet so simply return now.
        return;
    }

320 321 322
    // Collect host identifiers if host reservations enabled. The identifiers
    // are stored in order of preference. The server will use them in that
    // order to search for host reservations.
323
    if (ctx.subnet_) {
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
        const ConstCfgHostOperationsPtr cfg =
            CfgMgr::instance().getCurrentCfg()->getCfgHostOperations6();
        BOOST_FOREACH(const Host::IdentifierType& id_type,
                      cfg->getIdentifierTypes()) {
            switch (id_type) {
            case Host::IDENT_DUID:
                if (ctx.duid_) {
                    ctx.addHostIdentifier(id_type, ctx.duid_->getDuid());
                }
                break;

            case Host::IDENT_HWADDR:
                if (ctx.hwaddr_) {
                    ctx.addHostIdentifier(id_type, ctx.hwaddr_->hwaddr_);
                }
                break;
340 341 342 343 344 345
            case Host::IDENT_FLEX:
                // At this point the information in the packet has been unpacked into
                // the various packet fields and option objects has been created.
                // Execute callouts registered for packet6_receive.
                if (HooksManager::calloutsPresent(Hooks.hook_index_host6_identifier_)) {
                    CalloutHandlePtr callout_handle = getCalloutHandle(pkt);
346

347 348 349
                    Host::IdentifierType type = Host::IDENT_FLEX;
                    std::vector<uint8_t> id;

350 351 352 353 354
                    // Use the RAII wrapper to make sure that the callout handle state is
                    // reset when this object goes out of scope. All hook points must do
                    // it to prevent possible circular dependency between the callout
                    // handle and its arguments.
                    ScopedCalloutHandleState callout_handle_state(callout_handle);
355 356 357 358 359 360 361

                    // Pass incoming packet as argument
                    callout_handle->setArgument("query6", pkt);
                    callout_handle->setArgument("id_type", type);
                    callout_handle->setArgument("id_value", id);

                    // Call callouts
362 363
                    HooksManager::callCallouts(Hooks.hook_index_host6_identifier_,
                                               *callout_handle);
364 365 366 367 368 369 370 371 372 373 374 375 376 377

                    callout_handle->getArgument("id_type", type);
                    callout_handle->getArgument("id_value", id);

                    if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_CONTINUE) &&
                        !id.empty()) {

                        LOG_DEBUG(packet6_logger, DBGLVL_TRACE_BASIC, DHCP6_FLEX_ID)
                            .arg(Host::getIdentifierAsText(type, &id[0], id.size()));

                        ctx.addHostIdentifier(type, id);
                    }
                }
                break;
378 379 380 381 382 383 384
            default:
                ;
            }
        }

        // Find host reservations using specified identifiers.
        alloc_engine_->findReservation(ctx);
385
    }
386

387 388 389
    // Set KNOWN builtin class if something was found, UNKNOWN if not.
    if (!ctx.hosts_.empty()) {
        pkt->addClass("KNOWN");
Francis Dupont's avatar
Francis Dupont committed
390 391 392
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
          .arg(pkt->getLabel())
          .arg("KNOWN");
393 394
    } else {
        pkt->addClass("UNKNOWN");
Francis Dupont's avatar
Francis Dupont committed
395 396 397
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
          .arg(pkt->getLabel())
          .arg("UNKNOWN");
398
    }
399 400 401

    // Perform second pass of classification.
    evaluateClasses(pkt, true);
402 403
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
404
bool Dhcpv6Srv::run() {
405
    while (!shutdown_) {
406
        try {
Francis Dupont's avatar
Francis Dupont committed
407
            run_one();
408
            getIOService()->poll();
Marcin Siodelski's avatar
Marcin Siodelski committed
409
        } catch (const std::exception& e) {
Francis Dupont's avatar
Francis Dupont committed
410 411
            // General catch-all standard exceptions that are not caught by more
            // specific catches.
412
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION)
413
                .arg(e.what());
Francis Dupont's avatar
Francis Dupont committed
414 415 416
        } catch (...) {
            // General catch-all non-standard exception that are not caught
            // by more specific catches.
417
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION);
418
        }
Francis Dupont's avatar
Francis Dupont committed
419
    }
420

Francis Dupont's avatar
Francis Dupont committed
421 422
    return (true);
}
423

Francis Dupont's avatar
Francis Dupont committed
424 425 426 427
void Dhcpv6Srv::run_one() {
    // client's message and server's response
    Pkt6Ptr query;
    Pkt6Ptr rsp;
428

Francis Dupont's avatar
Francis Dupont committed
429
    try {
430 431 432 433
        // Set select() timeout to 1s. This value should not be modified
        // because it is important that the select() returns control
        // frequently so as the IOService can be polled for ready handlers.
        uint32_t timeout = 1;
Francis Dupont's avatar
Francis Dupont committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
        query = receivePacket(timeout);

        // Log if packet has arrived. We can't log the detailed information
        // about the DHCP message because it hasn't been unpacked/parsed
        // yet, and it can't be parsed at this point because hooks will
        // have to process it first. The only information available at this
        // point are: the interface, source address and destination addresses
        // and ports.
        if (query) {
            LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC, DHCP6_BUFFER_RECEIVED)
                .arg(query->getRemoteAddr().toText())
                .arg(query->getRemotePort())
                .arg(query->getLocalAddr().toText())
                .arg(query->getLocalPort())
                .arg(query->getIface());

            // Log reception of the packet. We need to increase it early, as
            // any failures in unpacking will cause the packet to be dropped.
            // we will increase type specific packets further down the road.
            // See processStatsReceived().
            StatsMgr::instance().addValue("pkt6-received", static_cast<int64_t>(1));

456
        }
457 458 459 460 461 462
        // We used to log that the wait was interrupted, but this is no longer
        // the case. Our wait time is 1s now, so the lack of query packet more
        // likely means that nothing new appeared within a second, rather than
        // we were interrupted. And we don't want to print a message every
        // second.

463

Francis Dupont's avatar
Francis Dupont committed
464
    } catch (const SignalInterruptOnSelect&) {
Francis Dupont's avatar
Francis Dupont committed
465 466 467 468 469 470 471 472 473 474
        // Packet reception interrupted because a signal has been received.
        // This is not an error because we might have received a SIGTERM,
        // SIGINT or SIGHUP which are handled by the server. For signals
        // that are not handled by the server we rely on the default
        // behavior of the system.
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT_SIGNAL)
            .arg(signal_set_->getNext());
    } catch (const std::exception& e) {
        LOG_ERROR(packet6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
    }
475

Francis Dupont's avatar
Francis Dupont committed
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    // Handle next signal received by the process. It must be called after
    // an attempt to receive a packet to properly handle server shut down.
    // The SIGTERM or SIGINT will be received prior to, or during execution
    // of select() (select is invoked by receivePacket()). When that happens,
    // select will be interrupted. The signal handler will be invoked
    // immediately after select(). The handler will set the shutdown flag
    // and cause the process to terminate before the next select() function
    // is called. If the function was called before receivePacket the
    // process could wait up to the duration of timeout of select() to
    // terminate.
    try {
        handleSignal();
    } catch (const std::exception& e) {
        // An (a standard or ISC) exception occurred.
        LOG_ERROR(dhcp6_logger, DHCP6_HANDLE_SIGNAL_EXCEPTION)
            .arg(e.what());
    }
493

Francis Dupont's avatar
Francis Dupont committed
494 495 496 497 498
    // Timeout may be reached or signal received, which breaks select()
    // with no packet received
    if (!query) {
        return;
    }
499

500
    // If the DHCP service has been globally disabled, drop the packet.
501
    if (!network_state_->isServiceEnabled()) {
502 503 504
        LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL_DATA,
                  DHCP6_PACKET_DROP_DHCP_DISABLED)
            .arg(query->getLabel());
505
        return;
506
    } else {
507 508
        processPacket(query, rsp);
    }
509

510 511 512
    if (!rsp) {
        return;
    }
513

514 515
    CalloutHandlePtr callout_handle = getCalloutHandle(query);
    processPacketBufferSend(callout_handle, rsp);
516
}
517

518
void
519
Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
Francis Dupont's avatar
Francis Dupont committed
520 521 522 523 524 525 526
    bool skip_unpack = false;

    // The packet has just been received so contains the uninterpreted wire
    // data; execute callouts registered for buffer6_receive.
    if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
        CalloutHandlePtr callout_handle = getCalloutHandle(query);

527 528 529 530 531 532
        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);

533 534 535
        // Enable copying options from the packet within hook library.
        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

Francis Dupont's avatar
Francis Dupont committed
536 537 538 539 540 541 542 543 544 545 546 547
        // Pass incoming packet as argument
        callout_handle->setArgument("query6", query);

        // Call callouts
        HooksManager::callCallouts(Hooks.hook_index_buffer6_receive_, *callout_handle);

        // Callouts decided to skip the next processing step. The next
        // processing step would to parse the packet, so skip at this
        // stage means that callouts did the parsing already, so server
        // should skip parsing.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_DETAIL, DHCP6_HOOK_BUFFER_RCVD_SKIP)
548
                .arg(query->getRemoteAddr().toText())
Francis Dupont's avatar
Francis Dupont committed
549 550 551
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());
            skip_unpack = true;
552 553
        }

554 555 556 557 558 559 560 561 562 563 564 565 566 567
        // Callouts decided to drop the received packet
        // The response (rsp) is null so the caller (run_one) will
        // immediately return too.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_DETAIL, DHCP6_HOOK_BUFFER_RCVD_DROP)
                .arg(query->getRemoteAddr().toText())
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());

            // Increase the statistic of dropped packets.
            StatsMgr::instance().addValue("pkt6-receive-drop",
                                          static_cast<int64_t>(1));
            return;
        }
568

Francis Dupont's avatar
Francis Dupont committed
569 570
        callout_handle->getArgument("query6", query);
    }
571

Francis Dupont's avatar
Francis Dupont committed
572 573 574 575 576
    // Unpack the packet information unless the buffer6_receive callouts
    // indicated they did it
    if (!skip_unpack) {
        try {
            LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_UNPACK)
577
                .arg(query->getRemoteAddr().toText())
Francis Dupont's avatar
Francis Dupont committed
578 579 580
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());
            query->unpack();
581 582 583 584 585 586
        } catch (const SkipRemainingOptionsError& e) {
            // An option failed to unpack but we are to attempt to process it
            // anyway.  Log it and let's hope for the best.
            LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL,
                      DHCP6_PACKET_OPTIONS_SKIPPED)
                .arg(e.what());
Francis Dupont's avatar
Francis Dupont committed
587 588 589 590 591 592 593
        } catch (const std::exception &e) {
            // Failed to parse the packet.
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL,
                      DHCP6_PACKET_DROP_PARSE_FAIL)
                .arg(query->getRemoteAddr().toText())
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface())
594
                .arg(e.what());
595

Francis Dupont's avatar
Francis Dupont committed
596 597 598 599 600 601
            // Increase the statistics of parse failures and dropped packets.
            StatsMgr::instance().addValue("pkt6-parse-failed",
                                          static_cast<int64_t>(1));
            StatsMgr::instance().addValue("pkt6-receive-drop",
                                          static_cast<int64_t>(1));
            return;
602
        }
Francis Dupont's avatar
Francis Dupont committed
603
    }
604

Francis Dupont's avatar
Francis Dupont committed
605 606
    // Update statistics accordingly for received packet.
    processStatsReceived(query);
607

Francis Dupont's avatar
Francis Dupont committed
608 609 610
    // Check if received query carries server identifier matching
    // server identifier being used by the server.
    if (!testServerID(query)) {
611

Francis Dupont's avatar
Francis Dupont committed
612 613 614 615
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
616

Francis Dupont's avatar
Francis Dupont committed
617 618 619 620
    // Check if the received query has been sent to unicast or multicast.
    // The Solicit, Confirm, Rebind and Information Request will be
    // discarded if sent to unicast address.
    if (!testUnicast(query)) {
621

Francis Dupont's avatar
Francis Dupont committed
622 623 624 625
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
626

627 628 629
    // Assign this packet to a class, if possible
    classifyPacket(query);

Francis Dupont's avatar
Francis Dupont committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646
    LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_PACKET_RECEIVED)
        .arg(query->getLabel())
        .arg(query->getName())
        .arg(static_cast<int>(query->getType()))
        .arg(query->getRemoteAddr())
        .arg(query->getLocalAddr())
        .arg(query->getIface());
    LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
        .arg(query->getLabel())
        .arg(query->toText());

    // At this point the information in the packet has been unpacked into
    // the various packet fields and option objects has been created.
    // Execute callouts registered for packet6_receive.
    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
        CalloutHandlePtr callout_handle = getCalloutHandle(query);

647 648 649 650 651
        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);
Francis Dupont's avatar
Francis Dupont committed
652

653 654 655
        // Enable copying options from the packet within hook library.
        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

Francis Dupont's avatar
Francis Dupont committed
656 657 658 659 660 661 662 663 664
        // Pass incoming packet as argument
        callout_handle->setArgument("query6", query);

        // Call callouts
        HooksManager::callCallouts(Hooks.hook_index_pkt6_receive_, *callout_handle);

        // Callouts decided to skip the next processing step. The next
        // processing step would to process the packet, so skip at this
        // stage means drop.
665 666
        if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
            (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
Francis Dupont's avatar
Francis Dupont committed
667 668
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP)
                .arg(query->getLabel());
669 670 671
            // Increase the statistic of dropped packets.
            StatsMgr::instance().addValue("pkt6-receive-drop",
                                          static_cast<int64_t>(1));
Francis Dupont's avatar
Francis Dupont committed
672
            return;
673
        }
674

Francis Dupont's avatar
Francis Dupont committed
675 676
        callout_handle->getArgument("query6", query);
    }
677

678 679
    // Reject the message if it doesn't pass the sanity check.
    if (!sanityCheck(query)) {
680 681 682 683
        return;
    }

    if (query->getType() == DHCPV6_DHCPV4_QUERY) {
684 685
        // This call never throws. Should this change, this section must be
        // enclosed in try-catch.
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
        processDhcp4Query(query);
        return;
    }

    // Let's create a simplified client context here.
    AllocEngine::ClientContext6 ctx;
    bool drop = false;
    initContext(query, ctx, drop);

    // Stop here if initContext decided to drop the packet.
    if (drop) {
        return;
    }

    // Park point here.

    try {
        switch (query->getType()) {
        case DHCPV6_SOLICIT:
705
            rsp = processSolicit(ctx);
706 707 708
            break;

        case DHCPV6_REQUEST:
709
            rsp = processRequest(ctx);
710 711 712
            break;

        case DHCPV6_RENEW:
713
            rsp = processRenew(ctx);
714 715 716
            break;

        case DHCPV6_REBIND:
717
            rsp = processRebind(ctx);
718 719 720
            break;

        case DHCPV6_CONFIRM:
721
            rsp = processConfirm(ctx);
722 723 724
            break;

        case DHCPV6_RELEASE:
725
            rsp = processRelease(ctx);
726 727 728
            break;

        case DHCPV6_DECLINE:
729
            rsp = processDecline(ctx);
730 731 732
            break;

        case DHCPV6_INFORMATION_REQUEST:
733
            rsp = processInfRequest(ctx);
734 735 736 737 738 739
            break;

        default:
            return;
        }

Francis Dupont's avatar
Francis Dupont committed
740 741 742 743 744 745 746 747 748 749 750 751 752
    } catch (const std::exception& e) {

        // Catch-all exception (at least for ones based on the isc Exception
        // class, which covers more or less all that are explicitly raised
        // in the Kea code), but also the standard one, which may possibly be
        // thrown from boost code.  Just log the problem and ignore the packet.
        // (The problem is logged as a debug message because debug is
        // disabled by default - it prevents a DDOS attack based on the
        // sending of problem packets.)
        LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
            .arg(query->getName())
            .arg(query->getRemoteAddr().toText())
            .arg(e.what());
753

Francis Dupont's avatar
Francis Dupont committed
754 755 756
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
    }
757

758 759 760
    if (!rsp) {
        return;
    }
761

762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781
    // Process relay-supplied options. It is important to call this very
    // late in the process, because we now have all the options the
    // server wanted to send already set. This is important, because
    // RFC6422, section 6 states:
    //
    //   The server SHOULD discard any options that appear in the RSOO
    //   for which it already has one or more candidates.
    //
    // So we ignore any RSOO options if there's an option with the same
    // code already present.
    processRSOO(query, rsp);

    rsp->setRemoteAddr(query->getRemoteAddr());
    rsp->setLocalAddr(query->getLocalAddr());

    if (rsp->relay_info_.empty()) {
        // Direct traffic, send back to the client directly
        rsp->setRemotePort(DHCP6_CLIENT_PORT);
    } else {
        // Relayed traffic, send back to the relay agent
782
        uint16_t relay_port = checkRelaySourcePort(query);
783
        rsp->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT);
784
    }
785

786 787 788
    rsp->setLocalPort(DHCP6_SERVER_PORT);
    rsp->setIndex(query->getIndex());
    rsp->setIface(query->getIface());
789

790 791
    bool packet_park = false;

792 793
    if (!ctx.fake_allocation_ && (ctx.query_->getType() != DHCPV6_CONFIRM) &&
        (ctx.query_->getType() != DHCPV6_INFORMATION_REQUEST) &&
794
        HooksManager::calloutsPresent(Hooks.hook_index_leases6_committed_)) {
795 796
        CalloutHandlePtr callout_handle = getCalloutHandle(query);

797 798 799 800 801
        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);
802 803 804 805 806 807 808

        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

        // Also pass the corresponding query packet as argument
        callout_handle->setArgument("query6", query);

        Lease6CollectionPtr new_leases(new Lease6Collection());
809 810 811
        if (!ctx.new_leases_.empty()) {
            new_leases->assign(ctx.new_leases_.cbegin(),
                               ctx.new_leases_.cend());
812 813 814 815
        }
        callout_handle->setArgument("leases6", new_leases);

        Lease6CollectionPtr deleted_leases(new Lease6Collection());
816

817
        // Do per IA lists
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
        for (auto const iac : ctx.ias_) {
            if (!iac.old_leases_.empty()) {
                for (auto old_lease : iac.old_leases_) {
                    if (ctx.new_leases_.empty()) {
                        deleted_leases->push_back(old_lease);
                        continue;
                    }
                    bool in_new = false;
                    for (auto const new_lease : ctx.new_leases_) {
                        if ((new_lease->addr_ == old_lease->addr_) &&
                            (new_lease->prefixlen_ == old_lease->prefixlen_)) {
                            in_new = true;
                            break;
                        }
                    }
833 834
                    if (!in_new) {
                        deleted_leases->push_back(old_lease);
835 836
                    }
                }
837 838 839 840 841 842 843 844 845 846 847 848 849 850
            }
        }
        callout_handle->setArgument("deleted_leases6", deleted_leases);

        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_leases6_committed_,
                                   *callout_handle);

        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS,
                      DHCP6_HOOK_LEASES6_COMMITTED_DROP)
                .arg(query->getLabel());
            rsp.reset();

851
        } else if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_PARK) {
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891
            packet_park = true;
        }
    }

    if (!rsp) {
        return;
    }

    // PARKING SPOT after leases6_committed hook point.
    CalloutHandlePtr callout_handle = getCalloutHandle(query);
    if (packet_park) {
        LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS,
                  DHCP6_HOOK_LEASES6_COMMITTED_PARK)
            .arg(query->getLabel());

        // Park the packet. The function we bind here will be executed when the hook
        // library unparks the packet.
        HooksManager::park("leases6_committed", query,
        [this, callout_handle, query, rsp]() mutable {
            processPacketPktSend(callout_handle, query, rsp);
            processPacketBufferSend(callout_handle, rsp);
        });

        // If we have parked the packet, let's reset the pointer to the
        // response to indicate to the caller that it should return, as
        // the packet processing will continue via the callback.
        rsp.reset();

    } else {
        processPacketPktSend(callout_handle, query, rsp);
    }
}

void
Dhcpv6Srv::processPacketPktSend(hooks::CalloutHandlePtr& callout_handle,
                                Pkt6Ptr& query, Pkt6Ptr& rsp) {
    if (!rsp) {
        return;
    }

892 893
    // Specifies if server should do the packing
    bool skip_pack = false;
894

895 896 897 898 899
    // Server's reply packet now has all options and fields set.
    // Options are represented by individual objects, but the
    // output wire data has not been prepared yet.
    // Execute all callouts registered for packet6_send
    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
900

901 902 903 904 905 906
        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);

907 908 909
        // Enable copying options from the packets within hook library.
        ScopedEnableOptionsCopy<Pkt6> query_resp_options_copy(query, rsp);

910 911 912
        // Pass incoming packet as argument
        callout_handle->setArgument("query6", query);

913 914
        // Set our response
        callout_handle->setArgument("response6", rsp);
915

916 917
        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
918

919 920 921 922 923 924 925 926 927
        // Callouts decided to skip the next processing step. The next
        // processing step would to pack the packet (create wire data).
        // That step will be skipped if any callout sets skip flag.
        // It essentially means that the callout already did packing,
        // so the server does not have to do it again.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP)
                .arg(rsp->getLabel());
            skip_pack = true;
928
        }
929

930 931 932 933 934 935 936
        /// Callouts decided to drop the packet.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_DROP)
                .arg(rsp->getLabel());
            rsp.reset();
            return;
        }
937
    }
938

939
    if (!skip_pack) {
Francis Dupont's avatar
Francis Dupont committed
940
        try {
941
            rsp->pack();
942
        } catch (const std::exception& e) {
943 944
            LOG_ERROR(options6_logger, DHCP6_PACK_FAIL).arg(e.what());
            return;
945
        }
946 947 948

    }
}
949

950 951 952 953 954 955 956 957 958 959 960 961 962 963
void
Dhcpv6Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
                                   Pkt6Ptr& rsp) {
    if (!rsp) {
        return;
    }

    try {
        // Now all fields and options are constructed into output wire buffer.
        // Option objects modification does not make sense anymore. Hooks
        // can only manipulate wire buffer at this stage.
        // Let's execute all callouts registered for buffer6_send
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {

964 965 966 967 968
            // Use the RAII wrapper to make sure that the callout handle state is
            // reset when this object goes out of scope. All hook points must do
            // it to prevent possible circular dependency between the callout
            // handle and its arguments.
            ScopedCalloutHandleState callout_handle_state(callout_handle);
969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006

            // Enable copying options from the packet within hook library.
            ScopedEnableOptionsCopy<Pkt6> response6_options_copy(rsp);

            // Pass incoming packet as argument
            callout_handle->setArgument("response6", rsp);

            // Call callouts
            HooksManager::callCallouts(Hooks.hook_index_buffer6_send_,
                                       *callout_handle);

            // Callouts decided to skip the next processing step. The next
            // processing step would to parse the packet, so skip at this
            // stage means drop.
            if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
                (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
                LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS,
                          DHCP6_HOOK_BUFFER_SEND_SKIP)
                    .arg(rsp->getLabel());
                return;
            }

            callout_handle->getArgument("response6", rsp);
        }

        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_RESPONSE_DATA)
            .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());

        sendPacket(rsp);

        // Update statistics accordingly for sent packet.
        processStatsSent(rsp);

    } catch (const std::exception& e) {
        LOG_ERROR(packet6_logger, DHCP6_PACKET_SEND_FAIL).arg(e.what());
    }
}

1007 1008
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
1009
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1010

1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
    OptionBuffer data = opt->getData();

    bool colon = false;
    for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
        if (colon) {
            tmp << ":";
        }
        tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
        if (!colon) {
            colon = true;
        }
    }

    return tmp.str();
}

1027
void
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1028
Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1029 1030
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
1031 1032 1033
    if (clientid) {
        answer->addOption(clientid);
    }
1034 1035
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

1036
    // If this is a relayed message, we need to copy relay information
1037 1038 1039
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
1040

1041 1042
}

1043
void
1044 1045
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer,
                                const CfgOptionList&) {
1046 1047 1048 1049
    // add server-id
    answer->addOption(getServerID());
}

1050 1051
void
Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
1052 1053
                              AllocEngine::ClientContext6& ctx,
                              CfgOptionList& co_list) {
1054
    // Firstly, host specific options.
1055 1056
    if (ctx.currentHost() && !ctx.currentHost()->getCfgOption6()->empty()) {
        co_list.push_back(ctx.currentHost()->getCfgOption6());
1057 1058
    }

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
    // Secondly, pool specific options. Pools are defined within a subnet, so
    // if there is no subnet, there is nothing to do.
    if (ctx.subnet_) {
        BOOST_FOREACH(const AllocEngine::ResourceType& resource,
                      ctx.allocated_resources_) {
            PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ?
                                                Lease::TYPE_NA : Lease::TYPE_PD,
                                                resource.first, false);
            if (pool && !pool->getCfgOption()->empty()) {
                co_list.push_back(pool->getCfgOption());
            }
        }
    };