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

7 8
#include <config.h>

9
#include <dhcp/dhcp4.h>
10
#include <dhcp/dhcp6.h>
11
#include <dhcp/libdhcp++.h>
12
#include <dhcp/option.h>
13
#include <dhcp/option_vendor.h>
14 15
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
16
#include <dhcp/option_definition.h>
17
#include <dhcp/option_int_array.h>
18
#include <dhcp/std_option_defs.h>
19
#include <dhcp/docsis3_option_defs.h>
20 21
#include <exceptions/exceptions.h>
#include <util/buffer.h>
22
#include <util/multi_threading_mgr.h>
23

24
#include <boost/lexical_cast.hpp>
25 26
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
27

28
#include <limits>
29
#include <list>
30
#include <mutex>
31

32
using namespace std;
33
using namespace isc::dhcp;
34
using namespace isc::util;
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
namespace isc {
namespace dhcp {

namespace {

const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
    { STANDARD_V4_OPTION_DEFINITIONS,       STANDARD_V4_OPTION_DEFINITIONS_SIZE,     DHCP4_OPTION_SPACE          },
    { STANDARD_V6_OPTION_DEFINITIONS,       STANDARD_V6_OPTION_DEFINITIONS_SIZE,     DHCP6_OPTION_SPACE          },
    { DOCSIS3_V4_OPTION_DEFINITIONS,        DOCSIS3_V4_OPTION_DEFINITIONS_SIZE,      DOCSIS3_V4_OPTION_SPACE     },
    { DOCSIS3_V6_OPTION_DEFINITIONS,        DOCSIS3_V6_OPTION_DEFINITIONS_SIZE,      DOCSIS3_V6_OPTION_SPACE     },
    { ISC_V6_OPTION_DEFINITIONS,            ISC_V6_OPTION_DEFINITIONS_SIZE,          ISC_V6_OPTION_SPACE         },
    { MAPE_V6_OPTION_DEFINITIONS,           MAPE_V6_OPTION_DEFINITIONS_SIZE,         MAPE_V6_OPTION_SPACE        },
    { MAPT_V6_OPTION_DEFINITIONS,           MAPT_V6_OPTION_DEFINITIONS_SIZE,         MAPT_V6_OPTION_SPACE        },
    { LW_V6_OPTION_DEFINITIONS,             LW_V6_OPTION_DEFINITIONS_SIZE,           LW_V6_OPTION_SPACE          },
    { V4V6_RULE_OPTION_DEFINITIONS,         V4V6_RULE_OPTION_DEFINITIONS_SIZE,       V4V6_RULE_OPTION_SPACE      },
    { V4V6_BIND_OPTION_DEFINITIONS,         V4V6_BIND_OPTION_DEFINITIONS_SIZE,       V4V6_BIND_OPTION_SPACE      },
    { LAST_RESORT_V4_OPTION_DEFINITIONS,    LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE,  LAST_RESORT_V4_OPTION_SPACE },
    { NULL,                                 0,                                       ""                          }
};

}  // namespace

}  // namespace dhcp
}  // namespace isc

61 62 63
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;

64
// static array with factories for options
65 66
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;

67 68 69
// Static container with option definitions grouped by option space.
OptionDefContainers LibDHCP::option_defs_;

70
// Static container with option definitions created in runtime.
71
StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
72

73 74
// Null container.
const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer());
75

Tomek Mrugalski's avatar
Tomek Mrugalski committed
76 77 78 79 80 81 82 83
// Those two vendor classes are used for cable modems:

/// DOCSIS3.0 compatible cable modem
const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";

/// DOCSIS3.0 cable modem that has router built-in
const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";

84 85
// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
// definitions there
86
void initOptionSpace(OptionDefContainerPtr& defs,
87 88 89
                     const OptionDefParams* params,
                     size_t params_size);

90
const OptionDefContainerPtr&
91
LibDHCP::getOptionDefs(const std::string& space) {
92
    static mutex local_mutex;
93 94
    if (MultiThreadingMgr::instance().getMode()) {
        std::lock_guard<std::mutex> lock(local_mutex);
Razvan Becheriu's avatar
Razvan Becheriu committed
95
        return LibDHCP::getOptionDefsInternal(space);
96
    } else {
Razvan Becheriu's avatar
Razvan Becheriu committed
97
        return LibDHCP::getOptionDefsInternal(space);
98 99 100 101 102
    }
}

const OptionDefContainerPtr&
LibDHCP::getOptionDefsInternal(const std::string& space) {
103 104
    // If any of the containers is not initialized, it means that we haven't
    // initialized option definitions at all.
105 106
    if (option_defs_.end() == option_defs_.find(space)) {
        initOptionDefs(space);
107
    }
108 109 110 111 112 113

    OptionDefContainers::const_iterator container = option_defs_.find(space);
    if (container != option_defs_.end()) {
        return (container->second);
    }
    return (null_option_def_container_);
114
}
115

116 117 118 119 120 121 122 123 124 125 126 127
const OptionDefContainerPtr
LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
    if (Option::V4 == u) {
        if (VENDOR_ID_CABLE_LABS == vendor_id) {
            return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
        }
    } else if (Option::V6 == u) {
        if (VENDOR_ID_CABLE_LABS == vendor_id) {
            return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
        } else if (ENTERPRISE_ID_ISC == vendor_id) {
            return getOptionDefs(ISC_V6_OPTION_SPACE);
        }
128
    }
129
    return (null_option_def_container_);
130 131
}

132
OptionDefinitionPtr
133 134
LibDHCP::getOptionDef(const std::string& space, const uint16_t code) {
    const OptionDefContainerPtr& defs = getOptionDefs(space);
135
    const OptionDefContainerTypeIndex& idx = defs->get<1>();
136 137 138 139 140 141 142
    const OptionDefContainerTypeRange& range = idx.equal_range(code);
    if (range.first != range.second) {
        return (*range.first);
    }
    return (OptionDefinitionPtr());
}

143
OptionDefinitionPtr
144 145
LibDHCP::getOptionDef(const std::string& space, const std::string& name) {
    const OptionDefContainerPtr defs = getOptionDefs(space);
146
    const OptionDefContainerNameIndex& idx = defs->get<2>();
147 148 149 150 151 152 153 154 155 156
    const OptionDefContainerNameRange& range = idx.equal_range(name);
    if (range.first != range.second) {
        return (*range.first);
    }
    return (OptionDefinitionPtr());
}

OptionDefinitionPtr
LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
                            const std::string& name) {
157
    const OptionDefContainerPtr option_defs_ptr = getVendorOptionDefs(u, vendor_id);
158

159
    if (!option_defs_ptr) {
160 161 162
        return (OptionDefinitionPtr());
    }

163
    const OptionDefContainerNameIndex& idx = option_defs_ptr->get<2>();
164 165 166 167 168 169 170
    const OptionDefContainerNameRange& range = idx.equal_range(name);
    if (range.first != range.second) {
        return (*range.first);
    }
    return (OptionDefinitionPtr());
}

171 172 173
OptionDefinitionPtr
LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
                            const uint16_t code) {
174
    const OptionDefContainerPtr option_defs_ptr = getVendorOptionDefs(u, vendor_id);
175

176
    if (!option_defs_ptr) {
177 178 179 180 181 182
        // Weird universe or unknown vendor_id. We don't care. No definitions
        // one way or another
        // What is it anyway?
        return (OptionDefinitionPtr());
    }

183
    const OptionDefContainerTypeIndex& idx = option_defs_ptr->get<1>();
184 185 186 187 188 189 190
    const OptionDefContainerTypeRange& range = idx.equal_range(code);
    if (range.first != range.second) {
        return (*range.first);
    }
    return (OptionDefinitionPtr());
}

191 192
OptionDefinitionPtr
LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) {
193
    OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
194 195 196 197 198 199 200 201 202 203 204
    const OptionDefContainerTypeIndex& index = container->get<1>();
    const OptionDefContainerTypeRange& range = index.equal_range(code);
    if (range.first != range.second) {
        return (*range.first);
    }

    return (OptionDefinitionPtr());
}

OptionDefinitionPtr
LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) {
205
    OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
206 207 208 209 210 211 212 213 214 215 216
    const OptionDefContainerNameIndex& index = container->get<2>();
    const OptionDefContainerNameRange& range = index.equal_range(name);
    if (range.first != range.second) {
        return (*range.first);
    }

    return (OptionDefinitionPtr());
}

OptionDefContainerPtr
LibDHCP::getRuntimeOptionDefs(const std::string& space) {
217
    return (runtime_option_defs_.getValue().getItems(space));
218 219
}

220 221
void
LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
222
    OptionDefSpaceContainer defs_copy;
223 224 225 226 227 228 229
    std::list<std::string> option_space_names = defs.getOptionSpaceNames();
    for (std::list<std::string>::const_iterator name = option_space_names.begin();
         name != option_space_names.end(); ++name) {
        OptionDefContainerPtr container = defs.getItems(*name);
        for (OptionDefContainer::const_iterator def = container->begin();
             def != container->end(); ++def) {
            OptionDefinitionPtr def_copy(new OptionDefinition(**def));
230
            defs_copy.addItem(def_copy, *name);
231 232
        }
    }
233
    runtime_option_defs_ = defs_copy;
234 235 236 237
}

void
LibDHCP::clearRuntimeOptionDefs() {
238 239 240 241 242 243 244 245 246 247 248
    runtime_option_defs_.reset();
}

void
LibDHCP::revertRuntimeOptionDefs() {
    runtime_option_defs_.revert();
}

void
LibDHCP::commitRuntimeOptionDefs() {
    runtime_option_defs_.commit();
249 250
}

Francis Dupont's avatar
Francis Dupont committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
OptionDefinitionPtr
LibDHCP::getLastResortOptionDef(const std::string& space, const uint16_t code) {
    OptionDefContainerPtr container = getLastResortOptionDefs(space);
    const OptionDefContainerTypeIndex& index = container->get<1>();
    const OptionDefContainerTypeRange& range = index.equal_range(code);
    if (range.first != range.second) {
        return (*range.first);
    }

    return (OptionDefinitionPtr());
}

OptionDefinitionPtr
LibDHCP::getLastResortOptionDef(const std::string& space, const std::string& name) {
    OptionDefContainerPtr container = getLastResortOptionDefs(space);
    const OptionDefContainerNameIndex& index = container->get<2>();
    const OptionDefContainerNameRange& range = index.equal_range(name);
    if (range.first != range.second) {
        return (*range.first);
    }

    return (OptionDefinitionPtr());
}

OptionDefContainerPtr
LibDHCP::getLastResortOptionDefs(const std::string& space) {
    if (space == DHCP4_OPTION_SPACE) {
278
        return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
Francis Dupont's avatar
Francis Dupont committed
279 280 281 282
    }
    return (null_option_def_container_);
}

283
bool
284
LibDHCP::shouldDeferOptionUnpack(const std::string& space, const uint16_t code) {
285 286 287 288 289
    return ((space == DHCP4_OPTION_SPACE) &&
            ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) ||
             ((code >= 224) && (code <= 254))));
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
                       uint16_t type,
                       const OptionBuffer& buf) {
    FactoryMap::iterator it;
    if (u == Option::V4) {
        it = v4factories_.find(type);
        if (it == v4factories_.end()) {
            isc_throw(BadValue, "factory function not registered "
            "for DHCP v4 option type " << type);
        }
    } else if (u == Option::V6) {
        it = v6factories_.find(type);
        if (it == v6factories_.end()) {
            isc_throw(BadValue, "factory function not registered "
                      "for DHCPv6 option type " << type);
        }
    } else {
        isc_throw(BadValue, "invalid universe specified (expected "
                  "Option::V4 or Option::V6");
    }
311
    return (it->second(u, type, buf));
312 313 314
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
315
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
316
                               const std::string& option_space,
317
                               isc::dhcp::OptionCollection& options,
318 319
                               size_t* relay_msg_offset /* = 0 */,
                               size_t* relay_msg_len /* = 0 */) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
320
    size_t offset = 0;
321
    size_t length = buf.size();
322
    size_t last_offset = 0;
323

324
    // Get the list of standard option definitions.
325
    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
326 327 328
    // Runtime option definitions for non standard option space and if
    // the definition doesn't exist within the standard option definitions.
    const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
329

330 331 332 333
    // @todo Once we implement other option spaces we should add else clause
    // here and gather option definitions for them. For now leaving option_defs
    // empty will imply creation of generic Option.

334
    // Get the search indexes #1. It allows to search for option definitions
335
    // using option code.
336 337
    const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
    const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
338 339 340

    // The buffer being read comprises a set of options, each starting with
    // a two-byte type code and a two-byte length field.
341 342 343 344 345 346 347 348 349 350 351
    while (offset < length) {
        // Save the current offset for backtracking
        last_offset = offset;

        // Check if there is room for another option
        if (offset + 4 > length) {
            // Still something but smaller than an option
            return (last_offset);
        }

        // Parse the option header
352
        uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
353
        offset += 2;
354

355
        uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
356 357
        offset += 2;

358
        if (offset + opt_len > length) {
359 360 361
            // We peeked at the option header of the next option, but
            // discovered that it would end up beyond buffer end, so
            // the option is truncated. Hence we can't parse
362
            // it. Therefore we revert back by those bytes (as if
363 364
            // we never parsed them).
            //
Shawn Routhier's avatar
Shawn Routhier committed
365
            // @note it is the responsibility of the caller to throw
366
            // an exception on partial parsing
367
            return (last_offset);
368
        }
369

370
        if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
371 372
            // remember offset of the beginning of the relay-msg option
            *relay_msg_offset = offset;
373 374 375 376 377
            *relay_msg_len = opt_len;

            // do not create that relay-msg option
            offset += opt_len;
            continue;
378 379
        }

380 381
        if (opt_type == D6O_VENDOR_OPTS) {
            if (offset + 4 > length) {
382 383
                // Truncated vendor-option. We expect at least
                // 4 bytes for the enterprise-id field. Let's roll back
384
                // option code + option length (4 bytes) and return.
385
                return (last_offset);
386 387 388 389 390 391 392 393 394 395 396
            }

            // Parse this as vendor option
            OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
                                                  buf.begin() + offset + opt_len));
            options.insert(std::make_pair(opt_type, vendor_opt));

            offset += opt_len;
            continue;
        }

397 398 399 400 401
        // Get all definitions with the particular option code. Note
        // that option code is non-unique within this container
        // however at this point we expect to get one option
        // definition with the particular code. If more are returned
        // we report an error.
402 403 404
        OptionDefContainerTypeRange range;
        // Number of option definitions returned.
        size_t num_defs = 0;
405 406 407 408

        // We previously did the lookup only for dhcp6 option space, but with the
        // addition of S46 options, we now do it for every space.
        range = idx.equal_range(opt_type);
409
        num_defs = std::distance(range.first, range.second);
410 411

        // Standard option definitions do not include the definition for
412
        // our option or we're searching for non-standard option. Try to
413 414 415
        // find the definition among runtime option definitions.
        if (num_defs == 0) {
            range = runtime_idx.equal_range(opt_type);
416
            num_defs = std::distance(range.first, range.second);
417
        }
418

419
        OptionPtr opt;
420 421
        if (num_defs > 1) {
            // Multiple options of the same code are not supported right now!
422 423 424 425 426 427
            isc_throw(isc::Unexpected, "Internal error: multiple option"
                      " definitions for option type " << opt_type <<
                      " returned. Currently it is not supported to initialize"
                      " multiple option definitions for the same option code."
                      " This will be supported once support for option spaces"
                      " is implemented");
428
        } else if (num_defs == 0) {
429 430 431 432 433
            // @todo Don't crash if definition does not exist because
            // only a few option definitions are initialized right
            // now. In the future we will initialize definitions for
            // all options and we will remove this elseif. For now,
            // return generic option.
434
            opt = OptionPtr(new Option(Option::V6, opt_type,
435 436
                                       buf.begin() + offset,
                                       buf.begin() + offset + opt_len));
437
        } else {
438 439 440 441 442 443 444 445
            try {
                // The option definition has been found. Use it to create
                // the option instance from the provided buffer chunk.
                const OptionDefinitionPtr& def = *(range.first);
                assert(def);
                opt = def->optionFactory(Option::V6, opt_type,
                                         buf.begin() + offset,
                                         buf.begin() + offset + opt_len);
446
            } catch (const SkipThisOptionError&)  {
447 448
                opt.reset();
            }
449
        }
450

451
        // add option to options
452 453 454 455
        if (opt) {
            options.insert(std::make_pair(opt_type, opt));
        }

456 457 458
        offset += opt_len;
    }

459 460
    last_offset = offset;
    return (last_offset);
461 462
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
463
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
464
                               const std::string& option_space,
465 466
                               isc::dhcp::OptionCollection& options,
                               std::list<uint16_t>& deferred) {
467
    size_t offset = 0;
468
    size_t last_offset = 0;
469

470 471 472
    // Special case when option_space is dhcp4.
    bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);

473
    // Get the list of standard option definitions.
474
    const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
475 476 477
    // Runtime option definitions for non standard option space and if
    // the definition doesn't exist within the standard option definitions.
    const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
478

479
    // Get the search indexes #1. It allows to search for option definitions
480
    // using option code.
481 482
    const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
    const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
483 484 485

    // The buffer being read comprises a set of options, each starting with
    // a one-byte type code and a one-byte length field.
486 487 488 489 490
    while (offset < buf.size()) {
        // Save the current offset for backtracking
        last_offset = offset;

        // Get the option type
491
        uint8_t opt_type = buf[offset++];
492

Tomek Mrugalski's avatar
Tomek Mrugalski committed
493
        // DHO_END is a special, one octet long option
494
        if (space_is_dhcp4 && (opt_type == DHO_END)) {
495 496 497 498 499
            // just return. Don't need to add DHO_END option
            // Don't return offset because it makes this condition
            // and partial parsing impossible to recognize.
            return (last_offset);
        }
500

Tomek Mrugalski's avatar
Tomek Mrugalski committed
501 502
        // DHO_PAD is just a padding after DHO_END. Let's continue parsing
        // in case we receive a message without DHO_END.
503
        if (space_is_dhcp4 && (opt_type == DHO_PAD)) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
504
            continue;
505
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
506

507
        if (offset + 1 > buf.size()) {
508 509 510
            // We peeked at the option header of the next option, but
            // discovered that it would end up beyond buffer end, so
            // the option is truncated. Hence we can't parse
511
            // it. Therefore we revert back (as if we never parsed it).
512
            //
Shawn Routhier's avatar
Shawn Routhier committed
513
            // @note it is the responsibility of the caller to throw
514
            // an exception on partial parsing
515
            return (last_offset);
516 517
        }

518
        uint8_t opt_len =  buf[offset++];
519
        if (offset + opt_len > buf.size()) {
520 521 522
            // We peeked at the option header of the next option, but
            // discovered that it would end up beyond buffer end, so
            // the option is truncated. Hence we can't parse
523 524
            // it. Therefore we revert back (as if we never parsed it).
            return (last_offset);
525 526
        }

527 528 529 530 531
        // While an empty Host Name option is non-RFC compliant, some clients
        // do send it.  In the spirit of being liberal, we'll just drop it,
        // rather than the dropping the whole packet.  We do not have a
        // way to log this from here but meh...  a PCAP will show it arriving,
        // and we know we drop it.
532
        if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
533 534 535
            continue;
        }

536 537 538 539 540
        // Get all definitions with the particular option code. Note
        // that option code is non-unique within this container
        // however at this point we expect to get one option
        // definition with the particular code. If more are returned
        // we report an error.
541 542 543
        OptionDefContainerTypeRange range;
        // Number of option definitions returned.
        size_t num_defs = 0;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
544 545 546 547 548

        // Previously we did the lookup only for "dhcp4" option space, but there
        // may be standard options in other spaces (e.g. radius). So we now do
        // the lookup for every space.
        range = idx.equal_range(opt_type);
549
        num_defs = std::distance(range.first, range.second);
550 551

        // Standard option definitions do not include the definition for
552
        // our option or we're searching for non-standard option. Try to
553 554 555
        // find the definition among runtime option definitions.
        if (num_defs == 0) {
            range = runtime_idx.equal_range(opt_type);
556
            num_defs = std::distance(range.first, range.second);
557
        }
558

559
        // Check if option unpacking must be deferred
560
        if (shouldDeferOptionUnpack(option_space, opt_type)) {
561
            num_defs = 0;
562
            deferred.push_back(opt_type);
563 564
        }

565
        OptionPtr opt;
566 567
        if (num_defs > 1) {
            // Multiple options of the same code are not supported right now!
568 569 570 571 572 573 574
            isc_throw(isc::Unexpected, "Internal error: multiple option"
                      " definitions for option type " <<
                      static_cast<int>(opt_type) <<
                      " returned. Currently it is not supported to initialize"
                      " multiple option definitions for the same option code."
                      " This will be supported once support for option spaces"
                      " is implemented");
575
        } else if (num_defs == 0) {
576
            opt = OptionPtr(new Option(Option::V4, opt_type,
577 578
                                       buf.begin() + offset,
                                       buf.begin() + offset + opt_len));
579
            opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
580
        } else {
581 582 583 584 585 586 587 588
            try {
                // The option definition has been found. Use it to create
                // the option instance from the provided buffer chunk.
                const OptionDefinitionPtr& def = *(range.first);
                assert(def);
                opt = def->optionFactory(Option::V4, opt_type,
                                         buf.begin() + offset,
                                         buf.begin() + offset + opt_len);
589
            } catch (const SkipThisOptionError&)  {
590 591 592 593 594 595 596
                opt.reset();
            }
        }

        // If we have the option, insert it
        if (opt) {
            options.insert(std::make_pair(opt_type, opt));
597 598 599 600
        }

        offset += opt_len;
    }
601 602
    last_offset = offset;
    return (last_offset);
603 604
}

605
size_t LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
606 607 608 609 610 611
                                     const OptionBuffer& buf,
                                     isc::dhcp::OptionCollection& options) {
    size_t offset = 0;
    size_t length = buf.size();

    // Get the list of option definitions for this particular vendor-id
612 613
    const OptionDefContainerPtr option_defs_ptr =
        LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
614 615 616 617 618

    // Get the search index #1. It allows to search for option definitions
    // using option code. If there's no such vendor-id space, we're out of luck
    // anyway.
    const OptionDefContainerTypeIndex* idx = NULL;
619 620
    if (option_defs_ptr) {
        idx = &(option_defs_ptr->get<1>());
621 622 623 624
    }

    // The buffer being read comprises a set of options, each starting with
    // a two-byte type code and a two-byte length field.
625 626
    while (offset < length) {
        if (offset + 4 > length) {
627
            isc_throw(SkipRemainingOptionsError,
628
                      "Vendor option parse failed: truncated header");
629 630
        }

631
        uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
632 633
        offset += 2;

634
        uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
635 636 637
        offset += 2;

        if (offset + opt_len > length) {
638 639 640 641
            isc_throw(SkipRemainingOptionsError,
                      "Vendor option parse failed. Tried to parse "
                      << offset + opt_len << " bytes from " << length
                      << "-byte long buffer.");
642 643 644 645 646 647 648
        }

        OptionPtr opt;
        opt.reset();

        // If there is a definition for such a vendor option...
        if (idx) {
649 650 651 652 653 654 655 656 657
            // Get all definitions with the particular option
            // code. Note that option code is non-unique within this
            // container however at this point we expect to get one
            // option definition with the particular code. If more are
            // returned we report an error.
            const OptionDefContainerTypeRange& range =
                idx->equal_range(opt_type);
            // Get the number of returned option definitions for the
            // option code.
658
            size_t num_defs = std::distance(range.first, range.second);
659 660

            if (num_defs > 1) {
661 662 663 664 665 666 667
                // Multiple options of the same code are not supported
                // right now!
                isc_throw(isc::Unexpected, "Internal error: multiple option"
                          " definitions for option type " << opt_type <<
                          " returned. Currently it is not supported to"
                          " initialize multiple option definitions for the"
                          " same option code. This will be supported once"
668 669 670 671 672 673 674 675 676 677 678 679 680 681
                          " support for option spaces is implemented");
            } else if (num_defs == 1) {
                // The option definition has been found. Use it to create
                // the option instance from the provided buffer chunk.
                const OptionDefinitionPtr& def = *(range.first);
                assert(def);
                opt = def->optionFactory(Option::V6, opt_type,
                                         buf.begin() + offset,
                                         buf.begin() + offset + opt_len);
            }
        }

        // This can happen in one of 2 cases:
        // 1. we do not have definitions for that vendor-space
682 683 684
        // 2. we do have definitions, but that particular option was
        //    not defined

685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
        if (!opt) {
            opt = OptionPtr(new Option(Option::V6, opt_type,
                                       buf.begin() + offset,
                                       buf.begin() + offset + opt_len));
        }

        // add option to options
        if (opt) {
            options.insert(std::make_pair(opt_type, opt));
        }
        offset += opt_len;
    }

    return (offset);
}

701
size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
702 703 704
                                     isc::dhcp::OptionCollection& options) {
    size_t offset = 0;

Josh Soref's avatar
Josh Soref committed
705
    // Get the list of standard option definitions.
706 707
    const OptionDefContainerPtr option_defs_ptr =
        LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
708 709 710
    // Get the search index #1. It allows to search for option definitions
    // using option code.
    const OptionDefContainerTypeIndex* idx = NULL;
711 712
    if (option_defs_ptr) {
        idx = &(option_defs_ptr->get<1>());
713 714 715 716
    }

    // The buffer being read comprises a set of options, each starting with
    // a one-byte type code and a one-byte length field.
717
    while (offset < buf.size()) {
718 719
        // Note that Vendor-Specific info option (RFC3925) has a
        // different option format than Vendor-Spec info for
720
        // DHCPv6. (there's additional layer of data-length)
721 722 723
        uint8_t data_len = buf[offset++];

        if (offset + data_len > buf.size()) {
724
            // The option is truncated.
725 726
            isc_throw(SkipRemainingOptionsError,
                      "Attempt to parse truncated vendor option");
727 728 729 730 731
        }

        uint8_t offset_end = offset + data_len;

        // beginning of data-chunk parser
732
        while (offset < offset_end) {
733 734
            uint8_t opt_type = buf[offset++];

735
            // No DHO_END or DHO_PAD in vendor options
736

737
            if (offset + 1 > offset_end) {
738 739 740
                // opt_type must be cast to integer so as it is not
                // treated as unsigned char value (a number is
                // presented in error message).
741
                isc_throw(SkipRemainingOptionsError,
742
                          "Attempt to parse truncated vendor option "
743 744 745 746
                          << static_cast<int>(opt_type));
            }

            uint8_t opt_len =  buf[offset++];
747
            if (offset + opt_len > offset_end) {
748 749
                isc_throw(SkipRemainingOptionsError,
                          "Option parse failed. Tried to parse "
750 751 752 753 754 755 756 757
                          << offset + opt_len << " bytes from " << buf.size()
                          << "-byte long buffer.");
            }

            OptionPtr opt;
            opt.reset();

            if (idx) {
758 759 760 761 762 763 764 765 766
                // Get all definitions with the particular option
                // code. Note that option code is non-unique within
                // this container however at this point we expect to
                // get one option definition with the particular
                // code. If more are returned we report an error.
                const OptionDefContainerTypeRange& range =
                    idx->equal_range(opt_type);
                // Get the number of returned option definitions for
                // the option code.
767
                size_t num_defs = std::distance(range.first, range.second);
768 769

                if (num_defs > 1) {
770 771 772 773 774 775 776 777 778
                    // Multiple options of the same code are not
                    // supported right now!
                    isc_throw(isc::Unexpected, "Internal error: multiple"
                              " option definitions for option type "
                              << opt_type << " returned. Currently it is"
                              " not supported to initialize multiple option"
                              " definitions for the same option code."
                              " This will be supported once support for"
                              " option spaces is implemented");
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
                } else if (num_defs == 1) {
                    // The option definition has been found. Use it to create
                    // the option instance from the provided buffer chunk.
                    const OptionDefinitionPtr& def = *(range.first);
                    assert(def);
                    opt = def->optionFactory(Option::V4, opt_type,
                                             buf.begin() + offset,
                                             buf.begin() + offset + opt_len);
                }
            }

            if (!opt) {
                opt = OptionPtr(new Option(Option::V4, opt_type,
                                           buf.begin() + offset,
                                           buf.begin() + offset + opt_len));
            }

            options.insert(std::make_pair(opt_type, opt));
            offset += opt_len;

799
        }  // end of data-chunk
800

801
        break; // end of the vendor block.
802 803 804 805
    }
    return (offset);
}

806
void
807
LibDHCP::packOptions4(isc::util::OutputBuffer& buf,
808 809
                     const OptionCollection& options,
                     bool top /* = false */) {
810 811
    OptionPtr agent;
    OptionPtr end;
812 813 814 815 816 817 818 819 820 821 822 823

    // We only look for type when we're the top level
    // call that starts packing for options for a packet.
    // This way we avoid doing type logic in all ensuing
    // recursive calls.
    if (top) {
        auto x = options.find(DHO_DHCP_MESSAGE_TYPE);
        if (x != options.end()) {
            x->second->pack(buf);
        }
    }

824
    for (OptionCollection::const_iterator it = options.begin();
825
         it != options.end(); ++it) {
826

827
        // TYPE is already done, RAI and END options must be last.
828
        switch (it->first) {
829 830
            case DHO_DHCP_MESSAGE_TYPE:
                break;
831 832 833 834 835 836 837 838 839 840
            case DHO_DHCP_AGENT_OPTIONS:
                agent = it->second;
                break;
            case DHO_END:
                end = it->second;
                break;
            default:
                it->second->pack(buf);
                break;
        }
841
    }
842 843 844 845

    // Add the RAI option if it exists.
    if (agent) {
       agent->pack(buf);
846
    }
847

848 849 850
    // And at the end the END option.
    if (end)  {
       end->pack(buf);
851 852
    }
}
853

854
void
855 856
LibDHCP::packOptions6(isc::util::OutputBuffer& buf,
                      const OptionCollection& options) {
857
    for (OptionCollection::const_iterator it = options.begin();
858
         it != options.end(); ++it) {
859
        it->second->pack(buf);
860 861 862
    }
}

863 864
void LibDHCP::OptionFactoryRegister(Option::Universe u,
                                    uint16_t opt_type,
865
                                    Option::Factory* factory) {
866 867
    switch (u) {
    case Option::V6: {
868
        if (v6factories_.find(opt_type) != v6factories_.end()) {
869 870 871
            isc_throw(BadValue, "There is already DHCPv6 factory registered "
                     << "for option type "  << opt_type);
        }
872
        v6factories_[opt_type] = factory;
873
        return;
874
    }
875
    case Option::V4: {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
876 877
        // Option 0 is special (a one octet-long, equal 0) PAD option. It is never
        // instantiated as an Option object, but rather consumed during packet parsing.
878 879 880
        if (opt_type == 0) {
            isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
881
        // Option 255 is never instantiated as an option object. It is special
882 883
        // (a one-octet equal 255) option that is added at the end of all options
        // during packet assembly. It is also silently consumed during packet parsing.
884 885 886
        if (opt_type > 254) {
            isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
        }
887
        if (v4factories_.find(opt_type) != v4factories_.end()) {
888 889 890
            isc_throw(BadValue, "There is already DHCPv4 factory registered "
                     << "for option type "  << opt_type);
        }
891
        v4factories_[opt_type] = factory;
892
        return;
893
    }
894 895
    default:
        isc_throw(BadValue, "Invalid universe type specified.");
896 897
    }

898
    return;
899
}
900

901 902 903 904 905 906 907 908 909 910 911 912
void LibDHCP::initOptionDefs(const std::string &space) {
    if (option_defs_.end() == option_defs_.find(space)) {
        option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
        for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) {
            if (space == OPTION_DEF_PARAMS[i].space) {
                initOptionSpace(option_defs_[space],
                                OPTION_DEF_PARAMS[i].optionDefParams,
                                OPTION_DEF_PARAMS[i].size);
                break;
            }
        }
    }
913 914
}

915 916
uint32_t
LibDHCP::optionSpaceToVendorId(const std::string& option_space) {
917 918
    // 8 is a minimal length of "vendor-X" format
    if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
919 920 921 922 923
        return (0);
    }

    int64_t check;
    try {
924 925 926
        // text after "vendor-", supposedly numbers only
        std::string x = option_space.substr(7);

927 928 929 930
        check = boost::lexical_cast<int64_t>(x);
    } catch (const boost::bad_lexical_cast &) {
        return (0);
    }
931 932

    if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {