dhcp6_srv.cc 106 KB
Newer Older
1
// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15 16
#include <config.h>

17
#include <asiolink/io_address.h>
18
#include <dhcp_ddns/ncr_msg.h>
19
#include <dhcp/dhcp6.h>
20
#include <dhcp/docsis3_option_defs.h>
21
#include <dhcp/duid.h>
22
#include <dhcp/iface_mgr.h>
23
#include <dhcp/libdhcp++.h>
24
#include <dhcp/option6_addrlst.h>
25
#include <dhcp/option6_client_fqdn.h>
26
#include <dhcp/option6_ia.h>
27
#include <dhcp/option6_iaaddr.h>
28
#include <dhcp/option6_iaprefix.h>
29
#include <dhcp/option_custom.h>
30
#include <dhcp/option_vendor.h>
31
#include <dhcp/option_vendor_class.h>
32
#include <dhcp/option_int_array.h>
33
#include <dhcp/pkt6.h>
34 35
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
36
#include <dhcpsrv/callout_handle_store.h>
37 38 39 40
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
41
#include <dhcpsrv/utils.h>
42
#include <exceptions/exceptions.h>
43 44 45
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
#include <util/encode/hex.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
46
#include <util/io_utilities.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
47
#include <util/range_utilities.h>
48

49
#include <boost/bind.hpp>
50
#include <boost/foreach.hpp>
51 52
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
53

54 55
#include <stdlib.h>
#include <time.h>
56 57
#include <iomanip>
#include <fstream>
58
#include <sstream>
59

60
using namespace isc;
61
using namespace isc::asiolink;
62
using namespace isc::dhcp_ddns;
63
using namespace isc::dhcp;
64
using namespace isc::hooks;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
65
using namespace isc::util;
66
using namespace std;
67

68 69 70 71
namespace {

/// Structure that holds registered hook indexes
struct Dhcp6Hooks {
72
    int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
73 74
    int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
    int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
75
    int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
76
    int hook_index_lease6_rebind_;  ///< index for "lease6_rebind" hook point
77
    int hook_index_lease6_release_; ///< index for "lease6_release" hook point
78
    int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
79
    int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
80 81 82

    /// Constructor that registers hook points for DHCPv6 engine
    Dhcp6Hooks() {
83
        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
84 85
        hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
        hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
86
        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
87
        hook_index_lease6_rebind_   = HooksManager::registerHook("lease6_rebind");
88
        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
89
        hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
90
        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
91 92 93 94 95 96 97 98 99 100 101
    }
};

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

}; // anonymous namespace

102 103
namespace isc {
namespace dhcp {
104

105
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
106

107 108 109 110 111 112 113 114
/// @brief file name of a server-id file
///
/// Server must store its duid in persistent storage that must not change
/// between restarts. This is name of the file that is created in dataDir
/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
/// double digit hex values separated by colons format, e.g.
/// 01:ff:02:03:06:80:90:ab:cd:ef. Server will create it during first
/// run and then use it afterwards.
115
static const char* SERVER_DUID_FILE = "kea-dhcp6-serverid";
116

117
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
118
:alloc_engine_(), serverid_(), port_(port), shutdown_(true)
119
{
120

121
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
122

123
    // Initialize objects required for DHCP server operation.
124
    try {
125 126 127 128 129 130
        // 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;
131
        }
132

133 134 135
        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
        if (loadServerID(duid_file)) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
136
                .arg(duidToString(getServerID()))
137 138 139 140 141 142 143 144 145 146 147 148
                .arg(duid_file);
        } else {
            generateServerID();
            LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
                .arg(duidToString(getServerID()))
                .arg(duid_file);

            if (!writeServerID(duid_file)) {
                LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
                    .arg(duid_file);
            }
        }
149

150 151 152
        // Instantiate allocation engine
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));

153 154
        /// @todo call loadLibraries() when handling configuration changes

155
    } catch (const std::exception &e) {
156
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
157 158
        return;
    }
159

160 161
    // All done, so can proceed
    shutdown_ = false;
162 163
}

164
Dhcpv6Srv::~Dhcpv6Srv() {
165
    IfaceMgr::instance().closeSockets();
166

167
    LeaseMgrFactory::destroy();
168 169
}

170
void Dhcpv6Srv::shutdown() {
171
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
172 173 174
    shutdown_ = true;
}

175 176 177 178
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

179 180 181 182
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

183
bool
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
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
    /// sanityCheck function should deal with it. We may rethink this
    /// design if we decide that it is appropriate to check at this stage
    /// of message processing that the server identifier must or must not
    /// be present. In such case however, the logic checking server id
    /// will have to be removed from sanityCheck and placed here instead,
    /// to avoid duplicate checks.
    OptionPtr server_id = pkt->getOption(D6O_SERVERID);
    if (server_id){
        // Let us test received ServerID if it is same as ServerID
        // which is beeing used by server
        if (getServerID()->getData() != server_id->getData()){
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
                .arg(pkt->getName())
                .arg(pkt->getTransid())
                .arg(pkt->getIface());
            return (false);
        }
    }
    // retun True if: no serverid received or ServerIDs matching
    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()) {
            return (false);
        }
        break;
    default:
        // do nothing
        ;
    }
    return (true);
226 227
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
228
bool Dhcpv6Srv::run() {
229
    while (!shutdown_) {
230
        /// @todo Calculate actual timeout to the next event (e.g. lease
Tomek Mrugalski's avatar
Tomek Mrugalski committed
231 232 233 234 235
        /// expiration) once we have lease database. The idea here is that
        /// it is possible to do everything in a single process/thread.
        /// For now, we are just calling select for 1000 seconds. There
        /// were some issues reported on some systems when calling select()
        /// with too large values. Unfortunately, I don't recall the details.
236 237
        //cppcheck-suppress variableScope This is temporary anyway
        const int timeout = 1000;
238

Tomek Mrugalski's avatar
Tomek Mrugalski committed
239
        // client's message and server's response
240
        Pkt6Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
241
        Pkt6Ptr rsp;
242

243
        try {
244
            query = receivePacket(timeout);
Marcin Siodelski's avatar
Marcin Siodelski committed
245
        } catch (const std::exception& e) {
246 247 248
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

249 250 251 252 253 254 255 256 257 258 259 260
        // 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 recivePacket()). 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.
        handleSignal();

261 262
        // Timeout may be reached or signal received, which breaks select()
        // with no packet received
263 264 265
        if (!query) {
            continue;
        }
266

267 268 269 270 271 272 273 274 275
        // 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));

276
        bool skip_unpack = false;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
277

Tomek Mrugalski's avatar
Tomek Mrugalski committed
278 279
        // The packet has just been received so contains the uninterpreted wire
        // data; execute callouts registered for buffer6_receive.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
280
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
281
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
282

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

286 287
            // Pass incoming packet as argument
            callout_handle->setArgument("query6", query);
288

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

292 293
            // Callouts decided to skip the next processing step. The next
            // processing step would to parse the packet, so skip at this
Tomek Mrugalski's avatar
Tomek Mrugalski committed
294 295
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
296 297
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
298
                skip_unpack = true;
299 300
            }

301 302
            callout_handle->getArgument("query6", query);
        }
303

Tomek Mrugalski's avatar
Tomek Mrugalski committed
304 305
        // Unpack the packet information unless the buffer6_receive callouts
        // indicated they did it
306
        if (!skip_unpack) {
307
            if (!query->unpack()) {
308 309
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
310 311
                continue;
            }
312
        }
313 314
        // Check if received query carries server identifier matching
        // server identifier being used by the server.
315 316 317 318 319 320 321 322 323
        if (!testServerID(query)) {
            continue;
        }

        // 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)) {
            continue;
324 325
        }

326 327 328 329 330 331 332
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
            .arg(query->getName());
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
            .arg(static_cast<int>(query->getType()))
            .arg(query->getBuffer().getLength())
            .arg(query->toText());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
333 334 335
        // At this point the information in the packet has been unpacked into
        // the various packet fields and option objects has been cretated.
        // Execute callouts registered for packet6_receive.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
336
        if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
            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->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
                continue;
            }
355

356 357
            callout_handle->getArgument("query6", query);
        }
358

359 360 361
        // Assign this packet to a class, if possible
        classifyPacket(query);

362
        try {
363
                NameChangeRequestPtr ncr;
364 365 366
            switch (query->getType()) {
            case DHCPV6_SOLICIT:
                rsp = processSolicit(query);
367
                    break;
368

369 370 371
            case DHCPV6_REQUEST:
                rsp = processRequest(query);
                break;
372

373 374 375
            case DHCPV6_RENEW:
                rsp = processRenew(query);
                break;
376

377 378 379
            case DHCPV6_REBIND:
                rsp = processRebind(query);
                break;
380

381 382 383
            case DHCPV6_CONFIRM:
                rsp = processConfirm(query);
                break;
384

385 386 387 388 389 390 391 392 393 394 395
            case DHCPV6_RELEASE:
                rsp = processRelease(query);
                break;

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

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

397
            default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
398 399 400 401
                // We received a packet type that we do not recognize.
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
                    .arg(static_cast<int>(query->getType()))
                    .arg(query->getIface());
402 403 404 405
                // Only action is to output a message if debug is enabled,
                // and that will be covered by the debug statement before
                // the "switch" statement.
                ;
406
            }
407

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
        } catch (const RFCViolation& e) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
                .arg(query->getName())
                .arg(query->getRemoteAddr().toText())
                .arg(e.what());

        } catch (const isc::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 BIND 10 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(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
                .arg(query->getName())
                .arg(query->getRemoteAddr().toText())
                .arg(e.what());
        }
428

429 430 431
        if (rsp) {
            rsp->setRemoteAddr(query->getRemoteAddr());
            rsp->setLocalAddr(query->getLocalAddr());
432 433 434 435 436 437 438 439 440

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

441 442 443 444
            rsp->setLocalPort(DHCP6_SERVER_PORT);
            rsp->setIndex(query->getIndex());
            rsp->setIface(query->getIface());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
445
            // Specifies if server should do the packing
446 447
            bool skip_pack = false;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
448 449 450
            // 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.
451
            // Execute all callouts registered for packet6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
452
            if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
453
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
454

455 456 457 458 459
                // Delete all previous arguments
                callout_handle->deleteAllArguments();

                // Set our response
                callout_handle->setArgument("response6", rsp);
460

461 462 463 464
                // Call all installed callouts
                HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);

                // Callouts decided to skip the next processing step. The next
Tomek Mrugalski's avatar
Tomek Mrugalski committed
465 466 467 468
                // 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.
469 470
                if (callout_handle->getSkip()) {
                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
471
                    skip_pack = true;
472
                }
473
            }
474

475 476 477
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                      DHCP6_RESPONSE_DATA)
                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
478

479
            if (!skip_pack) {
480 481 482 483 484
                try {
                    rsp->pack();
                } catch (const std::exception& e) {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
                        .arg(e.what());
485 486
                    continue;
                }
487

488 489
            }

490
            try {
491

Tomek Mrugalski's avatar
Tomek Mrugalski committed
492 493 494
                // 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.
495
                // Let's execute all callouts registered for buffer6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
496
                if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
497
                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
498

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

502
                    // Pass incoming packet as argument
Tomek Mrugalski's avatar
Tomek Mrugalski committed
503
                    callout_handle->setArgument("response6", rsp);
504

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

508
                    // Callouts decided to skip the next processing step. The next
509 510
                    // processing step would to parse the packet, so skip at this
                    // stage means drop.
511
                    if (callout_handle->getSkip()) {
512
                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
513 514
                        continue;
                    }
515

516
                    callout_handle->getArgument("response6", rsp);
517
                }
518 519 520

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

523 524
                sendPacket(rsp);
            } catch (const std::exception& e) {
525 526
                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                    .arg(e.what());
527
            }
528 529 530
        }
    }

531
    return (true);
532
}
533

534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
bool Dhcpv6Srv::loadServerID(const std::string& file_name) {

    // load content of the file into a string
    fstream f(file_name.c_str(), ios::in);
    if (!f.is_open()) {
        return (false);
    }

    string hex_string;
    f >> hex_string;
    f.close();

    // remove any spaces
    boost::algorithm::erase_all(hex_string, " ");

    // now remove :
    /// @todo: We should check first if the format is sane.
    /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
    boost::algorithm::erase_all(hex_string, ":");

    std::vector<uint8_t> bin;

    // Decode the hex string and store it in bin (which happens
    // to be OptionBuffer format)
    isc::util::encode::decodeHex(hex_string, bin);

    // Now create server-id option
    serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));

    return (true);
}

566 567
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
568
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
569

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    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();
}

586 587
bool
Dhcpv6Srv::writeServerID(const std::string& file_name) {
588 589 590 591 592 593
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
594
    return (true);
595
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
596

597 598
void
Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
599 600 601 602 603 604 605

    /// @todo: This code implements support for DUID-LLT (the recommended one).
    /// We should eventually add support for other DUID types: DUID-LL, DUID-EN
    /// and DUID-UUID

    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
606
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
607 608
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
609
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
610 611 612 613
        // statement, but let's keep them separated as perhaps one day
        // we will grow knobs to selectively turn them on or off. Also,
        // this code is used only *once* during first start on a new machine
        // and then server-id is stored. (or at least it will be once
614
        // DUID storage is implemented)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
615 616 617

        // I wish there was a this_is_a_real_physical_interface flag...

Tomek Mrugalski's avatar
Tomek Mrugalski committed
618 619 620 621 622
        // MAC address should be at least 6 bytes. Although there is no such
        // requirement in any RFC, all decent physical interfaces (Ethernet,
        // WiFi, Infiniband, etc.) have 6 bytes long MAC address. We want to
        // base our DUID on real hardware address, rather than virtual
        // interface that pretends that underlying IP address is its MAC.
623
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
624 625 626
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
627
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
628 629 630 631
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
632
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
633 634 635 636
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
637
        // Some interfaces (like lo on Linux) report 6-bytes long
638
        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
Tomek Mrugalski's avatar
Tomek Mrugalski committed
639
        // to generate DUID.
640
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
641 642 643 644 645 646 647 648 649 650 651
            continue;
        }

        // Ok, we have useful MAC. Let's generate DUID-LLT based on
        // it. See RFC3315, Section 9.2 for details.

        // DUID uses seconds since midnight of 01-01-2000, time() returns
        // seconds since 01-01-1970. DUID_TIME_EPOCH substution corrects that.
        time_t seconds = time(NULL);
        seconds -= DUID_TIME_EPOCH;

652
        OptionBuffer srvid(8 + iface->getMacLen());
Stephen Morris's avatar
Stephen Morris committed
653
        // We know that the buffer is more than 8 bytes long at this point.
654 655 656 657
        writeUint16(DUID::DUID_LLT, &srvid[0], 2);
        writeUint16(HWTYPE_ETHERNET, &srvid[2], 2);
        writeUint32(static_cast<uint32_t>(seconds), &srvid[4], 4);
        memcpy(&srvid[8], iface->getMac(), iface->getMacLen());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
658 659 660 661

        serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                         srvid.begin(), srvid.end()));
        return;
662
    }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
663

Tomek Mrugalski's avatar
Tomek Mrugalski committed
664
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
665 666 667 668 669
    // Either interface detection is not supported on this platform or
    // this is really weird box. Let's use DUID-EN instead.
    // See Section 9.3 of RFC3315 for details.

    OptionBuffer srvid(12);
670 671
    writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
    writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
672

Tomek Mrugalski's avatar
Tomek Mrugalski committed
673 674
    // Length of the identifier is company specific. I hereby declare
    // ISC "standard" of 6 bytes long pseudo-random numbers.
675
    srandom(time(NULL));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
676
    fillRandom(&srvid[6], &srvid[12]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
677

Tomek Mrugalski's avatar
Tomek Mrugalski committed
678 679
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
680 681
}

682 683
void
Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
684 685
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
686 687 688
    if (clientid) {
        answer->addOption(clientid);
    }
689 690
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

691
    // If this is a relayed message, we need to copy relay information
692 693 694
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
695

696 697
}

698
void
699
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
700 701 702 703
    // add server-id
    answer->addOption(getServerID());
}

704 705
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
706 707 708 709 710 711 712
    // Get the configured subnet suitable for the incoming packet.
    Subnet6Ptr subnet = selectSubnet(question);
    // 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.
713 714 715
    if (!subnet) {
        return;
    }
716

717 718
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
719 720
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
721 722 723 724 725 726 727
    // Option ORO not found. Don't do anything then.
    if (!option_oro) {
        return;
    }
    // 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) {
728 729
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
730 731 732
            answer->addOption(desc.option);
        }
    }
733 734
}

735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
void
Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
    // Get the configured subnet suitable for the incoming packet.
    Subnet6Ptr subnet = selectSubnet(question);
    // 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.
    if (!subnet) {
        return;
    }

    // Try to get the vendor option
    boost::shared_ptr<OptionVendor> vendor_req =
        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
    if (!vendor_req) {
        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
766 767
    uint32_t vendor_id = vendor_req->getVendorId();

768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    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) {
        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt);
        if (desc.option) {
            vendor_rsp->addOption(desc.option);
            added = true;
        }
    }

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

786 787
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
788 789 790 791 792 793
    // @todo This function uses OptionCustom class to manage contents
    // of the data fields. Since this this option is frequently used
    // it may be good to implement dedicated class to avoid performance
    // impact.

    // Get the definition of the option holding status code.
794 795
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
796
    // This definition is assumed to be initialized in LibDHCP.
797 798
    assert(status_code_def);

799
    // As there is no dedicated class to represent Status Code
800 801 802
    // the OptionCustom class is used here instead.
    OptionCustomPtr option_status =
        OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
803 804
    assert(option_status);

805
    // Set status code to 'code' (0 - means data field #0).
806
    option_status->writeInteger(code, 0);
807
    // Set a message (1 - means data field #1).
808 809
    option_status->writeString(text, 1);
    return (option_status);
810 811
}

812 813 814
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
815
    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
    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;
    }

836
    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
837 838
    switch (serverid) {
    case FORBIDDEN:
839
        if (!server_ids.empty()) {
840
            isc_throw(RFCViolation, "Server-id option was not expected, but "
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
                      << 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()
856
                      << ") server-id options received in " << pkt->getName());
857 858 859 860
        }
    }
}

861 862
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
863

864
    Subnet6Ptr subnet;
865

866 867 868 869
    if (question->relay_info_.empty()) {
        // This is a direct (non-relayed) message

        // Try to find a subnet if received packet from a directly connected client
870 871
        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
                                               question->classes_);
872 873
        if (!subnet) {
            // If no subnet was found, try to find it based on remote address
874 875
            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
                                                   question->classes_);
876 877
        }
    } else {
878

879 880
        // This is a relayed message
        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
881
                                                             Pkt6::RELAY_GET_FIRST);
882
        if (interface_id) {
883 884
            subnet = CfgMgr::instance().getSubnet6(interface_id,
                                                   question->classes_);
885
        }
886

887
        if (!subnet) {
888 889
            // If no interface-id was specified (or not configured on server),
            // let's try address matching
890
            IOAddress link_addr = question->relay_info_.back().linkaddr_;
891

892 893
            // if relay filled in link_addr field, then let's use it
            if (link_addr != IOAddress("::")) {
894
                subnet = CfgMgr::instance().getSubnet6(link_addr,
895
                                                       question->classes_, true);
896
            }
897 898
        }
    }
899

900
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
901
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
902 903 904 905
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
907 908
        // Set new arguments
        callout_handle->setArgument("query6", question);
909
        callout_handle->setArgument("subnet6", subnet);
910 911 912 913

        // We pass pointer to const collection for performance reasons.
        // Otherwise we would get a non-trivial performance penalty each
        // time subnet6_select is called.
914
        callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
915 916

        // Call user (and server-side) callouts
917
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
918 919 920 921 922 923 924 925 926 927 928 929 930

        // Callouts decided to skip this step. This means that no subnet will be
        // selected. Packet processing will continue, but it will be severly limited
        // (i.e. only global options will be assigned)
        if (callout_handle->getSkip()) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_SUBNET6_SELECT_SKIP);
            return (Subnet6Ptr());
        }

        // Use whatever subnet was specified by the callout
        callout_handle->getArgument("subnet6", subnet);
    }

931
    return (subnet);
932 933
}

934
void
935
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
936

Tomek Mrugalski's avatar
Tomek Mrugalski committed
937 938
    // We need to allocate addresses for all IA_NA options in the client's
    // question (i.e. SOLICIT or REQUEST) message.
939
    // @todo add support for IA_TA
Tomek Mrugalski's avatar
Tomek Mrugalski committed
940 941

    // We need to select a subnet the client is connected in.
942
    Subnet6Ptr subnet = selectSubnet(question);
943
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
944 945 946 947 948 949 950
        // This particular client is out of luck today. We do not have
        // information about the subnet he is connected to. This likely means
        // misconfiguration of the server (or some relays). We will continue to
        // process this message, but our response will be almost useless: no
        // addresses or prefixes, no subnet specific configuration etc. The only
        // thing this client can get is some global information (like DNS
        // servers).
951

952
        LOG_WARN(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
953 954 955
            .arg(question->getRemoteAddr().toText())
            .arg(question->getName());

956 957 958
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
959 960 961 962
    }

    // @todo: We should implement Option6Duid some day, but we can do without it
    // just fine for now
Tomek Mrugalski's avatar
Tomek Mrugalski committed
963 964 965 966 967

    // Let's find client's DUID. Client is supposed to include its client-id
    // option almost all the time (the only exception is an anonymous inf-request,
    // but that is mostly a theoretical case). Our allocation engine needs DUID
    // and will refuse to allocate anything to anonymous clients.
968 969 970 971
    DuidPtr duid;
    OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
    if (opt_duid) {
        duid = DuidPtr(new DUID(opt_duid->getData()));
972
    } else {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
973
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
974 975
        // Let's drop the message. This client is not sane.
        isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
976 977
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
978 979 980 981
    // Now that we have all information about the client, let's iterate over all
    // received options and handle IA_NA options one by one and store our
    // responses in answer message (ADVERTISE or REPLY).
    //
982
    // @todo: IA_TA once we implement support for temporary addresses.
983
    for (OptionCollection::iterator opt = question->options_.begin();
984
         opt != question->options_.end(); ++opt) {
985 986
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
987
            OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
988
                                               boost::dynamic_pointer_cast<
989
                                               Option6IA>(opt->second));
990 991 992 993 994
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
            break;
        }
995 996 997 998 999 1000 1001 1002
        case D6O_IA_PD: {
            OptionPtr answer_opt = assignIA_PD(subnet, duid, question,
                                               boost::dynamic_pointer_cast<
                                               Option6IA>(opt->second));
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
        }
1003 1004
        default:
            break;
1005 1006
        }
    }
1007
}
1008

1009
void
1010