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

15
16
#include <config.h>

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

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

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

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

68
69
70
71
namespace {

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

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

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

}; // anonymous namespace

100
101
namespace isc {
namespace dhcp {
102

103
104
const std::string Dhcpv6Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");

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

115
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
116
:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
117
{
118

119
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
120

121
    // Initialize objects required for DHCP server operation.
122
    try {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
123
124
        // Port 0 is used for testing purposes. It means that the server should
        // not open any sockets at all. Some tests, e.g. configuration parser,
125
126
        // require Dhcpv6Srv object, but they don't really need it to do
        // anything. This speed up and simplifies the tests.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
127
        if (port > 0) {
128
129
130
131
            if (IfaceMgr::instance().countIfaces() == 0) {
                LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
                return;
            }
132
133
134
135
136
137
            // Create error handler. This handler will be called every time
            // the socket opening operation fails. We use this handler to
            // log a warning.
            isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
                boost::bind(&Dhcpv6Srv::ifaceMgrSocket6ErrorHandler, _1);
            IfaceMgr::instance().openSockets6(port_, error_handler);
138
        }
139

140
141
142
        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
        if (loadServerID(duid_file)) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
143
                .arg(duidToString(getServerID()))
144
145
146
147
148
149
150
151
152
153
154
155
                .arg(duid_file);
        } else {
            generateServerID();
            LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
                .arg(duidToString(getServerID()))
                .arg(duid_file);

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

157
158
159
        // Instantiate allocation engine
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));

160
161
        /// @todo call loadLibraries() when handling configuration changes

162
    } catch (const std::exception &e) {
163
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
164
165
        return;
    }
166

167
168
    // All done, so can proceed
    shutdown_ = false;
169
170
}

171
Dhcpv6Srv::~Dhcpv6Srv() {
172
    IfaceMgr::instance().closeSockets();
173

174
    LeaseMgrFactory::destroy();
175
176
}

177
void Dhcpv6Srv::shutdown() {
178
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
179
180
181
    shutdown_ = true;
}

182
183
184
185
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

186
187
188
189
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

190
191
bool
Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt){
192
193
194
195
196
197
198
199
200
201
202
	/// @todo Currently we always check server identifier regardless if
	/// it is allowed in the received message or not (per RFC3315).
	/// If the server identifier is not allowed in the message, the
	/// sanityCheck function should deal with it. We may rethink this
	/// design if we decide that it is appropriate to check at this stage
	/// of message processing that the server identifier must or must not
	/// be present. In such case however, the logic checking server id
	/// will have to be removed from sanityCheck and placed here instead,
	/// to avoid duplicate checks.
	OptionPtr server_id = pkt->getOption(D6O_SERVERID);
	if (server_id){
203
204
205
		// Let us test received ServerID if it is same as ServerID
		// which is beeing used by server
		if (getServerID()->getData() != server_id->getData()){
206
			LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
207
208
209
				.arg(pkt->getName())
				.arg(pkt->getTransid())
				.arg(pkt->getIface());
210
			return (false);
211
212
		}
	}
213
214
	// retun True if: no serverid received or ServerIDs matching
	return (true);
215
216
}

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
228
        // client's message and server's response
229
        Pkt6Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
230
        Pkt6Ptr rsp;
231

232
        try {
233
            query = receivePacket(timeout);
Marcin Siodelski's avatar
Marcin Siodelski committed
234
        } catch (const std::exception& e) {
235
236
237
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

238
239
240
241
        // Timeout may be reached or signal received, which breaks select() with no packet received
        if (!query) {
            continue;
        }
242

243
244
245
246
247
248
249
250
251
        // 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));

252
        bool skip_unpack = false;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
253

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

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

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

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

268
269
            // Callouts decided to skip the next processing step. The next
            // processing step would to parse the packet, so skip at this
Tomek Mrugalski's avatar
Tomek Mrugalski committed
270
271
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
272
273
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
274
                skip_unpack = true;
275
276
            }

277
278
            callout_handle->getArgument("query6", query);
        }
279

Tomek Mrugalski's avatar
Tomek Mrugalski committed
280
281
        // Unpack the packet information unless the buffer6_receive callouts
        // indicated they did it
282
        if (!skip_unpack) {
283
            if (!query->unpack()) {
284
285
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
286
287
                continue;
            }
288
        }
289
290
        // Check if received query carries server identifier matching
        // server identifier being used by the server.
291
292
293
294
        if (!testServerID(query)){
        	continue;
        }

295
296
297
298
299
300
301
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
            .arg(query->getName());
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
            .arg(static_cast<int>(query->getType()))
            .arg(query->getBuffer().getLength())
            .arg(query->toText());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
302
303
304
        // At this point the information in the packet has been unpacked into
        // the various packet fields and option objects has been cretated.
        // Execute callouts registered for packet6_receive.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
305
        if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
            CalloutHandlePtr callout_handle = getCalloutHandle(query);

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

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

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

            // Callouts decided to skip the next processing step. The next
            // processing step would to process the packet, so skip at this
            // stage means drop.
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
                continue;
            }
324

325
326
            callout_handle->getArgument("query6", query);
        }
327

328
329
330
        // Assign this packet to a class, if possible
        classifyPacket(query);

331
        try {
332
                NameChangeRequestPtr ncr;
333
334
335
            switch (query->getType()) {
            case DHCPV6_SOLICIT:
                rsp = processSolicit(query);
336
                    break;
337

338
339
340
            case DHCPV6_REQUEST:
                rsp = processRequest(query);
                break;
341

342
343
344
            case DHCPV6_RENEW:
                rsp = processRenew(query);
                break;
345

346
347
348
            case DHCPV6_REBIND:
                rsp = processRebind(query);
                break;
349

350
351
352
            case DHCPV6_CONFIRM:
                rsp = processConfirm(query);
                break;
353

354
355
356
357
358
359
360
361
362
363
364
            case DHCPV6_RELEASE:
                rsp = processRelease(query);
                break;

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

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

366
            default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
367
368
369
370
                // We received a packet type that we do not recognize.
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
                    .arg(static_cast<int>(query->getType()))
                    .arg(query->getIface());
371
372
373
374
                // Only action is to output a message if debug is enabled,
                // and that will be covered by the debug statement before
                // the "switch" statement.
                ;
375
            }
376

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
        } catch (const RFCViolation& e) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
                .arg(query->getName())
                .arg(query->getRemoteAddr().toText())
                .arg(e.what());

        } catch (const isc::Exception& e) {

            // Catch-all exception (at least for ones based on the isc
            // Exception class, which covers more or less all that
            // are explicitly raised in the BIND 10 code).  Just log
            // the problem and ignore the packet. (The problem is logged
            // as a debug message because debug is disabled by default -
            // it prevents a DDOS attack based on the sending of problem
            // packets.)
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
                .arg(query->getName())
                .arg(query->getRemoteAddr().toText())
                .arg(e.what());
        }
397

398
399
400
        if (rsp) {
            rsp->setRemoteAddr(query->getRemoteAddr());
            rsp->setLocalAddr(query->getLocalAddr());
401
402
403
404
405
406
407
408
409

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

410
411
412
413
            rsp->setLocalPort(DHCP6_SERVER_PORT);
            rsp->setIndex(query->getIndex());
            rsp->setIface(query->getIface());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
414
            // Specifies if server should do the packing
415
416
            bool skip_pack = false;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
417
418
419
            // 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.
420
            // Execute all callouts registered for packet6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
421
            if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
422
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
423

424
425
426
427
428
                // Delete all previous arguments
                callout_handle->deleteAllArguments();

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

430
431
432
433
                // Call all installed callouts
                HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);

                // Callouts decided to skip the next processing step. The next
Tomek Mrugalski's avatar
Tomek Mrugalski committed
434
435
436
437
                // 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.
438
439
                if (callout_handle->getSkip()) {
                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
440
                    skip_pack = true;
441
                }
442
            }
443

444
445
446
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                      DHCP6_RESPONSE_DATA)
                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
447

448
            if (!skip_pack) {
449
450
451
452
453
                try {
                    rsp->pack();
                } catch (const std::exception& e) {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
                        .arg(e.what());
454
455
                    continue;
                }
456

457
458
            }

459
            try {
460

Tomek Mrugalski's avatar
Tomek Mrugalski committed
461
462
463
                // 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.
464
                // Let's execute all callouts registered for buffer6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
465
                if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
466
                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
467

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

471
                    // Pass incoming packet as argument
Tomek Mrugalski's avatar
Tomek Mrugalski committed
472
                    callout_handle->setArgument("response6", rsp);
473

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

477
                    // Callouts decided to skip the next processing step. The next
478
479
                    // processing step would to parse the packet, so skip at this
                    // stage means drop.
480
                    if (callout_handle->getSkip()) {
481
                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
482
483
                        continue;
                    }
484

485
                    callout_handle->getArgument("response6", rsp);
486
                }
487
488
489

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

492
493
                sendPacket(rsp);
            } catch (const std::exception& e) {
494
495
                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                    .arg(e.what());
496
            }
497
498
499
        }
    }

500
    return (true);
501
}
502

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
bool Dhcpv6Srv::loadServerID(const std::string& file_name) {

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

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

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

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

    std::vector<uint8_t> bin;

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

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

    return (true);
}

535
536
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
537
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
538

539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    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();
}

555
556
bool
Dhcpv6Srv::writeServerID(const std::string& file_name) {
557
558
559
560
561
562
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
563
    return (true);
564
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
565

566
567
void
Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
568
569
570
571
572
573
574

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

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
575
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
576
577
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
578
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
579
580
581
582
        // statement, but let's keep them separated as perhaps one day
        // we will grow knobs to selectively turn them on or off. Also,
        // this code is used only *once* during first start on a new machine
        // and then server-id is stored. (or at least it will be once
583
        // DUID storage is implemented)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
584
585
586

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
587
588
589
590
591
        // MAC address should be at least 6 bytes. Although there is no such
        // requirement in any RFC, all decent physical interfaces (Ethernet,
        // WiFi, Infiniband, etc.) have 6 bytes long MAC address. We want to
        // base our DUID on real hardware address, rather than virtual
        // interface that pretends that underlying IP address is its MAC.
592
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
593
594
595
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
596
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
597
598
599
600
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
601
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
602
603
604
605
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
606
        // Some interfaces (like lo on Linux) report 6-bytes long
607
        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
Tomek Mrugalski's avatar
Tomek Mrugalski committed
608
        // to generate DUID.
609
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
610
611
612
613
614
615
616
617
618
619
620
            continue;
        }

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

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

621
        OptionBuffer srvid(8 + iface->getMacLen());
Stephen Morris's avatar
Stephen Morris committed
622
        // We know that the buffer is more than 8 bytes long at this point.
623
624
625
626
        writeUint16(DUID::DUID_LLT, &srvid[0], 2);
        writeUint16(HWTYPE_ETHERNET, &srvid[2], 2);
        writeUint32(static_cast<uint32_t>(seconds), &srvid[4], 4);
        memcpy(&srvid[8], iface->getMac(), iface->getMacLen());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
627
628
629
630

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
633
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
634
635
636
637
638
    // Either interface detection is not supported on this platform or
    // this is really weird box. Let's use DUID-EN instead.
    // See Section 9.3 of RFC3315 for details.

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

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
647
648
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
649
650
}

651
652
void
Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
653
654
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
655
656
657
    if (clientid) {
        answer->addOption(clientid);
    }
658
659
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

660
    // If this is a relayed message, we need to copy relay information
661
662
663
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
664

665
666
}

667
void
668
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
669
670
671
672
    // add server-id
    answer->addOption(getServerID());
}

673
674
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
675
676
677
678
679
680
681
    // Get the configured subnet suitable for the incoming packet.
    Subnet6Ptr subnet = selectSubnet(question);
    // Leave if there is no subnet matching the incoming packet.
    // There is no need to log the error message here because
    // it will be logged in the assignLease() when it fails to
    // pick the suitable subnet. We don't want to duplicate
    // error messages in such case.
682
683
684
    if (!subnet) {
        return;
    }
685

686
687
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
688
689
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
690
691
692
693
694
695
696
    // Option ORO not found. Don't do anything then.
    if (!option_oro) {
        return;
    }
    // Get the list of options that client requested.
    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
697
698
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
699
700
701
            answer->addOption(desc.option);
        }
    }
702
703
}

704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
void
Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
    // Get the configured subnet suitable for the incoming packet.
    Subnet6Ptr subnet = selectSubnet(question);
    // Leave if there is no subnet matching the incoming packet.
    // There is no need to log the error message here because
    // it will be logged in the assignLease() when it fails to
    // pick the suitable subnet. We don't want to duplicate
    // error messages in such case.
    if (!subnet) {
        return;
    }

    // Try to get the vendor option
    boost::shared_ptr<OptionVendor> vendor_req =
        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
    if (!vendor_req) {
        return;
    }

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

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

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

737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));

    // Get the list of options that client requested.
    bool added = false;
    const std::vector<uint16_t>& requested_opts = oro->getValues();
    BOOST_FOREACH(uint16_t opt, requested_opts) {
        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt);
        if (desc.option) {
            vendor_rsp->addOption(desc.option);
            added = true;
        }
    }

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

755
756
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
757
758
759
760
761
762
    // @todo This function uses OptionCustom class to manage contents
    // of the data fields. Since this this option is frequently used
    // it may be good to implement dedicated class to avoid performance
    // impact.

    // Get the definition of the option holding status code.
763
764
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
765
    // This definition is assumed to be initialized in LibDHCP.
766
767
    assert(status_code_def);

768
    // As there is no dedicated class to represent Status Code
769
770
771
    // the OptionCustom class is used here instead.
    OptionCustomPtr option_status =
        OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
772
773
    assert(option_status);

774
    // Set status code to 'code' (0 - means data field #0).
775
    option_status->writeInteger(code, 0);
776
    // Set a message (1 - means data field #1).
777
778
    option_status->writeString(text, 1);
    return (option_status);
779
780
}

781
782
783
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
784
    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
    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;
    }

805
    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
806
807
    switch (serverid) {
    case FORBIDDEN:
808
        if (!server_ids.empty()) {
809
            isc_throw(RFCViolation, "Server-id option was not expected, but "
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
                      << 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()
825
                      << ") server-id options received in " << pkt->getName());
826
827
828
829
        }
    }
}

830
831
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
832

833
    Subnet6Ptr subnet;
834

835
836
837
838
    if (question->relay_info_.empty()) {
        // This is a direct (non-relayed) message

        // Try to find a subnet if received packet from a directly connected client
839
840
        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
                                               question->classes_);
841
842
        if (!subnet) {
            // If no subnet was found, try to find it based on remote address
843
844
            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
                                                   question->classes_);
845
846
        }
    } else {
847

848
849
        // This is a relayed message
        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
850
                                                             Pkt6::RELAY_GET_FIRST);
851
        if (interface_id) {
852
853
            subnet = CfgMgr::instance().getSubnet6(interface_id,
                                                   question->classes_);
854
        }
855

856
        if (!subnet) {
857
858
            // If no interface-id was specified (or not configured on server),
            // let's try address matching
859
            IOAddress link_addr = question->relay_info_.back().linkaddr_;
860

861
862
            // if relay filled in link_addr field, then let's use it
            if (link_addr != IOAddress("::")) {
863
                subnet = CfgMgr::instance().getSubnet6(link_addr,
864
                                                       question->classes_, true);
865
            }
866
867
        }
    }
868

869
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
870
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
871
872
873
874
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
876
877
        // Set new arguments
        callout_handle->setArgument("query6", question);
878
        callout_handle->setArgument("subnet6", subnet);
879
880
881
882

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

        // Call user (and server-side) callouts
886
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
887
888
889
890
891
892
893
894
895
896
897
898
899

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

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

900
    return (subnet);
901
902
}

903
void
904
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
905

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

    // We need to select a subnet the client is connected in.
911
    Subnet6Ptr subnet = selectSubnet(question);
912
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
913
914
915
916
917
918
919
        // This particular client is out of luck today. We do not have
        // information about the subnet he is connected to. This likely means
        // misconfiguration of the server (or some relays). We will continue to
        // process this message, but our response will be almost useless: no
        // addresses or prefixes, no subnet specific configuration etc. The only
        // thing this client can get is some global information (like DNS
        // servers).
920

921
        LOG_WARN(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
922
923
924
            .arg(question->getRemoteAddr().toText())
            .arg(question->getName());

925
926
927
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
928
929
930
931
    }

    // @todo: We should implement Option6Duid some day, but we can do without it
    // just fine for now
Tomek Mrugalski's avatar
Tomek Mrugalski committed
932
933
934
935
936

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
947
948
949
950
951
952
    // Now that we have all information about the client, let's iterate over all
    // received options and handle IA_NA options one by one and store our
    // responses in answer message (ADVERTISE or REPLY).
    //
    // @todo: expand this to cover IA_PD and IA_TA once we implement support for
    // prefix delegation and temporary addresses.
953
    for (OptionCollection::iterator opt = question->options_.begin();
954
         opt != question->options_.end(); ++opt) {
955
956
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
957
            OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
958
                                               boost::dynamic_pointer_cast<
959
                                               Option6IA>(opt->second));
960
961
962
963
964
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
            break;
        }
965
966
967
968
969
970
971
972
        case D6O_IA_PD: {
            OptionPtr answer_opt = assignIA_PD(subnet, duid, question,
                                               boost::dynamic_pointer_cast<
                                               Option6IA>(opt->second));
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
        }
973
974
        default:
            break;
975
976
        }
    }
977
}
978

979
void
980
Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
981
982
983
984
985
    // Get Client FQDN Option from the client's message. If this option hasn't
    // been included, do nothing.
    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
        Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
    if (!fqdn) {
986
        return;
987
988
    }

989
990
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
              DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());
991
992
    // Create the DHCPv6 Client FQDN Option to be included in the server's
    // response to a client.
993
    Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
994

995
996
997
998
    // Set the server S, N, and O flags based on client's flags and
    // current configuration.
    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
    d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
999

1000
1001
1002
    // Adjust the domain name based on domain name value and type sent by the
    // client and current configuration.
    d2_mgr.adjustDomainName<Option6ClientFqdn>(*fqdn, *fqdn_resp);
1003

1004
1005
1006
1007
1008
    // The FQDN has been processed successfully. Let's append it to the
    // response to be sent to a client. Note that the Client FQDN option is
    // always sent back to the client if Client FQDN was included in the
    // client's message.
    answer->addOption(fqdn_resp);
1009
1010
1011
}

void
1012
Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
1013
    // Don't create NameChangeRequests if DNS updates are disabled.
1014
    if (!CfgMgr::instance().ddnsEnabled()) {
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
        return;
    }

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

1027
1028
1029
1030
1031
1032
1033
1034
    // It is likely that client haven't included the FQDN option. In such case,
    // FQDN option will be NULL. This is valid state, so we simply return.
    Option6ClientFqdnPtr opt_fqdn = boost::dynamic_pointer_cast<
        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
    if (!opt_fqdn) {
        return;
    }

1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
    // Get the update directions that should be performed based on our
    // response FQDN flags.
    bool do_fwd = false;
    bool do_rev = false;
    CfgMgr::instance().getD2ClientMgr().getUpdateDirections(*opt_fqdn,
                                                             do_fwd, do_rev);
    if (!do_fwd && !do_rev) {
        // Flags indicate there is Nothing to do, get out now.
        // The Most likely scenario is that we are honoring the client's
        // request that no updates be done.
        return;
    }

1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
    // Get the Client Id. It is mandatory and a function creating a response
    // would have thrown an exception if it was missing. Thus throwning
    // Unexpected if it is missing as it is a programming error.
    OptionPtr opt_duid = answer->getOption(D6O_CLIENTID);
    if (!opt_duid) {
        isc_throw(isc::Unexpected,
                  "client identifier is required when creating a new"
                  " DNS NameChangeRequest");
    }
    DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));

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

    // Get all IAs from the answer. For each IA, holding an address we will
    // create a corresponding NameChangeRequest.
1072
1073