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

7
8
#include <config.h>

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

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

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

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

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

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

84
85
86
87
namespace {

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

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

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

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

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

162
163
}; // anonymous namespace

164
165
namespace isc {
namespace dhcp {
166

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

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

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

175
    // Initialize objects required for DHCP server operation.
176
    try {
177
178
179
180
181
182
        // Port 0 is used for testing purposes where in most cases we don't
        // rely on the physical interfaces. Therefore, it should be possible
        // to create an object even when there are no usable interfaces.
        if ((port > 0) && (IfaceMgr::instance().countIfaces() == 0)) {
            LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
            return;
183
        }
184

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

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

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

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

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

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

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

216
    LeaseMgrFactory::destroy();
217
218
219

    // Explicitly unload hooks
    HooksManager::getHooksManager().unloadLibraries();
220
221
}

222
void Dhcpv6Srv::shutdown() {
223
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
224
225
226
    shutdown_ = true;
}

227
228
229
230
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

231
232
233
234
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

235
bool
236
237
238
239
Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt) {
    /// @todo Currently we always check server identifier regardless if
    /// it is allowed in the received message or not (per RFC3315).
    /// If the server identifier is not allowed in the message, the
240
    /// sanityCheck function should deal with it.
241
242
243
    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
244
        // which is being used by server
245
        if (getServerID()->getData() != server_id->getData()){
246
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL_DATA,
247
248
249
250
                      DHCP6_PACKET_DROP_SERVERID_MISMATCH)
                .arg(pkt->getLabel())
                .arg(duidToString(server_id))
                .arg(duidToString(getServerID()));
251
252
253
            return (false);
        }
    }
Francis Dupont's avatar
Francis Dupont committed
254
    // return True if: no serverid received or ServerIDs matching
255
256
257
258
259
260
261
262
263
264
265
    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()) {
266
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_DROP_UNICAST)
267
268
                .arg(pkt->getLabel())
                .arg(pkt->getName());
269
270
271
272
273
274
275
276
            return (false);
        }
        break;
    default:
        // do nothing
        ;
    }
    return (true);
277
278
}

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

287
288
289
    // 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.
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    if (ctx.subnet_ &&
        (ctx.subnet_->getHostReservationMode() != Subnet::HR_DISABLED)) {
        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;

            default:
                ;
            }
        }

        // Find host reservations using specified identifiers.
        alloc_engine_->findReservation(ctx);
316
    }
317
318
319
320

    return (ctx);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
321
bool Dhcpv6Srv::run() {
322
    while (!shutdown_) {
323
        try {
Francis Dupont's avatar
Francis Dupont committed
324
            run_one();
Marcin Siodelski's avatar
Marcin Siodelski committed
325
        } catch (const std::exception& e) {
Francis Dupont's avatar
Francis Dupont committed
326
327
            // General catch-all standard exceptions that are not caught by more
            // specific catches.
328
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_STD_EXCEPTION)
329
                .arg(e.what());
Francis Dupont's avatar
Francis Dupont committed
330
331
332
        } catch (...) {
            // General catch-all non-standard exception that are not caught
            // by more specific catches.
333
            LOG_ERROR(packet6_logger, DHCP6_PACKET_PROCESS_EXCEPTION);
334
        }
Francis Dupont's avatar
Francis Dupont committed
335
    }
336

Francis Dupont's avatar
Francis Dupont committed
337
338
    return (true);
}
339

Francis Dupont's avatar
Francis Dupont committed
340
341
342
343
void Dhcpv6Srv::run_one() {
    // client's message and server's response
    Pkt6Ptr query;
    Pkt6Ptr rsp;
344

Francis Dupont's avatar
Francis Dupont committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
    try {
        uint32_t timeout = 1000;
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT).arg(timeout);
        query = receivePacket(timeout);

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

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

        } else {
            LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT_INTERRUPTED)
                .arg(timeout);
373
        }
374

Francis Dupont's avatar
Francis Dupont committed
375
376
377
378
379
380
381
382
383
384
385
    } catch (const SignalInterruptOnSelect) {
        // Packet reception interrupted because a signal has been received.
        // This is not an error because we might have received a SIGTERM,
        // SIGINT or SIGHUP which are handled by the server. For signals
        // that are not handled by the server we rely on the default
        // behavior of the system.
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL, DHCP6_BUFFER_WAIT_SIGNAL)
            .arg(signal_set_->getNext());
    } catch (const std::exception& e) {
        LOG_ERROR(packet6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
    }
386

Francis Dupont's avatar
Francis Dupont committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    // 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());
    }
404

Francis Dupont's avatar
Francis Dupont committed
405
406
407
408
409
    // Timeout may be reached or signal received, which breaks select()
    // with no packet received
    if (!query) {
        return;
    }
410

411
    processPacket(query, rsp);
412

413
414
415
    if (!rsp) {
        return;
    }
416

417
    try {
418

419
420
421
422
423
        // 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_)) {
424
425
426
427
428
429
            CalloutHandlePtr callout_handle = getCalloutHandle(query);

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

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

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

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

444
            /// @todo: Add support for DROP status
445

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

449
450
        LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_RESPONSE_DATA)
            .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
451

452
453
454
455
456
457
458
459
460
        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());
    }
}
461

462
463
void
Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
Francis Dupont's avatar
Francis Dupont committed
464
465
466
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
    // In order to parse the DHCP options, the server needs to use some
    // configuration information such as: existing option spaces, option
    // definitions etc. This is the kind of information which is not
    // available in the libdhcp, so we need to supply our own implementation
    // of the option parsing function here, which would rely on the
    // configuration data.
    query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
                                   _3, _4, _5));

    bool skip_unpack = false;

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

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

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

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

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

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

Francis Dupont's avatar
Francis Dupont committed
503
504
        callout_handle->getArgument("query6", query);
    }
505

Francis Dupont's avatar
Francis Dupont committed
506
507
508
509
510
    // 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)
511
                .arg(query->getRemoteAddr().toText())
Francis Dupont's avatar
Francis Dupont committed
512
513
514
515
516
517
518
519
520
521
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface());
            query->unpack();
        } catch (const std::exception &e) {
            // Failed to parse the packet.
            LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_DETAIL,
                      DHCP6_PACKET_DROP_PARSE_FAIL)
                .arg(query->getRemoteAddr().toText())
                .arg(query->getLocalAddr().toText())
                .arg(query->getIface())
522
                .arg(e.what());
523

Francis Dupont's avatar
Francis Dupont committed
524
525
526
527
528
529
            // 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;
530
        }
Francis Dupont's avatar
Francis Dupont committed
531
    }
532

Francis Dupont's avatar
Francis Dupont committed
533
534
    // Update statistics accordingly for received packet.
    processStatsReceived(query);
535

Francis Dupont's avatar
Francis Dupont committed
536
537
538
    // Check if received query carries server identifier matching
    // server identifier being used by the server.
    if (!testServerID(query)) {
539

Francis Dupont's avatar
Francis Dupont committed
540
541
542
543
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
544

Francis Dupont's avatar
Francis Dupont committed
545
546
547
548
    // 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)) {
549

Francis Dupont's avatar
Francis Dupont committed
550
551
552
553
        // Increase the statistic of dropped packets.
        StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
        return;
    }
554

Francis Dupont's avatar
Francis Dupont committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
    LOG_DEBUG(packet6_logger, DBG_DHCP6_BASIC_DATA, DHCP6_PACKET_RECEIVED)
        .arg(query->getLabel())
        .arg(query->getName())
        .arg(static_cast<int>(query->getType()))
        .arg(query->getRemoteAddr())
        .arg(query->getLocalAddr())
        .arg(query->getIface());
    LOG_DEBUG(packet6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
        .arg(query->getLabel())
        .arg(query->toText());

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

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

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

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

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

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

Francis Dupont's avatar
Francis Dupont committed
592
593
        callout_handle->getArgument("query6", query);
    }
594

Francis Dupont's avatar
Francis Dupont committed
595
596
    // Assign this packet to a class, if possible
    classifyPacket(query);
597

Francis Dupont's avatar
Francis Dupont committed
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
    try {
        NameChangeRequestPtr ncr;

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

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

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

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

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

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

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

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

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

Francis Dupont's avatar
Francis Dupont committed
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
    } catch (const RFCViolation& e) {
        LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
            .arg(query->getName())
            .arg(query->getRemoteAddr().toText())
            .arg(e.what());

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

    } catch (const std::exception& e) {

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

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

672
673
674
    if (!rsp) {
        return;
    }
675

676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
    // Process relay-supplied options. It is important to call this very
    // late in the process, because we now have all the options the
    // server wanted to send already set. This is important, because
    // RFC6422, section 6 states:
    //
    //   The server SHOULD discard any options that appear in the RSOO
    //   for which it already has one or more candidates.
    //
    // So we ignore any RSOO options if there's an option with the same
    // code already present.
    processRSOO(query, rsp);

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

    if (rsp->relay_info_.empty()) {
        // Direct traffic, send back to the client directly
        rsp->setRemotePort(DHCP6_CLIENT_PORT);
    } else {
        // Relayed traffic, send back to the relay agent
        rsp->setRemotePort(DHCP6_SERVER_PORT);
    }
698

699
700
701
    rsp->setLocalPort(DHCP6_SERVER_PORT);
    rsp->setIndex(query->getIndex());
    rsp->setIface(query->getIface());
702

703
704
    // Specifies if server should do the packing
    bool skip_pack = false;
705

706
707
708
709
710
711
    // Server's reply packet now has all options and fields set.
    // Options are represented by individual objects, but the
    // output wire data has not been prepared yet.
    // Execute all callouts registered for packet6_send
    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
        CalloutHandlePtr callout_handle = getCalloutHandle(query);
712

713
714
        // Delete all previous arguments
        callout_handle->deleteAllArguments();
715

716
717
        // Set our response
        callout_handle->setArgument("response6", rsp);
718

719
720
        // Call all installed callouts
        HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
721

722
723
724
725
726
727
728
729
730
        // 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;
731
        }
732

733
734
        /// @todo: Add support for DROP status
    }
735

736
    if (!skip_pack) {
Francis Dupont's avatar
Francis Dupont committed
737
        try {
738
            rsp->pack();
739
        } catch (const std::exception& e) {
740
741
            LOG_ERROR(options6_logger, DHCP6_PACK_FAIL).arg(e.what());
            return;
742
        }
743
744
745

    }
}
746

747
748
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
749
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
750

751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
    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();
}

767
void
Tomek Mrugalski's avatar
Tomek Mrugalski committed
768
Dhcpv6Srv::copyClientOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
769
770
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
771
772
773
    if (clientid) {
        answer->addOption(clientid);
    }
774
775
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

776
    // If this is a relayed message, we need to copy relay information
777
778
779
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
780

781
782
}

783
void
784
785
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer,
                                const CfgOptionList&) {
786
787
788
789
    // add server-id
    answer->addOption(getServerID());
}

790
791
void
Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
792
793
                              AllocEngine::ClientContext6& ctx,
                              CfgOptionList& co_list) {
794
    // First subnet configured options
795
    if (ctx.subnet_ && !ctx.subnet_->getCfgOption()->empty()) {
796
797
798
799
800
801
802
803
804
805
806
807
        co_list.push_back(ctx.subnet_->getCfgOption());
    }

    // Each class in the incoming packet
    const ClientClasses& classes = question->getClasses();
    for (ClientClasses::const_iterator cclass = classes.begin();
         cclass != classes.end(); ++cclass) {
        // Find the client class definition for this class
        const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
            getClientClassDictionary()->findClass(*cclass);
        if (!ccdef) {
            // Not found: the class is not configured
808
809
810
811
            if (((*cclass).size() <= VENDOR_CLASS_PREFIX.size()) ||
                ((*cclass).compare(0, VENDOR_CLASS_PREFIX.size(), VENDOR_CLASS_PREFIX) != 0)) {
                // Not a VENDOR_CLASS_* so should be configured
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNCONFIGURED)
812
                    .arg(question->getLabel())
813
814
815
                    .arg(*cclass);
            }
            // Skip it
816
817
            continue;
        }
818
819
820
821
        if (ccdef->getCfgOption()->empty()) {
            // Skip classes which don't configure options
            continue;
        }
822
823
824
825
        co_list.push_back(ccdef->getCfgOption());
    }

    // Last global options
826
827
828
    if (!CfgMgr::instance().getCurrentCfg()->getCfgOption()->empty()) {
        co_list.push_back(CfgMgr::instance().getCurrentCfg()->getCfgOption());
    }
829
830
}

831
void
832
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
833
                                  const CfgOptionList& co_list) {
834

835
836
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
837
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
838
839
840
841
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
        (question->getOption(D6O_ORO));

    // Option ORO not found? We're done here then.
842
    if (!option_oro || co_list.empty()) {
843
844
        return;
    }
845

846
847
848
    // 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) {
849
850
851
852
853
        // Iterate on the configured option list
        for (CfgOptionList::const_iterator copts = co_list.begin();
             copts != co_list.end(); ++copts) {
            OptionDescriptor desc = (*copts)->get("dhcp6", opt);
            // Got it: add it and jump to the outer loop
854
855
            if (desc.option_) {
                answer->addOption(desc.option_);
856
                break;
857
            }
858
859
        }
    }
860
861
}

862
void
863
864
865
866
Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
                                        Pkt6Ptr& answer,
                                        AllocEngine::ClientContext6& ctx,
                                        const CfgOptionList& co_list) {
867

868
869
870
871
872
    // 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.
873
    if (!ctx.subnet_) {
874
875
876
877
878
879
        return;
    }

    // Try to get the vendor option
    boost::shared_ptr<OptionVendor> vendor_req =
        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
880
    if (!vendor_req || co_list.empty()) {
881
882
883
884
885
886
887
888
889
890
891
892
893
894
        return;
    }

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

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

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

897
898
899
900
901
902
    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));

    // Get the list of options that client requested.
    bool added = false;
    const std::vector<uint16_t>& requested_opts = oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
903
904
905
906
907
908
909
910
        for (CfgOptionList::const_iterator copts = co_list.begin();
             copts != co_list.end(); ++copts) {
            OptionDescriptor desc = (*copts)->get(vendor_id, opt);
            if (desc.option_) {
                vendor_rsp->addOption(desc.option_);
                added = true;
                break;
            }
911
912
913
914
915
916
917
918
        }
    }

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

919
920
921
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
922
    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
    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;
    }

943
    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
944
945
    switch (serverid) {
    case FORBIDDEN:
946
        if (!server_ids.empty()) {
947
            isc_throw(RFCViolation, "Server-id option was not expected, but "
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
                      << 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()
963
                      << ") server-id options received in " << pkt->getName());
964
965
966
967
        }
    }
}

968
969
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
970
971
972
973
974
975
976
977
978
    // Initialize subnet selector with the values used to select the subnet.
    SubnetSelector selector;
    selector.iface_name_ = question->getIface();
    selector.remote_address_ = question->getRemoteAddr();
    selector.first_relay_linkaddr_ = IOAddress("::");
    selector.client_classes_ = question->classes_;

    // Initialize fields specific to relayed messages.
    if (!question->relay_info_.empty()) {
979
980
981
982
983
984
985
        BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, question->relay_info_) {
            if (!relay.linkaddr_.isV6Zero() &&
                !relay.linkaddr_.isV6LinkLocal()) {
                selector.first_relay_linkaddr_ = relay.linkaddr_;
                break;
            }
        }
986
987
988
989
        selector.interface_id_ =
            question->getAnyRelayOption(D6O_INTERFACE_ID,
                                        Pkt6::RELAY_GET_FIRST);
    }
990

991
992
    Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
        getCfgSubnets6()->selectSubnet(selector);
993

994

995
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
996
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
997
998
999
1000
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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