dhcp6_srv.cc 71 KB
Newer Older
1
// Copyright (C) 2011-2013 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/duid.h>
21
#include <dhcp/iface_mgr.h>
22
#include <dhcp/libdhcp++.h>
23
#include <dhcp/option6_addrlst.h>
24
#include <dhcp/option6_client_fqdn.h>
25
#include <dhcp/option6_ia.h>
26 27
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
28
#include <dhcp/option_custom.h>
29
#include <dhcp/option_int_array.h>
30
#include <dhcp/pkt6.h>
31 32
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
33
#include <dhcpsrv/callout_handle_store.h>
34 35 36 37
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
38
#include <dhcpsrv/utils.h>
39
#include <exceptions/exceptions.h>
40 41 42
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>
#include <util/encode/hex.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
43
#include <util/io_utilities.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
44
#include <util/range_utilities.h>
45

46
#include <boost/foreach.hpp>
47 48
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string/erase.hpp>
49

50 51
#include <stdlib.h>
#include <time.h>
52 53
#include <iomanip>
#include <fstream>
54

55
using namespace isc;
56
using namespace isc::asiolink;
57
using namespace isc::dhcp_ddns;
58
using namespace isc::dhcp;
59
using namespace isc::hooks;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
60
using namespace isc::util;
61
using namespace std;
62

63 64 65 66
namespace {

/// Structure that holds registered hook indexes
struct Dhcp6Hooks {
67
    int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
68 69
    int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
    int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
70 71
    int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
    int hook_index_lease6_release_; ///< index for "lease6_release" hook point
72
    int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
73
    int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
74 75 76

    /// Constructor that registers hook points for DHCPv6 engine
    Dhcp6Hooks() {
77
        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
78 79
        hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
        hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
80 81
        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
82
        hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
83
        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
84 85 86 87 88 89 90 91 92 93 94
    }
};

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

95 96
namespace isc {
namespace dhcp {
97

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
namespace {

// The following constants describe server's behavior with respect to the
// DHCPv6 Client FQDN Option sent by a client. They will be removed
// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.

// Should server always include the FQDN option in its response, regardless
// if it has been requested in ORO (Disabled).
const bool FQDN_ALWAYS_INCLUDE = false;
// Enable AAAA RR update delegation to the client (Disabled).
const bool FQDN_ALLOW_CLIENT_UPDATE = false;
// Globally enable updates (Enabled).
const bool FQDN_ENABLE_UPDATE = true;
// The partial name generated for the client if empty name has been
// supplied.
const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
// Do update, even if client requested no updates with N flag (Disabled).
const bool FQDN_OVERRIDE_NO_UPDATE = false;
// Server performs an update when client requested delegation (Enabled).
const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
// The fully qualified domain-name suffix if partial name provided by
// a client.
const char* FQDN_PARTIAL_SUFFIX = "example.com";
// Should server replace the domain-name supplied by the client (Disabled).
const bool FQDN_REPLACE_CLIENT_NAME = false;

}

126 127 128 129 130 131 132 133 134 135
/// @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.
static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";

136
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
137
:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
138
{
139

140
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
141

142
    // Initialize objects required for DHCP server operation.
143
    try {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
144 145
        // Port 0 is used for testing purposes. It means that the server should
        // not open any sockets at all. Some tests, e.g. configuration parser,
146 147
        // require Dhcpv6Srv object, but they don't really need it to do
        // anything. This speed up and simplifies the tests.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
148
        if (port > 0) {
149 150 151 152
            if (IfaceMgr::instance().countIfaces() == 0) {
                LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
                return;
            }
153
            IfaceMgr::instance().openSockets6(port_);
154
        }
155

156 157 158
        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
        if (loadServerID(duid_file)) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
159
                .arg(duidToString(getServerID()))
160 161 162 163 164 165 166 167 168 169 170 171
                .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);
            }
        }
172

173 174 175
        // Instantiate allocation engine
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));

176 177
        /// @todo call loadLibraries() when handling configuration changes

178
    } catch (const std::exception &e) {
179
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
180 181
        return;
    }
182

183 184
    // All done, so can proceed
    shutdown_ = false;
185 186
}

187
Dhcpv6Srv::~Dhcpv6Srv() {
188
    IfaceMgr::instance().closeSockets();
189

190
    LeaseMgrFactory::destroy();
191 192
}

193
void Dhcpv6Srv::shutdown() {
194
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
195 196 197
    shutdown_ = true;
}

198 199 200 201
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

202 203 204 205
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
206
bool Dhcpv6Srv::run() {
207
    while (!shutdown_) {
208
        /// @todo Calculate actual timeout to the next event (e.g. lease
Tomek Mrugalski's avatar
Tomek Mrugalski committed
209 210 211 212 213
        /// 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.
214 215
        //cppcheck-suppress variableScope This is temporary anyway
        const int timeout = 1000;
216

Tomek Mrugalski's avatar
Tomek Mrugalski committed
217
        // client's message and server's response
218
        Pkt6Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
219
        Pkt6Ptr rsp;
220

221
        try {
222
            query = receivePacket(timeout);
Marcin Siodelski's avatar
Marcin Siodelski committed
223
        } catch (const std::exception& e) {
224 225 226
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

227 228 229 230
        // Timeout may be reached or signal received, which breaks select() with no packet received
        if (!query) {
            continue;
        }
231

232
        bool skip_unpack = false;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
233

Tomek Mrugalski's avatar
Tomek Mrugalski committed
234 235
        // The packet has just been received so contains the uninterpreted wire
        // data; execute callouts registered for buffer6_receive.
236 237
        if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
238

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

242 243
            // Pass incoming packet as argument
            callout_handle->setArgument("query6", query);
244

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

248 249
            // 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
250 251
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
252 253
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
254
                skip_unpack = true;
255 256
            }

257 258
            callout_handle->getArgument("query6", query);
        }
259

Tomek Mrugalski's avatar
Tomek Mrugalski committed
260 261
        // Unpack the packet information unless the buffer6_receive callouts
        // indicated they did it
262
        if (!skip_unpack) {
263
            if (!query->unpack()) {
264 265
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
266 267
                continue;
            }
268 269 270 271 272 273 274 275
        }
        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
276 277 278
        // 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.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        if (HooksManager::getHooksManager().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->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
                continue;
            }
298

299 300
            callout_handle->getArgument("query6", query);
        }
301

302
        try {
303
                NameChangeRequestPtr ncr;
304 305 306
            switch (query->getType()) {
            case DHCPV6_SOLICIT:
                rsp = processSolicit(query);
307
                    break;
308

309 310 311
            case DHCPV6_REQUEST:
                rsp = processRequest(query);
                break;
312

313 314 315
            case DHCPV6_RENEW:
                rsp = processRenew(query);
                break;
316

317 318 319
            case DHCPV6_REBIND:
                rsp = processRebind(query);
                break;
320

321 322 323
            case DHCPV6_CONFIRM:
                rsp = processConfirm(query);
                break;
324

325 326 327 328 329 330 331 332 333 334 335
            case DHCPV6_RELEASE:
                rsp = processRelease(query);
                break;

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

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

337
            default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
338 339 340 341
                // 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());
342 343 344 345
                // Only action is to output a message if debug is enabled,
                // and that will be covered by the debug statement before
                // the "switch" statement.
                ;
346
            }
347

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
        } 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());
        }
368

369 370 371 372 373 374 375 376
        if (rsp) {
            rsp->setRemoteAddr(query->getRemoteAddr());
            rsp->setLocalAddr(query->getLocalAddr());
            rsp->setRemotePort(DHCP6_CLIENT_PORT);
            rsp->setLocalPort(DHCP6_SERVER_PORT);
            rsp->setIndex(query->getIndex());
            rsp->setIface(query->getIface());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
377
            // Specifies if server should do the packing
378 379
            bool skip_pack = false;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
380 381 382
            // 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.
383 384 385
            // Execute all callouts registered for packet6_send
            if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_send_)) {
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
386

387 388 389 390 391
                // Delete all previous arguments
                callout_handle->deleteAllArguments();

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

393 394 395 396
                // 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
397 398 399 400
                // 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.
401 402
                if (callout_handle->getSkip()) {
                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
403
                    skip_pack = true;
404
                }
405
            }
406

407 408 409
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                      DHCP6_RESPONSE_DATA)
                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
410

411
            if (!skip_pack) {
412 413 414 415 416
                try {
                    rsp->pack();
                } catch (const std::exception& e) {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
                        .arg(e.what());
417 418
                    continue;
                }
419

420 421
            }

422
            try {
423

Tomek Mrugalski's avatar
Tomek Mrugalski committed
424 425 426
                // 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.
427 428
                // Let's execute all callouts registered for buffer6_send
                if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_send_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
429
                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
430

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

434
                    // Pass incoming packet as argument
Tomek Mrugalski's avatar
Tomek Mrugalski committed
435
                    callout_handle->setArgument("response6", rsp);
436 437 438 439
                    
                    // Call callouts
                    HooksManager::callCallouts(Hooks.hook_index_buffer6_send_, *callout_handle);
                    
440
                    // Callouts decided to skip the next processing step. The next
441 442
                    // processing step would to parse the packet, so skip at this
                    // stage means drop.
443
                    if (callout_handle->getSkip()) {
444
                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
445 446
                        continue;
                    }
447 448
                    
                    callout_handle->getArgument("response6", rsp);
449
                }
450 451 452

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

455 456
                sendPacket(rsp);
            } catch (const std::exception& e) {
457 458
                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                    .arg(e.what());
459
            }
460

461
            // Send NameChangeRequests to the b10-dhcp_ddns module.
462
            sendNameChangeRequests();
463 464 465
        }
    }

466
    return (true);
467
}
468

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
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);
}

501 502
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
503
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
504

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
    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();
}

521 522
bool
Dhcpv6Srv::writeServerID(const std::string& file_name) {
523 524 525 526 527 528
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
529
    return (true);
530
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
531

532 533
void
Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
534 535 536 537 538 539 540

    /// @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
541
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
542 543
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
544
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
545 546 547 548
        // 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
549
        // DUID storage is implemented)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
550 551 552

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
553 554 555 556 557
        // 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.
558
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
559 560 561
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
562
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
563 564 565 566
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
567
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
568 569 570 571
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
572
        // Some interfaces (like lo on Linux) report 6-bytes long
573
        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
Tomek Mrugalski's avatar
Tomek Mrugalski committed
574
        // to generate DUID.
575
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
576 577 578 579 580 581 582 583 584 585 586
            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;

587
        OptionBuffer srvid(8 + iface->getMacLen());
588
        writeUint16(DUID::DUID_LLT, &srvid[0]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
589 590
        writeUint16(HWTYPE_ETHERNET, &srvid[2]);
        writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
591
        memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
592 593 594 595

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
598
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
599 600 601 602 603
    // 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);
604
    writeUint16(DUID::DUID_EN, &srvid[0]);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
605 606
    writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
612 613
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
614 615
}

616 617
void
Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
618 619
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
620 621 622
    if (clientid) {
        answer->addOption(clientid);
    }
623 624
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

625
    // If this is a relayed message, we need to copy relay information
626 627 628
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
629

630 631
}

632
void
633
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
634 635 636 637
    // add server-id
    answer->addOption(getServerID());
}

638 639
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
640 641 642 643 644 645 646
    // 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.
647 648 649
    if (!subnet) {
        return;
    }
650

651 652
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
653 654
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
655 656 657 658 659 660 661
    // 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) {
662 663
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
664 665 666
            answer->addOption(desc.option);
        }
    }
667 668
}

669 670
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
671 672 673 674 675 676
    // @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.
677 678
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
679
    // This definition is assumed to be initialized in LibDHCP.
680 681
    assert(status_code_def);

682
    // As there is no dedicated class to represent Status Code
683 684 685
    // the OptionCustom class is used here instead.
    OptionCustomPtr option_status =
        OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
686 687
    assert(option_status);

688
    // Set status code to 'code' (0 - means data field #0).
689
    option_status->writeInteger(code, 0);
690
    // Set a message (1 - means data field #1).
691 692
    option_status->writeString(text, 1);
    return (option_status);
693 694
}

695 696 697
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
    Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
    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;
    }

    Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
    switch (serverid) {
    case FORBIDDEN:
722
        if (!server_ids.empty()) {
723
            isc_throw(RFCViolation, "Server-id option was not expected, but "
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
                      << 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()
739
                      << ") server-id options received in " << pkt->getName());
740 741 742 743
        }
    }
}

744 745
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
746

747
    Subnet6Ptr subnet;
748

749 750 751 752
    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
753 754 755 756
        subnet = CfgMgr::instance().getSubnet6(question->getIface());
        if (!subnet) {
            // If no subnet was found, try to find it based on remote address
            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
757 758
        }
    } else {
759

760 761
        // This is a relayed message
        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
762
                                                             Pkt6::RELAY_GET_FIRST);
763 764 765
        if (interface_id) {
            subnet = CfgMgr::instance().getSubnet6(interface_id);
        }
766

767 768 769 770
        if (!subnet) {
            // If no interface-id was specified (or not configured on server), let's
            // try address matching
            IOAddress link_addr = question->relay_info_.back().linkaddr_;
771

772 773 774 775
            // if relay filled in link_addr field, then let's use it
            if (link_addr != IOAddress("::")) {
                subnet = CfgMgr::instance().getSubnet6(link_addr);
            }
776 777
        }
    }
778

779 780
    // Let's execute all callouts registered for subnet6_receive
    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
781 782 783 784
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
786 787
        // Set new arguments
        callout_handle->setArgument("query6", question);
788
        callout_handle->setArgument("subnet6", subnet);
789 790 791 792

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

        // Call user (and server-side) callouts
796
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
797 798 799 800 801 802 803 804 805 806 807 808 809

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

810
    return (subnet);
811 812
}

813
void
814 815
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
                        const Option6ClientFqdnPtr& fqdn) {
816

Tomek Mrugalski's avatar
Tomek Mrugalski committed
817 818
    // We need to allocate addresses for all IA_NA options in the client's
    // question (i.e. SOLICIT or REQUEST) message.
819 820
    // @todo add support for IA_TA
    // @todo add support for IA_PD
Tomek Mrugalski's avatar
Tomek Mrugalski committed
821 822

    // We need to select a subnet the client is connected in.
823
    Subnet6Ptr subnet = selectSubnet(question);
824
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
825 826 827 828 829 830 831
        // 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).
832

833
        LOG_WARN(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
834 835 836
            .arg(question->getRemoteAddr().toText())
            .arg(question->getName());

837 838 839
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
840 841 842 843
    }

    // @todo: We should implement Option6Duid some day, but we can do without it
    // just fine for now
Tomek Mrugalski's avatar
Tomek Mrugalski committed
844 845 846 847 848

    // 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.
849 850 851 852
    DuidPtr duid;
    OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
    if (opt_duid) {
        duid = DuidPtr(new DUID(opt_duid->getData()));
853
    } else {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
854
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
855 856
        // Let's drop the message. This client is not sane.
        isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
857 858
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
859 860 861 862 863 864
    // 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).
    //
    // @todo: expand this to cover IA_PD and IA_TA once we implement support for
    // prefix delegation and temporary addresses.
865 866
    for (Option::OptionCollection::iterator opt = question->options_.begin();
         opt != question->options_.end(); ++opt) {
867 868
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
869
            OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
870 871 872
                                               boost::dynamic_pointer_cast<
                                               Option6IA>(opt->second),
                                               fqdn);
873 874 875 876 877 878 879
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
            break;
        }
        default:
            break;
880 881
        }
    }
882
}
883

884 885
Option6ClientFqdnPtr
Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
886 887 888 889 890
    // Get Client FQDN Option from the client's message. If this option hasn't
    // been included, do nothing.
    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
        Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
    if (!fqdn) {
891
        return (fqdn);
892 893
    }

894 895 896 897
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
              DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());


898 899 900 901 902 903 904 905 906 907 908 909 910 911
    // Prepare the FQDN option which will be included in the response to
    // the client.
    Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
    // RFC 4704, section 6. - all flags set to 0.
    fqdn_resp->resetFlags();

    // Conditions when N flag has to be set to indicate that server will not
    // perform DNS updates:
    // 1. Updates are globally disabled,
    // 2. Client requested no update and server respects it,
    // 3. Client requested that the AAAA update is delegated to the client but
    //    server neither respects delegation of updates nor it is configured
    //    to send update on its own when client requested delegation.
    if (!FQDN_ENABLE_UPDATE ||
912 913 914 915
        (fqdn->getFlag(Option6ClientFqdn::FLAG_N) &&
         !FQDN_OVERRIDE_NO_UPDATE) ||
        (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958
        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);

    // Conditions when S flag is set to indicate that server will perform
    // DNS update on its own:
    // 1. Client requested that server performs DNS update and DNS updates are
    //    globally enabled
    // 2. Client requested that server delegates AAAA update to the client but
    //    server doesn't respect delegation and it is configured to perform
    //    an update on its own when client requested delegation.
    } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
               (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
                !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
    }

    // Server MUST set the O flag if it has overridden the client's setting
    // of S flag.
    if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
        fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
    }

    // If client supplied partial or empty domain-name, server should
    // generate one.
    if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
        std::ostringstream name;
        if (fqdn->getDomainName().empty()) {
            name << FQDN_GENERATED_PARTIAL_NAME;
        } else {
            name << fqdn->getDomainName();
        }
        name << "." << FQDN_PARTIAL_SUFFIX;
        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);

    // Server may be configured to replace a name supplied by a client,
    // even if client supplied fully qualified domain-name.
    } else if (FQDN_REPLACE_CLIENT_NAME) {
        std::ostringstream name;
        name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);

    }

959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
    // Return the FQDN option which can be included in the server's response.
    // Note that it doesn't have to be included, if client didn't request
    // it using ORO and server is not configured to always include it.
    return (fqdn_resp);
}


void
Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question,
                            Pkt6Ptr& answer,
                            const Option6ClientFqdnPtr& fqdn) {

    // If FQDN is NULL, it means that client did not request DNS Update, plus
    // server doesn't force updates.
    if (fqdn) {
        return;
    }

977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
    // Server sends back the FQDN option to the client if client has requested
    // it using Option Request Option. However, server may be configured to
    // send the FQDN option in its response, regardless whether client requested
    // it or not.
    bool include_fqdn = FQDN_ALWAYS_INCLUDE;
    if (!include_fqdn) {
        OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
            OptionUint16Array>(question->getOption(D6O_ORO));
        if (oro) {
            const std::vector<uint16_t>& values = oro->getValues();
            for (int i = 0; i < values.size(); ++i) {
                if (values[i] == D6O_CLIENT_FQDN) {
                    include_fqdn = true;
                }
            }
        }
    }

    if (include_fqdn) {
996 997
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                  DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText());
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
        answer->addOption(fqdn);
    }

}

void
Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
                                    const Option6ClientFqdnPtr& opt_fqdn) {

    // It is likely that client haven't included the FQDN option in the message
    // and server is not configured to always update DNS. In such cases,
    // FQDN option will be NULL. This is valid state, so we simply return.
    if (!opt_fqdn) {
        return;
    }

    // The response message instance is always required. For instance it
    // holds the Client Identifier. It is a programming error if supplied
    // message is NULL.
    if (!answer) {
        isc_throw(isc::Unexpected, "an instance of the object"
                  << " encapsulating server's message must not be"
                  << " NULL when creating DNS NameChangeRequest");
1021
    }
1022

1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
    // Get the Client Id. It is mandatory and a function creating a response
    // would have thrown an exception if it was missing. Thus throwning
    // Unexpected if it is missing as it is a programming error.
    OptionPtr opt_duid = answer->getOption(D6O_CLIENTID);
    if (!opt_duid) {
        isc_throw(isc::Unexpected,
                  "client identifier is required when creating a new"
                  " DNS NameChangeRequest");
    }
    DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));

    // Get the FQDN in the on-wire format. It will be needed to compute
    // DHCID.
    OutputBuffer name_buf(1);
    opt_fqdn->packDomainName(name_buf);
    const uint8_t* name_data = static_cast<const uint8_t*>(name_buf.getData());
    // @todo currently D2Dhcid accepts a vector holding FQDN.
    // However, it will be faster if we used a pointer name_data.
    std::vector<uint8_t> buf_vec(name_data, name_data + name_buf.getLength());
    // Compute DHCID from Client Identifier and FQDN.
1043
    isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec);
Marcin Siodelski's avatar