dhcp6_srv.cc 98 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
namespace {

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

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

}

128
129
130
131
132
133
134
135
136
137
/// @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";

138
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
139
:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
140
{
141

142
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
143

144
    // Initialize objects required for DHCP server operation.
145
    try {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
146
147
        // 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,
148
149
        // 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
150
        if (port > 0) {
151
152
153
154
            if (IfaceMgr::instance().countIfaces() == 0) {
                LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
                return;
            }
155
156
157
158
159
160
            // 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);
161
        }
162

163
164
165
        string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
        if (loadServerID(duid_file)) {
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
166
                .arg(duidToString(getServerID()))
167
168
169
170
171
172
173
174
175
176
177
178
                .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);
            }
        }
179

180
181
182
        // Instantiate allocation engine
        alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));

183
184
        /// @todo call loadLibraries() when handling configuration changes

185
    } catch (const std::exception &e) {
186
        LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
187
188
        return;
    }
189

190
191
    // All done, so can proceed
    shutdown_ = false;
192
193
}

194
Dhcpv6Srv::~Dhcpv6Srv() {
195
    IfaceMgr::instance().closeSockets();
196

197
    LeaseMgrFactory::destroy();
198
199
}

200
void Dhcpv6Srv::shutdown() {
201
    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SHUTDOWN_REQUEST);
202
203
204
    shutdown_ = true;
}

205
206
207
208
Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
    return (IfaceMgr::instance().receive6(timeout));
}

209
210
211
212
void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
    IfaceMgr::instance().send(packet);
}

213
214
bool
Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt){
215
216
217
218
219
220
221
222
223
224
225
	/// @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){
226
227
228
		// Let us test received ServerID if it is same as ServerID
		// which is beeing used by server
		if (getServerID()->getData() != server_id->getData()){
229
			LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
230
231
232
				.arg(pkt->getName())
				.arg(pkt->getTransid())
				.arg(pkt->getIface());
233
			return (false);
234
235
		}
	}
236
237
	// retun True if: no serverid received or ServerIDs matching
	return (true);
238
239
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
240
bool Dhcpv6Srv::run() {
241
    while (!shutdown_) {
242
        /// @todo Calculate actual timeout to the next event (e.g. lease
Tomek Mrugalski's avatar
Tomek Mrugalski committed
243
244
245
246
247
        /// 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.
248
249
        //cppcheck-suppress variableScope This is temporary anyway
        const int timeout = 1000;
250

Tomek Mrugalski's avatar
Tomek Mrugalski committed
251
        // client's message and server's response
252
        Pkt6Ptr query;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
253
        Pkt6Ptr rsp;
254

255
        try {
256
            query = receivePacket(timeout);
Marcin Siodelski's avatar
Marcin Siodelski committed
257
        } catch (const std::exception& e) {
258
259
260
            LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
        }

261
262
263
264
        // Timeout may be reached or signal received, which breaks select() with no packet received
        if (!query) {
            continue;
        }
265

266
267
268
269
270
271
272
273
274
        // 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));

275
        bool skip_unpack = false;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
276

Tomek Mrugalski's avatar
Tomek Mrugalski committed
277
278
        // 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
279
        if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
280
            CalloutHandlePtr callout_handle = getCalloutHandle(query);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
281

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

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

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

291
292
            // 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
293
294
            // stage means that callouts did the parsing already, so server
            // should skip parsing.
295
296
            if (callout_handle->getSkip()) {
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
297
                skip_unpack = true;
298
299
            }

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
303
304
        // Unpack the packet information unless the buffer6_receive callouts
        // indicated they did it
305
        if (!skip_unpack) {
306
            if (!query->unpack()) {
307
308
                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                          DHCP6_PACKET_PARSE_FAIL);
309
310
                continue;
            }
311
        }
312
313
        // Check if received query carries server identifier matching
        // server identifier being used by the server.
314
315
316
317
        if (!testServerID(query)){
        	continue;
        }

318
319
320
321
322
323
324
        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
325
326
327
        // 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
328
        if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
            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;
            }
347

348
349
            callout_handle->getArgument("query6", query);
        }
350

351
352
353
        // Assign this packet to a class, if possible
        classifyPacket(query);

354
        try {
355
                NameChangeRequestPtr ncr;
356
357
358
            switch (query->getType()) {
            case DHCPV6_SOLICIT:
                rsp = processSolicit(query);
359
                    break;
360

361
362
363
            case DHCPV6_REQUEST:
                rsp = processRequest(query);
                break;
364

365
366
367
            case DHCPV6_RENEW:
                rsp = processRenew(query);
                break;
368

369
370
371
            case DHCPV6_REBIND:
                rsp = processRebind(query);
                break;
372

373
374
375
            case DHCPV6_CONFIRM:
                rsp = processConfirm(query);
                break;
376

377
378
379
380
381
382
383
384
385
386
387
            case DHCPV6_RELEASE:
                rsp = processRelease(query);
                break;

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

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

389
            default:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
390
391
392
393
                // 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());
394
395
396
397
                // Only action is to output a message if debug is enabled,
                // and that will be covered by the debug statement before
                // the "switch" statement.
                ;
398
            }
399

400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        } 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());
        }
420

421
422
423
        if (rsp) {
            rsp->setRemoteAddr(query->getRemoteAddr());
            rsp->setLocalAddr(query->getLocalAddr());
424
425
426
427
428
429
430
431
432

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

433
434
435
436
            rsp->setLocalPort(DHCP6_SERVER_PORT);
            rsp->setIndex(query->getIndex());
            rsp->setIface(query->getIface());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
437
            // Specifies if server should do the packing
438
439
            bool skip_pack = false;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
440
441
442
            // 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.
443
            // Execute all callouts registered for packet6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
444
            if (HooksManager::calloutsPresent(Hooks.hook_index_pkt6_send_)) {
445
                CalloutHandlePtr callout_handle = getCalloutHandle(query);
446

447
448
449
450
451
                // Delete all previous arguments
                callout_handle->deleteAllArguments();

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

453
454
455
456
                // 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
457
458
459
460
                // 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.
461
462
                if (callout_handle->getSkip()) {
                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
463
                    skip_pack = true;
464
                }
465
            }
466

467
468
469
            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                      DHCP6_RESPONSE_DATA)
                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
470

471
            if (!skip_pack) {
472
473
474
475
476
                try {
                    rsp->pack();
                } catch (const std::exception& e) {
                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
                        .arg(e.what());
477
478
                    continue;
                }
479

480
481
            }

482
            try {
483

Tomek Mrugalski's avatar
Tomek Mrugalski committed
484
485
486
                // 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.
487
                // Let's execute all callouts registered for buffer6_send
Tomek Mrugalski's avatar
Tomek Mrugalski committed
488
                if (HooksManager::calloutsPresent(Hooks.hook_index_buffer6_send_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
489
                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
490

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

494
                    // Pass incoming packet as argument
Tomek Mrugalski's avatar
Tomek Mrugalski committed
495
                    callout_handle->setArgument("response6", rsp);
496

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

500
                    // Callouts decided to skip the next processing step. The next
501
502
                    // processing step would to parse the packet, so skip at this
                    // stage means drop.
503
                    if (callout_handle->getSkip()) {
504
                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
505
506
                        continue;
                    }
507

508
                    callout_handle->getArgument("response6", rsp);
509
                }
510
511
512

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

515
516
                sendPacket(rsp);
            } catch (const std::exception& e) {
517
518
                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                    .arg(e.what());
519
            }
520

521
            // Send NameChangeRequests to the b10-dhcp-ddns module.
522
            sendNameChangeRequests();
523
524
525
        }
    }

526
    return (true);
527
}
528

529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
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);
}

561
562
std::string
Dhcpv6Srv::duidToString(const OptionPtr& opt) {
563
    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
564

565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
    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();
}

581
582
bool
Dhcpv6Srv::writeServerID(const std::string& file_name) {
583
584
585
586
587
588
    fstream f(file_name.c_str(), ios::out | ios::trunc);
    if (!f.good()) {
        return (false);
    }
    f << duidToString(getServerID());
    f.close();
589
    return (true);
590
}
Tomek Mrugalski's avatar
Tomek Mrugalski committed
591

592
593
void
Dhcpv6Srv::generateServerID() {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
594
595
596
597
598
599
600

    /// @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
601
    // Let's find suitable interface.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
602
603
    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
         iface != ifaces.end(); ++iface) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
604
        // All the following checks could be merged into one multi-condition
Tomek Mrugalski's avatar
Tomek Mrugalski committed
605
606
607
608
        // 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
609
        // DUID storage is implemented)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
610
611
612

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
613
614
615
616
617
        // 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.
618
        if (iface->getMacLen() < MIN_MAC_LEN) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
619
620
621
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
622
        // Let's don't use loopback.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
623
624
625
626
        if (iface->flag_loopback_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
627
        // Let's skip downed interfaces. It is better to use working ones.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
628
629
630
631
        if (!iface->flag_up_) {
            continue;
        }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
632
        // Some interfaces (like lo on Linux) report 6-bytes long
633
        // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
Tomek Mrugalski's avatar
Tomek Mrugalski committed
634
        // to generate DUID.
635
        if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
636
637
638
639
640
641
642
643
644
645
646
            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;

647
        OptionBuffer srvid(8 + iface->getMacLen());
Stephen Morris's avatar
Stephen Morris committed
648
        // We know that the buffer is more than 8 bytes long at this point.
649
650
651
652
        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
653
654
655
656

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
659
    // If we reached here, there are no suitable interfaces found.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
660
661
662
663
664
    // 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);
665
666
    writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
    writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
667

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
673
674
    serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                     srvid.begin(), srvid.end()));
675
676
}

677
678
void
Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
679
680
    // Add client-id.
    OptionPtr clientid = question->getOption(D6O_CLIENTID);
681
682
683
    if (clientid) {
        answer->addOption(clientid);
    }
684
685
    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)

686
    // If this is a relayed message, we need to copy relay information
687
688
689
    if (!question->relay_info_.empty()) {
        answer->copyRelayInfo(question);
    }
690

691
692
}

693
void
694
Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr&, Pkt6Ptr& answer) {
695
696
697
698
    // add server-id
    answer->addOption(getServerID());
}

699
700
void
Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
701
702
703
704
705
706
707
    // 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.
708
709
710
    if (!subnet) {
        return;
    }
711

712
713
    // Client requests some options using ORO option. Try to
    // get this option from client's message.
714
715
    boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
716
717
718
719
720
721
722
    // 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) {
723
724
        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
        if (desc.option) {
725
726
727
            answer->addOption(desc.option);
        }
    }
728
729
}

730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
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
761
762
    uint32_t vendor_id = vendor_req->getVendorId();

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
    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);
    }
}

781
782
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
783
784
785
786
787
788
    // @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.
789
790
    OptionDefinitionPtr status_code_def =
        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
791
    // This definition is assumed to be initialized in LibDHCP.
792
793
    assert(status_code_def);

794
    // As there is no dedicated class to represent Status Code
795
796
797
    // the OptionCustom class is used here instead.
    OptionCustomPtr option_status =
        OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
798
799
    assert(option_status);

800
    // Set status code to 'code' (0 - means data field #0).
801
    option_status->writeInteger(code, 0);
802
    // Set a message (1 - means data field #1).
803
804
    option_status->writeString(text, 1);
    return (option_status);
805
806
}

807
808
809
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                       RequirementLevel serverid) {
810
    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
    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;
    }

831
    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
832
833
    switch (serverid) {
    case FORBIDDEN:
834
        if (!server_ids.empty()) {
835
            isc_throw(RFCViolation, "Server-id option was not expected, but "
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
                      << 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()
851
                      << ") server-id options received in " << pkt->getName());
852
853
854
855
        }
    }
}

856
857
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
858

859
    Subnet6Ptr subnet;
860

861
862
863
864
    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
865
866
867
868
        subnet = CfgMgr::instance().getSubnet6(question->getIface());
        if (!subnet) {
            // If no subnet was found, try to find it based on remote address
            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
869
870
        }
    } else {
871

872
873
        // This is a relayed message
        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
874
                                                             Pkt6::RELAY_GET_FIRST);
875
876
877
        if (interface_id) {
            subnet = CfgMgr::instance().getSubnet6(interface_id);
        }
878

879
880
881
882
        if (!subnet) {
            // If no interface-id was specified (or not configured on server), let's
            // try address matching
            IOAddress link_addr = question->relay_info_.back().linkaddr_;
883

884
885
886
887
            // if relay filled in link_addr field, then let's use it
            if (link_addr != IOAddress("::")) {
                subnet = CfgMgr::instance().getSubnet6(link_addr);
            }
888
889
        }
    }
890

891
    // Let's execute all callouts registered for subnet6_receive
Tomek Mrugalski's avatar
Tomek Mrugalski committed
892
    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
893
894
895
896
        CalloutHandlePtr callout_handle = getCalloutHandle(question);

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
898
899
        // Set new arguments
        callout_handle->setArgument("query6", question);
900
        callout_handle->setArgument("subnet6", subnet);
901
902
903
904

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

        // Call user (and server-side) callouts
908
        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
909
910
911
912
913
914
915
916
917
918
919
920
921

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

922
    return (subnet);
923
924
}

925
void
926
Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
927

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

    // We need to select a subnet the client is connected in.
933
    Subnet6Ptr subnet = selectSubnet(question);
934
    if (!subnet) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
935
936
937
938
939
940
941
        // 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).
942

943
        LOG_WARN(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
944
945
946
            .arg(question->getRemoteAddr().toText())
            .arg(question->getName());

947
948
949
    } else {
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
            .arg(subnet->toText());
950
951
952
953
    }

    // @todo: We should implement Option6Duid some day, but we can do without it
    // just fine for now
Tomek Mrugalski's avatar
Tomek Mrugalski committed
954
955
956
957
958

    // 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.
959
960
961
962
    DuidPtr duid;
    OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
    if (opt_duid) {
        duid = DuidPtr(new DUID(opt_duid->getData()));
963
    } else {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
964
        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
965
966
        // Let's drop the message. This client is not sane.
        isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
967
968
    }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
969
970
971
972
973
974
    // 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.
975
    for (OptionCollection::iterator opt = question->options_.begin();
976
         opt != question->options_.end(); ++opt) {
977
978
        switch (opt->second->getType()) {
        case D6O_IA_NA: {
979
            OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
980
                                               boost::dynamic_pointer_cast<
981
                                               Option6IA>(opt->second));
982
983
984
985
986
            if (answer_opt) {
                answer->addOption(answer_opt);
            }
            break;
        }
987
988
989
990
991
992
993
994
        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);
            }
        }
995
996
        default:
            break;
997
998
        }
    }
999
}
1000

For faster browsing, not all history is shown. View entire blame