dhcp6_srv.cc 151 KB
Newer Older
1
// Copyright (C) 2011-2019 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6

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

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

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

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

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

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

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

93
94
95
96
namespace {

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

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

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

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

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

175
176
}; // anonymous namespace

177
178
namespace isc {
namespace dhcp {
179

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

182
183
184
185
Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port)
    : io_service_(new IOService()), server_port_(server_port),
      client_port_(client_port), serverid_(), shutdown_(true),
      alloc_engine_(), name_change_reqs_(),
186
187
      network_state_(new NetworkState(NetworkState::DHCPv6)),
      cb_control_(new CBControlDHCPv6()) {
188
189
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET)
        .arg(server_port);
190

191
192
    Dhcp6to4Ipc::instance().client_port = client_port;

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

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

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

213
214
        /// @todo call loadLibraries() when handling configuration changes

215
    } catch (const std::exception &e) {
216
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
217
218
        return;
    }
219

220
221
    // All done, so can proceed
    shutdown_ = false;
222
223
}

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

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

240
    IfaceMgr::instance().closeSockets();
241

242
    LeaseMgrFactory::destroy();
243
244
245

    // Explicitly unload hooks
    HooksManager::getHooksManager().unloadLibraries();
246
247
}

248
void Dhcpv6Srv::shutdown() {
249
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
250
251
252
    shutdown_ = true;
}

253
254
255
256
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

257
258
259
260
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

261
bool
262
263
Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt) {
    /// @todo Currently we always check server identifier regardless if
264
    /// it is allowed in the received message or not (per RFC 8415).
265
    /// If the server identifier is not allowed in the message, the
266
    /// sanityCheck function should deal with it.
267
268
269
    OptionPtr server_id = pkt->getOption(D6O_SERVERID);
    if (server_id){
        // Let us test received ServerID if it is same as ServerID
Francis Dupont's avatar
Francis Dupont committed
270
        // which is being used by server
271
        if (getServerID()->getData() != server_id->getData()){
272
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL_DATA,
273
274
275
276
                      DHCP6_PACKET_DROP_SERVERID_MISMATCH)
                .arg(pkt->getLabel())
                .arg(duidToString(server_id))
                .arg(duidToString(getServerID()));
277
278
279
            return (false);
        }
    }
Francis Dupont's avatar
Francis Dupont committed
280
    // return True if: no serverid received or ServerIDs matching
281
282
283
284
285
286
287
288
289
290
291
    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()) {
292
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_DROP_UNICAST)
293
294
                .arg(pkt->getLabel())
                .arg(pkt->getName());
295
296
297
298
299
300
301
302
            return (false);
        }
        break;
    default:
        // do nothing
        ;
    }
    return (true);
303
304
}

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

318
319
320
321
322
    if (drop) {
        // Caller will immediately drop the packet so simply return now.
        return;
    }

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

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

350
351
352
                    Host::IdentifierType type = Host::IDENT_FLEX;
                    std::vector<uint8_t> id;

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

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

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

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

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

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

                        ctx.addHostIdentifier(type, id);
                    }
                }
                break;
381
382
383
384
385
386
387
            default:
                ;
            }
        }

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

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

    // Perform second pass of classification.
    evaluateClasses(pkt, true);
405
406
}

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

Francis Dupont's avatar
Francis Dupont committed
424
425
    return (true);
}
426

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

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

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

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

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

466

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

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

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

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

513
514
515
    if (!rsp) {
        return;
    }
516

517
518
    CalloutHandlePtr callout_handle = getCalloutHandle(query);
    processPacketBufferSend(callout_handle, rsp);
519
}
520

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

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

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

536
537
538
        // Enable copying options from the packet within hook library.
        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

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

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

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

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

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

Francis Dupont's avatar
Francis Dupont committed
572
573
        callout_handle->getArgument("query6", query);
    }
574

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

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

Francis Dupont's avatar
Francis Dupont committed
608
609
    // Update statistics accordingly for received packet.
    processStatsReceived(query);
610

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

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

Francis Dupont's avatar
Francis Dupont committed
620
621
622
623
    // 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)) {
624

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

630
631
632
    // Assign this packet to a class, if possible
    classifyPacket(query);

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

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

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

656
657
658
        // Enable copying options from the packet within hook library.
        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

Francis Dupont's avatar
Francis Dupont committed
659
660
661
662
663
664
665
666
667
        // 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.
668
669
        if ((callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) ||
            (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP)) {
Francis Dupont's avatar
Francis Dupont committed
670
671
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP)
                .arg(query->getLabel());
672
673
674
            // Increase the statistic of dropped packets.
            StatsMgr::instance().addValue("pkt6-receive-drop",
                                          static_cast<int64_t>(1));
Francis Dupont's avatar
Francis Dupont committed
675
            return;
676
        }
677

Francis Dupont's avatar
Francis Dupont committed
678
679
        callout_handle->getArgument("query6", query);
    }
680

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

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

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

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

    // Park point here.

    try {
        switch (query->getType()) {
        case DHCPV6_SOLICIT:
708
            rsp = processSolicit(ctx);
709
710
711
            break;

        case DHCPV6_REQUEST:
712
            rsp = processRequest(ctx);
713
714
715
            break;

        case DHCPV6_RENEW:
716
            rsp = processRenew(ctx);
717
718
719
            break;

        case DHCPV6_REBIND:
720
            rsp = processRebind(ctx);
721
722
723
            break;

        case DHCPV6_CONFIRM:
724
            rsp = processConfirm(ctx);
725
726
727
            break;

        case DHCPV6_RELEASE:
728
            rsp = processRelease(ctx);
729
730
731
            break;

        case DHCPV6_DECLINE:
732
            rsp = processDecline(ctx);
733
734
735
            break;

        case DHCPV6_INFORMATION_REQUEST:
736
            rsp = processInfRequest(ctx);
737
738
739
740
741
742
            break;

        default:
            return;
        }

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

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

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

761
762
763
    if (!rsp) {
        return;
    }
764

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

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

780
781
782
783
    if (client_port_) {
        // A command line option enforces a specific client port
        rsp->setRemotePort(client_port_);
    } else if (rsp->relay_info_.empty()) {
784
785
786
787
        // Direct traffic, send back to the client directly
        rsp->setRemotePort(DHCP6_CLIENT_PORT);
    } else {
        // Relayed traffic, send back to the relay agent
788
        uint16_t relay_port = checkRelaySourcePort(query);
789
        rsp->setRemotePort(relay_port ? relay_port : DHCP6_SERVER_PORT);
790
    }
791

792
793
794
    rsp->setLocalPort(DHCP6_SERVER_PORT);
    rsp->setIndex(query->getIndex());
    rsp->setIface(query->getIface());
795

796
797
    bool packet_park = false;

798
799
    if (!ctx.fake_allocation_ && (ctx.query_->getType() != DHCPV6_CONFIRM) &&
        (ctx.query_->getType() != DHCPV6_INFORMATION_REQUEST) &&
800
        HooksManager::calloutsPresent(Hooks.hook_index_leases6_committed_)) {
801
802
        CalloutHandlePtr callout_handle = getCalloutHandle(query);

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

        ScopedEnableOptionsCopy<Pkt6> query6_options_copy(query);

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

        Lease6CollectionPtr new_leases(new Lease6Collection());
815
816
817
        if (!ctx.new_leases_.empty()) {
            new_leases->assign(ctx.new_leases_.cbegin(),
                               ctx.new_leases_.cend());
818
819
820
821
        }
        callout_handle->setArgument("leases6", new_leases);

        Lease6CollectionPtr deleted_leases(new Lease6Collection());
822

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

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

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

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

    if (!rsp) {
        return;
    }

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

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

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

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

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

898
899
    // Specifies if server should do the packing
    bool skip_pack = false;
900

901
902
903
904
905
    // Server's reply packet now has all options and fields set.
    // Options are represented by individual objects, but the
    // output wire data has not been prepared yet.
    // Execute all callouts registered for packet6_send
    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
906

907
908
909
910
911
912
        // Use the RAII wrapper to make sure that the callout handle state is
        // reset when this object goes out of scope. All hook points must do
        // it to prevent possible circular dependency between the callout
        // handle and its arguments.
        ScopedCalloutHandleState callout_handle_state(callout_handle);

913
914
915
        // Enable copying options from the packets within hook library.
        ScopedEnableOptionsCopy<Pkt6> query_resp_options_copy(query, rsp);

916
917
918
        // Pass incoming packet as argument
        callout_handle->setArgument("query6", query);

919
920
        // Set our response
        callout_handle->setArgument("response6", rsp);
921

922
923
        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
924

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

936
937
938
939
940
941
942
        /// Callouts decided to drop the packet.
        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
            LOG_DEBUG(hooks_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_DROP)
                .arg(rsp->getLabel());
            rsp.reset();
            return;
        }
943
    }
944

945
    if (!skip_pack) {
Francis Dupont's avatar
Francis Dupont committed
946
        try {
947
            rsp->pack();
948
        } catch (const std::exception& e) {
949
950
            LOG_ERROR(options6_logger, DHCP6_PACK_FAIL).arg(e.what());
            return;
951
        }
952
953
954

    }
}
955

956
957
958
959
960
961
962
963
964
965
966
967
968
969
void
Dhcpv6Srv::processPacketBufferSend(CalloutHandlePtr& callout_handle,
                                   Pkt6Ptr& rsp) {
    if (!rsp) {
        return;
    }

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

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

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

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

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

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

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

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
        LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_SEND)
          .arg(rsp->getLabel())
          .arg(rsp->getName())
          .arg(static_cast<int>(rsp->getType()))
          .arg(rsp->getLocalAddr().isV6Zero() ? "*" : rsp->getLocalAddr().toText())
          .arg(rsp->getLocalPort())
          .arg(rsp->getRemoteAddr())
          .arg(rsp->getRemotePort())
          .arg(rsp->getIface());

1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_RESPONSE_DATA)
            .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());

        sendPacket(rsp);

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

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

1023
1024
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
1025
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1026

1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
    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();
}

1043
void
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1044
Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1045
1046
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
1047
1048
1049
    if (clientid) {
        answer->addOption(clientid);
    }
1050
1051
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

1052
    // If this is a relayed message, we need to copy relay information
1053
1054
1055
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
1056

1057
1058
}

1059
void
1060
1061
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer,
                                const CfgOptionList&) {
1062
1063
1064
1065
    // add server-id
    answer->addOption(getServerID());
}

1066
1067
void
Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
1068
1069
                              AllocEngine::ClientContext6& ctx,
                              CfgOptionList& co_list) {
1070
    // Firstly, host specific options.
1071
1072
    if (ctx.currentHost() && !ctx.currentHost()->getCfgOption6()->empty()) {
        co_list.push_back(ctx.currentHost()->getCfgOption6());
1073
1074
    }

1075
1076
1077
    // Secondly, pool specific options. Pools are defined within a subnet, so
    // if there is no subnet, there is nothing to do.
    if (ctx.subnet_) {
1078
1079
1080
1081
1082
1083
        for (auto resource : ctx.allocated_resources_) {
            PoolPtr pool =
                ctx.subnet_->getPool(resource.getPrefixLength() == 128 ?
                                     Lease::TYPE_NA : Lease::TYPE_PD,
                                     resource.getAddress(),
                                     false);
1084
1085
1086
1087
1088
1089
            if (pool && !pool->getCfgOption()->empty()) {
                co_list.push_back(pool->getCfgOption());
            }
        }
    };

1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
    if (ctx.subnet_) {
        // Next, subnet configured options.
        if (!ctx.subnet_->getCfgOption()->empty()) {
            co_list.push_back(ctx.subnet_->getCfgOption());
        }

        // Then, shared network specific options.
        SharedNetwork6Ptr network;
        ctx.subnet_->getSharedNetwork(network);
        if (network && !network->getCfgOption()->empty()) {
            co_list.push_back(network->getCfgOption());
        }
1102
1103
1104
1105
    }

    // Each class in the incoming packet
    const ClientClasses& classes = question->getClasses();
1106
1107
    for (ClientClasses::const_iterator cclass = classes.cbegin();
         cclass != classes.cend(); ++cclass) {
1108
1109
1110
1111
        // Find the client class definition for this class
        const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
            getClientClassDictionary()->findClass(*cclass);
        if (!ccdef) {
Francis Dupont's avatar
Francis Dupont committed
1112
1113
            // Not found: the class is built-in or not configured
            if (!isClientClassBuiltIn(*cclass)) {
1114
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNCONFIGURED)
1115
                    .arg(question->getLabel())
1116
1117
1118
                    .arg(*cclass);
            }
            // Skip it
1119
1120
            continue;
        }
1121

1122
1123
1124
1125
        if (ccdef->getCfgOption()->empty()) {
            // Skip classes which don't configure options
            continue;
        }
1126

1127
1128
1129
1130
        co_list.push_back(ccdef->getCfgOption());
    }

    // Last global options
1131
1132
1133
    if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
        co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
    }
1134
1135
}

1136
void
1137
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
1138
                                  const CfgOptionList& co_list) {
1139

1140
1141
1142
1143
1144
1145
1146
    // Unlikely short cut
    if (co_list.empty()) {
        return;
    }

    std::vector<uint16_t> requested_opts;

1147
1148
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
1149
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
1150
1151
1152
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
        (question->getOption(D6O_ORO));

1153
    // Get the list of options that client requested.
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
    if (option_oro) {
        requested_opts = option_oro->getValues();
    }
    // Iterate on the configured option list to add persistent options
    for (CfgOptionList::const_iterator copts = co_list.begin();
         copts != co_list.end(); ++copts) {
        const OptionContainerPtr& opts = (*copts)->getAll(DHCP6_OPTION_SPACE);
        if (!opts) {
            continue;
        }
        // Get persistent options
        const OptionContainerPersistIndex& idx = opts->get<2>();
        const OptionContainerPersistRange& range = idx.equal_range(true);
        for (OptionContainerPersistIndex::const_iterator desc = range.first;
             desc != range.second; ++desc) {
            // Add the persistent option code to requested options
            requested_opts.push_back(desc->option_->getType());
        }
    }
1173

1174
    BOOST_FOREACH(uint16_t opt, requested_opts) {
1175
1176
1177
        // Iterate on the configured option list
        for (CfgOptionList::const_iterator copts = co_list.begin();
             copts != co_list.end(); ++copts) {
1178
            OptionDescriptor desc = (*copts)->get(DHCP6_OPTION_SPACE, opt);
1179
            // Got it: add it and jump to the outer loop
1180
1181
            if (desc.option_) {
                answer->addOption(desc.option_);
1182
                break;
1183
            }
1184
1185
        }
    }
1186
1187
}

1188
void
1189
1190
1191
1192
Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
                                        Pkt6Ptr& answer,
                                        AllocEngine::ClientContext6& ctx,
                                        const CfgOptionList& co_list) {
1193

1194
1195
1196
1197
1198
    // 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.
1199
1200
1201