dhcp6_srv.cc 105 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);
245 246 247 248 249 250 251

        } 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, but there is nothing we should log here.
Marcin Siodelski's avatar
Marcin Siodelski committed
252
        } catch (const std::exception& e) {
253 254 255
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

256 257 258 259 260 261 262 263 264 265 266 267
        // 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();

268 269
        // Timeout may be reached or signal received, which breaks select()
        // with no packet received
270 271 272
        if (!query) {
            continue;
        }
273

274 275 276 277 278 279 280 281 282
        // 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));

283
        bool skip_unpack = false;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
284

Tomek Mrugalski's avatar
Tomek Mrugalski committed
285 286
        // 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
287
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
288
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
289

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

293 294
            // Pass incoming packet as argument
            callout_handle->setArgument("query6", query);
295

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

299 300
            // 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
301 302
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
303 304
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
305
                skip_unpack = true;
306 307
            }

308 309
            callout_handle->getArgument("query6", query);
        }
310

Tomek Mrugalski's avatar
Tomek Mrugalski committed
311 312
        // Unpack the packet information unless the buffer6_receive callouts
        // indicated they did it
313
        if (!skip_unpack) {
314
            if (!query->unpack()) {
315 316
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
317 318
                continue;
            }
319
        }
320 321
        // Check if received query carries server identifier matching
        // server identifier being used by the server.
322 323 324 325 326 327 328 329 330
        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;
331 332
        }

333 334 335 336 337 338 339
        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
340 341 342
        // 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
343
        if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
            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;
            }
362

363 364
            callout_handle->getArgument("query6", query);
        }
365

366 367 368
        // Assign this packet to a class, if possible
        classifyPacket(query);

369
        try {
370
                NameChangeRequestPtr ncr;
371 372 373
            switch (query->getType()) {
            case DHCPV6_SOLICIT:
                rsp = processSolicit(query);
374
                    break;
375

376 377 378
            case DHCPV6_REQUEST:
                rsp = processRequest(query);
                break;
379

380 381 382
            case DHCPV6_RENEW:
                rsp = processRenew(query);
                break;
383

384 385 386
            case DHCPV6_REBIND:
                rsp = processRebind(query);
                break;
387

388 389 390
            case DHCPV6_CONFIRM:
                rsp = processConfirm(query);
                break;
391

392 393 394 395 396 397 398 399 400 401 402
            case DHCPV6_RELEASE:
                rsp = processRelease(query);
                break;

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

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

404
            default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
405 406 407 408
                // 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());
409 410 411 412
                // Only action is to output a message if debug is enabled,
                // and that will be covered by the debug statement before
                // the "switch" statement.
                ;
413
            }
414

415 416 417 418 419 420 421 422
        } 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) {

423 424 425 426 427 428
            // 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).  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.)
429 430 431 432 433
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
                .arg(query->getName())
                .arg(query->getRemoteAddr().toText())
                .arg(e.what());
        }
434

435 436 437
        if (rsp) {
            rsp->setRemoteAddr(query->getRemoteAddr());
            rsp->setLocalAddr(query->getLocalAddr());
438 439 440 441 442 443 444 445 446

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

447 448 449 450
            rsp->setLocalPort(DHCP6_SERVER_PORT);
            rsp->setIndex(query->getIndex());
            rsp->setIface(query->getIface());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
451
            // Specifies if server should do the packing
452 453
            bool skip_pack = false;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
454 455 456
            // 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.
457
            // Execute all callouts registered for packet6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
458
            if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
459
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
460

461 462 463 464 465
                // Delete all previous arguments
                callout_handle->deleteAllArguments();

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

467 468 469 470
                // 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
471 472 473 474
                // 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.
475 476
                if (callout_handle->getSkip()) {
                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
477
                    skip_pack = true;
478
                }
479
            }
480

481 482 483
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                      DHCP6_RESPONSE_DATA)
                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
484

485
            if (!skip_pack) {
486 487 488 489 490
                try {
                    rsp->pack();
                } catch (const std::exception& e) {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
                        .arg(e.what());
491 492
                    continue;
                }
493

494 495
            }

496
            try {
497

Tomek Mrugalski's avatar
Tomek Mrugalski committed
498 499 500
                // 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.
501
                // Let's execute all callouts registered for buffer6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
502
                if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
503
                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
504

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

508
                    // Pass incoming packet as argument
Tomek Mrugalski's avatar
Tomek Mrugalski committed
509
                    callout_handle->setArgument("response6", rsp);
510

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

514
                    // Callouts decided to skip the next processing step. The next
515 516
                    // processing step would to parse the packet, so skip at this
                    // stage means drop.
517
                    if (callout_handle->getSkip()) {
518
                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
519 520
                        continue;
                    }
521

522
                    callout_handle->getArgument("response6", rsp);
523
                }
524 525 526

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

529 530
                sendPacket(rsp);
            } catch (const std::exception& e) {
531 532
                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                    .arg(e.what());
533
            }
534 535 536
        }
    }

537
    return (true);
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 566 567 568 569 570 571
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);
}

572 573
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
574
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
575

576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
    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();
}

592 593
bool
Dhcpv6Srv::writeServerID(const std::string& file_name) {
594 595 596 597 598 599
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
600
    return (true);
601
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
602

603 604
void
Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
605 606 607 608 609 610 611

    /// @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
612
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
613 614
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
615
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
616 617 618 619
        // 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
620
        // DUID storage is implemented)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
621 622 623

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
624 625 626 627 628
        // 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.
629
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
630 631 632
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
633
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
634 635 636 637
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
638
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
639 640 641 642
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
643
        // Some interfaces (like lo on Linux) report 6-bytes long
644
        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
Tomek Mrugalski's avatar
Tomek Mrugalski committed
645
        // to generate DUID.
646
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
647 648 649 650 651 652 653 654 655 656 657
            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;

658
        OptionBuffer srvid(8 + iface->getMacLen());
Stephen Morris's avatar
Stephen Morris committed
659
        // We know that the buffer is more than 8 bytes long at this point.
660 661 662 663
        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
664 665 666 667

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
670
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
671 672 673 674 675
    // 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);
676 677
    writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
    writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
678

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
684 685
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
686 687
}

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

697
    // If this is a relayed message, we need to copy relay information
698 699 700
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
701

702 703
}

704
void
705
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
706 707 708 709
    // add server-id
    answer->addOption(getServerID());
}

710 711
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
712 713 714 715 716 717 718
    // 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.
719 720 721
    if (!subnet) {
        return;
    }
722

723 724
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
725 726
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
727 728 729 730 731 732 733
    // 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) {
734 735
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
736 737 738
            answer->addOption(desc.option);
        }
    }
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 766 767 768 769 770 771
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
772 773
    uint32_t vendor_id = vendor_req->getVendorId();

774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
    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);
    }
}

792 793
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
794 795 796 797 798 799
    // @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.
800 801
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
802
    // This definition is assumed to be initialized in LibDHCP.
803 804
    assert(status_code_def);

805
    // As there is no dedicated class to represent Status Code
806 807 808
    // the OptionCustom class is used here instead.
    OptionCustomPtr option_status =
        OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
809 810
    assert(option_status);

811
    // Set status code to 'code' (0 - means data field #0).
812
    option_status->writeInteger(code, 0);
813
    // Set a message (1 - means data field #1).
814 815
    option_status->writeString(text, 1);
    return (option_status);
816 817
}

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

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

867 868
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
869

870
    Subnet6Ptr subnet;
871

872 873 874 875
    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
876 877
        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
                                               question->classes_);
878 879
        if (!subnet) {
            // If no subnet was found, try to find it based on remote address
880 881
            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
                                                   question->classes_);
882 883
        }
    } else {
884

885 886
        // This is a relayed message
        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
887
                                                             Pkt6::RELAY_GET_FIRST);
888
        if (interface_id) {
889 890
            subnet = CfgMgr::instance().getSubnet6(interface_id,
                                                   question->classes_);
891
        }
892

893
        if (!subnet) {
894 895
            // If no interface-id was specified (or not configured on server),
            // let's try address matching
896
            IOAddress link_addr = question->relay_info_.back().linkaddr_;
897

898 899
            // if relay filled in link_addr field, then let's use it
            if (link_addr != IOAddress("::")) {
900
                subnet = CfgMgr::instance().getSubnet6(link_addr,
901
                                                       question->classes_, true);
902
            }
903 904
        }
    }
905

906
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
907
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
908 909 910 911
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
913 914
        // Set new arguments
        callout_handle->setArgument("query6", question);
915
        callout_handle->setArgument("subnet6", subnet);
916 917 918 919

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

        // Call user (and server-side) callouts
923
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
924 925 926 927 928 929 930 931 932 933 934 935 936

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

937
    return (subnet);
938 939
}

940
void
941
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
942

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