dhcp6_srv.cc 120 KB
Newer Older
Francis Dupont's avatar
Francis Dupont committed
1
// Copyright (C) 2011-2016 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 8
#include <config.h>

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

46
#include <util/encode/hex.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
47
#include <util/io_utilities.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
48
#include <util/range_utilities.h>
49 50
#include <log/logger.h>
#include <cryptolink/cryptolink.h>
51
#include <cfgrpt/config_report.h>
52

53 54 55 56 57 58 59 60
#ifdef HAVE_MYSQL
#include <dhcpsrv/mysql_lease_mgr.h>
#endif
#ifdef HAVE_PGSQL
#include <dhcpsrv/pgsql_lease_mgr.h>
#endif
#include <dhcpsrv/memfile_lease_mgr.h>

61
#include <boost/bind.hpp>
62
#include <boost/foreach.hpp>
63 64
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
65

66 67
#include <stdlib.h>
#include <time.h>
68 69
#include <iomanip>
#include <fstream>
70
#include <sstream>
71

72
using namespace isc;
73
using namespace isc::asiolink;
74
using namespace isc::cryptolink;
75
using namespace isc::dhcp;
76
using namespace isc::dhcp_ddns;
77
using namespace isc::hooks;
78
using namespace isc::log;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
79
using namespace isc::stats;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
80
using namespace isc::util;
81
using namespace std;
82

83 84 85 86
namespace {

/// Structure that holds registered hook indexes
struct Dhcp6Hooks {
87
    int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
88 89
    int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
    int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
90
    int hook_index_lease6_release_; ///< index for "lease6_release" hook point
91
    int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
92
    int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
93
    int hook_index_lease6_decline_; ///< index for "lease6_decline" hook point
94 95 96

    /// Constructor that registers hook points for DHCPv6 engine
    Dhcp6Hooks() {
97
        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
98 99
        hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
        hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
100
        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
101
        hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
102
        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
103
        hook_index_lease6_decline_ = HooksManager::registerHook("lease6_decline");
104 105 106 107 108 109 110 111 112
    }
};

// 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;

113
/// @brief Creates instance of the Status Code option.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
///
/// 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));
129
    LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL, DHCP6_ADD_GLOBAL_STATUS_CODE)
130 131 132 133 134
        .arg(pkt.getLabel())
        .arg(option_status->dataToText());
    return (option_status);
}

135
/// @brief Creates instance of the Status Code option.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
///
/// 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));
154
    LOG_DEBUG(options6_logger, DBG_DHCP6_DETAIL, DHCP6_ADD_STATUS_CODE_FOR_IA)
155 156 157 158 159 160
        .arg(pkt.getLabel())
        .arg(ia.getIAID())
        .arg(option_status->dataToText());
    return (option_status);
}

161 162
}; // anonymous namespace

163 164
namespace isc {
namespace dhcp {
165

166
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
167

168
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
169
    : port_(port), serverid_(), shutdown_(true), alloc_engine_()
170
{
171

172
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
173

174
    // Initialize objects required for DHCP server operation.
175
    try {
176 177 178 179 180 181
        // 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;
182
        }
183

184 185
        // Create a DUID instance but do not store it into a file.
        DUIDFactory duid_factory;
186 187
        DuidPtr duid = duid_factory.get();
        serverid_.reset(new Option(Option::V6, D6O_SERVERID, duid->getDuid()));
188

189 190 191 192
        // 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));
193

194 195
        /// @todo call loadLibraries() when handling configuration changes

196
    } catch (const std::exception &e) {
197
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
198 199
        return;
    }
200

201 202
    // All done, so can proceed
    shutdown_ = false;
203 204
}

205
Dhcpv6Srv::~Dhcpv6Srv() {
206 207 208 209 210 211 212
    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());
    }

213
    IfaceMgr::instance().closeSockets();
214

215
    LeaseMgrFactory::destroy();
216 217
}

218
void Dhcpv6Srv::shutdown() {
219
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
220 221 222
    shutdown_ = true;
}

223 224 225 226
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

227 228 229 230
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

231
bool
232 233 234 235
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
236
    /// sanityCheck function should deal with it.
237 238 239
    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
240
        // which is being used by server
241
        if (getServerID()->getData() != server_id->getData()){
242
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL_DATA,
243 244 245 246
                      DHCP6_PACKET_DROP_SERVERID_MISMATCH)
                .arg(pkt->getLabel())
                .arg(duidToString(server_id))
                .arg(duidToString(getServerID()));
247 248 249
            return (false);
        }
    }
Francis Dupont's avatar
Francis Dupont committed
250
    // return True if: no serverid received or ServerIDs matching
251 252 253 254 255 256 257 258 259 260 261
    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()) {
262
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_DROP_UNICAST)
263 264
                .arg(pkt->getLabel())
                .arg(pkt->getName());
265 266 267 268 269 270 271 272
            return (false);
        }
        break;
    default:
        // do nothing
        ;
    }
    return (true);
273 274
}

275 276
AllocEngine::ClientContext6
Dhcpv6Srv::createContext(const Pkt6Ptr& pkt) {
277 278
    AllocEngine::ClientContext6 ctx;
    ctx.subnet_ = selectSubnet(pkt);
279
    ctx.duid_ = pkt->getClientId();
280
    ctx.hwaddr_ = getMAC(pkt);
281
    ctx.query_ = pkt;
282 283 284 285 286
    alloc_engine_->findReservation(ctx);

    return (ctx);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
287
bool Dhcpv6Srv::run() {
288
    while (!shutdown_) {
289
        try {
Francis Dupont's avatar
Francis Dupont committed
290
            run_one();
Marcin Siodelski's avatar
Marcin Siodelski committed
291
        } catch (const std::exception& e) {
Francis Dupont's avatar
Francis Dupont committed
292 293
            // General catch-all standard exceptions that are not caught by more
            // specific catches.
294
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION)
295
                .arg(e.what());
Francis Dupont's avatar
Francis Dupont committed
296 297 298
        } catch (...) {
            // General catch-all non-standard exception that are not caught
            // by more specific catches.
299
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION);
300
        }
Francis Dupont's avatar
Francis Dupont committed
301
    }
302

Francis Dupont's avatar
Francis Dupont committed
303 304
    return (true);
}
305

Francis Dupont's avatar
Francis Dupont committed
306 307 308 309
void Dhcpv6Srv::run_one() {
    // client's message and server's response
    Pkt6Ptr query;
    Pkt6Ptr rsp;
310

Francis Dupont's avatar
Francis Dupont committed
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
    try {
        uint32_t timeout = 1000;
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT).arg(timeout);
        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));

        } else {
            LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT_INTERRUPTED)
                .arg(timeout);
339
        }
340

Francis Dupont's avatar
Francis Dupont committed
341 342 343 344 345 346 347 348 349 350 351
    } catch (const SignalInterruptOnSelect) {
        // 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());
    }
352

Francis Dupont's avatar
Francis Dupont committed
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
    // 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());
    }
370

Francis Dupont's avatar
Francis Dupont committed
371 372 373 374 375
    // Timeout may be reached or signal received, which breaks select()
    // with no packet received
    if (!query) {
        return;
    }
376

377
    processPacket(query, rsp);
378

379 380 381
    if (!rsp) {
        return;
    }
382

383
    try {
384

385 386 387 388 389
        // 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_)) {
390 391 392 393 394 395
            CalloutHandlePtr callout_handle = getCalloutHandle(query);

            // Delete previously set arguments
            callout_handle->deleteAllArguments();

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

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

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

410
            /// @todo: Add support for DROP status
411

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

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

418 419 420 421 422 423 424 425 426
        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());
    }
}
427

428 429
void
Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
Francis Dupont's avatar
Francis Dupont committed
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
    // In order to parse the DHCP options, the server needs to use some
    // configuration information such as: existing option spaces, option
    // definitions etc. This is the kind of information which is not
    // available in the libdhcp, so we need to supply our own implementation
    // of the option parsing function here, which would rely on the
    // configuration data.
    query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
                                   _3, _4, _5));

    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);

        // Delete previously set arguments
        callout_handle->deleteAllArguments();

        // 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)
461
                .arg(query->getRemoteAddr().toText())
Francis Dupont's avatar
Francis Dupont committed
462 463 464
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());
            skip_unpack = true;
465 466
        }

Francis Dupont's avatar
Francis Dupont committed
467
        /// @todo: Add support for DROP status.
468

Francis Dupont's avatar
Francis Dupont committed
469 470
        callout_handle->getArgument("query6", query);
    }
471

Francis Dupont's avatar
Francis Dupont committed
472 473 474 475 476
    // 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)
477
                .arg(query->getRemoteAddr().toText())
Francis Dupont's avatar
Francis Dupont committed
478 479 480 481 482 483 484 485 486 487
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());
            query->unpack();
        } 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())
488
                .arg(e.what());
489

Francis Dupont's avatar
Francis Dupont committed
490 491 492 493 494 495
            // 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;
496
        }
Francis Dupont's avatar
Francis Dupont committed
497
    }
498

Francis Dupont's avatar
Francis Dupont committed
499 500
    // Update statistics accordingly for received packet.
    processStatsReceived(query);
501

Francis Dupont's avatar
Francis Dupont committed
502 503 504
    // Check if received query carries server identifier matching
    // server identifier being used by the server.
    if (!testServerID(query)) {
505

Francis Dupont's avatar
Francis Dupont committed
506 507 508 509
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
510

Francis Dupont's avatar
Francis Dupont committed
511 512 513 514
    // 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)) {
515

Francis Dupont's avatar
Francis Dupont committed
516 517 518 519
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
520

Francis Dupont's avatar
Francis Dupont committed
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
    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);

        // Delete previously set arguments
        callout_handle->deleteAllArguments();

        // 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.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP)
                .arg(query->getLabel());
            return;
554
        }
555

Francis Dupont's avatar
Francis Dupont committed
556
        /// @todo: Add support for DROP status.
557

Francis Dupont's avatar
Francis Dupont committed
558 559
        callout_handle->getArgument("query6", query);
    }
560

Francis Dupont's avatar
Francis Dupont committed
561 562
    // Assign this packet to a class, if possible
    classifyPacket(query);
563

Francis Dupont's avatar
Francis Dupont committed
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
    try {
        NameChangeRequestPtr ncr;

        switch (query->getType()) {
        case DHCPV6_SOLICIT:
            rsp = processSolicit(query);
            break;

        case DHCPV6_REQUEST:
            rsp = processRequest(query);
            break;

        case DHCPV6_RENEW:
            rsp = processRenew(query);
            break;

        case DHCPV6_REBIND:
            rsp = processRebind(query);
            break;

        case DHCPV6_CONFIRM:
            rsp = processConfirm(query);
            break;

        case DHCPV6_RELEASE:
            rsp = processRelease(query);
            break;

        case DHCPV6_DECLINE:
            rsp = processDecline(query);
            break;

        case DHCPV6_INFORMATION_REQUEST:
            rsp = processInfRequest(query);
            break;

        default:
            // We received a packet type that we do not recognize.
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
                .arg(static_cast<int>(query->getType()))
                .arg(query->getIface());
            // Only action is to output a message if debug is enabled,
            // and that will be covered by the debug statement before
            // the "switch" statement.
            ;
609
        }
610

Francis Dupont's avatar
Francis Dupont committed
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
    } catch (const RFCViolation& e) {
        LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
            .arg(query->getName())
            .arg(query->getRemoteAddr().toText())
            .arg(e.what());

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

    } 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());
633

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

638 639 640
    if (!rsp) {
        return;
    }
641

642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
    // 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
        rsp->setRemotePort(DHCP6_SERVER_PORT);
    }
664

665 666 667
    rsp->setLocalPort(DHCP6_SERVER_PORT);
    rsp->setIndex(query->getIndex());
    rsp->setIface(query->getIface());
668

669 670
    // Specifies if server should do the packing
    bool skip_pack = false;
671

672 673 674 675 676 677
    // 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_)) {
        CalloutHandlePtr callout_handle = getCalloutHandle(query);
678

679 680
        // Delete all previous arguments
        callout_handle->deleteAllArguments();
681

682 683
        // Set our response
        callout_handle->setArgument("response6", rsp);
684

685 686
        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
687

688 689 690 691 692 693 694 695 696
        // 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;
697
        }
698

699 700
        /// @todo: Add support for DROP status
    }
701

702
    if (!skip_pack) {
Francis Dupont's avatar
Francis Dupont committed
703
        try {
704
            rsp->pack();
705
        } catch (const std::exception& e) {
706 707
            LOG_ERROR(options6_logger, DHCP6_PACK_FAIL).arg(e.what());
            return;
708
        }
709 710 711

    }
}
712

713 714
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
715
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
716

717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
    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();
}

733
void
Tomek Mrugalski's avatar
Tomek Mrugalski committed
734
Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
735 736
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
737 738 739
    if (clientid) {
        answer->addOption(clientid);
    }
740 741
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

742
    // If this is a relayed message, we need to copy relay information
743 744 745
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
746

747 748
}

749
void
750 751
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer,
                                const CfgOptionList&) {
752 753 754 755
    // add server-id
    answer->addOption(getServerID());
}

756 757
void
Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
758 759
                              AllocEngine::ClientContext6& ctx,
                              CfgOptionList& co_list) {
760
    // First subnet configured options
761
    if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
762 763 764 765 766 767 768 769 770 771 772 773
        co_list.push_back(ctx.subnet_->getCfgOption());
    }

    // Each class in the incoming packet
    const ClientClasses& classes = question->getClasses();
    for (ClientClasses::const_iterator cclass = classes.begin();
         cclass != classes.end(); ++cclass) {
        // Find the client class definition for this class
        const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
            getClientClassDictionary()->findClass(*cclass);
        if (!ccdef) {
            // Not found: the class is not configured
774 775 776 777
            if (((*cclass).size() <= VENDOR_CLASS_PREFIX.size()) ||
                ((*cclass).compare(0, VENDOR_CLASS_PREFIX.size(), VENDOR_CLASS_PREFIX) != 0)) {
                // Not a VENDOR_CLASS_* so should be configured
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNCONFIGURED)
778
                    .arg(question->getLabel())
779 780 781
                    .arg(*cclass);
            }
            // Skip it
782 783
            continue;
        }
784 785 786 787
        if (ccdef->getCfgOption()->empty()) {
            // Skip classes which don't configure options
            continue;
        }
788 789 790 791
        co_list.push_back(ccdef->getCfgOption());
    }

    // Last global options
792 793 794
    if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
        co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
    }
795 796
}

797
void
798
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
799
                                  const CfgOptionList& co_list) {
800

801 802
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
803
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
804 805 806 807
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
        (question->getOption(D6O_ORO));

    // Option ORO not found? We're done here then.
808
    if (!option_oro || co_list.empty()) {
809 810
        return;
    }
811

812 813 814
    // Get the list of options that client requested.
    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
815 816 817 818 819
        // Iterate on the configured option list
        for (CfgOptionList::const_iterator copts = co_list.begin();
             copts != co_list.end(); ++copts) {
            OptionDescriptor desc = (*copts)->get("dhcp6", opt);
            // Got it: add it and jump to the outer loop
820 821
            if (desc.option_) {
                answer->addOption(desc.option_);
822
                break;
823
            }
824 825
        }
    }
826 827
}

828
void
829 830 831 832
Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
                                        Pkt6Ptr& answer,
                                        AllocEngine::ClientContext6& ctx,
                                        const CfgOptionList& co_list) {
833

834 835 836 837 838
    // Leave if there is no subnet matching the incoming packet.
    // There is no need to log the error message here because
    // it will be logged in the assignLease() when it fails to
    // pick the suitable subnet. We don't want to duplicate
    // error messages in such case.
839
    if (!ctx.subnet_) {
840 841 842 843 844 845
        return;
    }

    // Try to get the vendor option
    boost::shared_ptr<OptionVendor> vendor_req =
        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
846
    if (!vendor_req || co_list.empty()) {
847 848 849 850 851 852 853 854 855 856 857 858 859 860
        return;
    }

    // Let's try to get ORO within that vendor-option
    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
    /// may have different policies.
    boost::shared_ptr<OptionUint16Array> oro =
        boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO));

    // Option ORO not found. Don't do anything then.
    if (!oro) {
        return;
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
861 862
    uint32_t vendor_id = vendor_req->getVendorId();

863 864 865 866 867 868
    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));

    // Get the list of options that client requested.
    bool added = false;
    const std::vector<uint16_t>& requested_opts = oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
869 870 871 872 873 874 875 876
        for (CfgOptionList::const_iterator copts = co_list.begin();
             copts != co_list.end(); ++copts) {
            OptionDescriptor desc = (*copts)->get(vendor_id, opt);
            if (desc.option_) {
                vendor_rsp->addOption(desc.option_);
                added = true;
                break;
            }
877 878 879 880 881 882 883 884
        }
    }

    if (added) {
        answer->addOption(vendor_rsp);
    }
}

885 886 887
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
888
    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908
    switch (clientid) {
    case MANDATORY:
        if (client_ids.size() != 1) {
            isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
                      << pkt->getName() << ", but " << client_ids.size()
                      << " received");
        }
        break;
    case OPTIONAL:
        if (client_ids.size() > 1) {
            isc_throw(RFCViolation, "Too many (" << client_ids.size()
                      << ") client-id options received in " << pkt->getName());
        }
        break;

    case FORBIDDEN:
        // doesn't make sense - client-id is always allowed
        break;
    }

909
    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
910 911
    switch (serverid) {
    case FORBIDDEN:
912
        if (!server_ids.empty()) {
913
            isc_throw(RFCViolation, "Server-id option was not expected, but "
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928
                      << server_ids.size() << " received in " << pkt->getName());
        }
        break;

    case MANDATORY:
        if (server_ids.size() != 1) {
            isc_throw(RFCViolation, "Invalid number of server-id options received ("
                      << server_ids.size() << "), exactly 1 expected in message "
                      << pkt->getName());
        }
        break;

    case OPTIONAL:
        if (server_ids.size() > 1) {
            isc_throw(RFCViolation, "Too many (" << server_ids.size()
929
                      << ") server-id options received in " << pkt->getName());
930 931 932 933
        }
    }
}

934 935
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
936 937 938 939 940 941 942 943 944
    // Initialize subnet selector with the values used to select the subnet.
    SubnetSelector selector;
    selector.iface_name_ = question->getIface();
    selector.remote_address_ = question->getRemoteAddr();
    selector.first_relay_linkaddr_ = IOAddress("::");
    selector.client_classes_ = question->classes_;

    // Initialize fields specific to relayed messages.
    if (!question->relay_info_.empty()) {
945 946 947 948 949 950 951
        BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, question->relay_info_) {
            if (!relay.linkaddr_.isV6Zero() &&
                !relay.linkaddr_.isV6LinkLocal()) {
                selector.first_relay_linkaddr_ = relay.linkaddr_;
                break;
            }
        }
952 953 954 955
        selector.interface_id_ =
            question->getAnyRelayOption(D6O_INTERFACE_ID,
                                        Pkt6::RELAY_GET_FIRST);
    }
956

957 958
    Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
        getCfgSubnets6()->selectSubnet(selector);
959

960

961
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
962
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
963 964 965 966
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

        // We're reusing callout_handle from previous calls
        callout_handle->deleteAllArguments();
967

Tomek Mrugalski's avatar
Tomek Mrugalski committed
968 969
        // Set new arguments
        callout_handle->setArgument("query6", question);
970
        callout_handle->setArgument("subnet6", subnet);
971 972 973 974

        // We pass pointer to const collection for performance reasons.
        // Otherwise we would get a non-trivial performance penalty each
        // time subnet6_select is called.
975 976 977
        callout_handle->setArgument("subnet6collection",
                                    CfgMgr::instance().getCurrentCfg()->
                                    getCfgSubnets6()->getAll());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
978 979

        // Call user (and server-side) callouts
980
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
981

Francis Dupont's avatar
Francis Dupont committed
982 983 984 985
        // Callouts decided to skip this step. This means that no
        // subnet will be selected. Packet processing will continue,
        // but it will be severely limited (i.e. only global options
        // will be assigned)
986
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
987 988
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_SUBNET6_SELECT_SKIP)
                .arg(question->getLabel());