libdhcp++.cc 17.4 KB
Newer Older
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1
// Copyright (C) 2011-2013 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 <dhcp/dhcp4.h>
18
#include <dhcp/dhcp6.h>
19
#include <dhcp/libdhcp++.h>
20 21 22
#include <dhcp/option.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
23
#include <dhcp/option_definition.h>
24
#include <dhcp/option_int_array.h>
25
#include <dhcp/std_option_defs.h>
26 27 28 29 30
#include <exceptions/exceptions.h>
#include <util/buffer.h>

#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
31

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

36 37 38
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;

39
// static array with factories for options
40 41
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;

42 43 44 45 46 47 48
// Static container with DHCPv4 option definitions.
OptionDefContainer LibDHCP::v4option_defs_;

// Static container with DHCPv6 option definitions.
OptionDefContainer LibDHCP::v6option_defs_;

const OptionDefContainer&
49
LibDHCP::getOptionDefs(const Option::Universe u) {
50 51
    switch (u) {
    case Option::V4:
52 53 54
        if (v4option_defs_.empty()) {
            initStdOptionDefs4();
        }
55 56
        return (v4option_defs_);
    case Option::V6:
57
        if (v6option_defs_.empty()) {
58
            initStdOptionDefs6();
59
        }
60 61 62 63 64
        return (v6option_defs_);
    default:
        isc_throw(isc::BadValue, "invalid universe " << u << " specified");
    }
}
65

66 67 68 69 70 71 72 73 74 75 76
OptionDefinitionPtr
LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
    const OptionDefContainer& defs = getOptionDefs(u);
    const OptionDefContainerTypeIndex& idx = defs.get<1>();
    const OptionDefContainerTypeRange& range = idx.equal_range(code);
    if (range.first != range.second) {
        return (*range.first);
    }
    return (OptionDefinitionPtr());
}

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
bool
LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
    if (u == Option::V6) {
        if (code < 79 &&
            code != 10 &&
            code != 35) {
            return (true);
        }

    } else if (u == Option::V4) {
        if (!(code == 84 ||
              code == 96 ||
              (code > 101 && code < 112) ||
              code == 115 ||
              code == 126 ||
              code == 127 ||
              (code > 146 && code < 150) ||
              (code > 177  && code < 208) ||
              (code > 213 && code <  220) ||
              (code > 221 && code < 224))) {
                return (true);
            }

    }

    return (false);
}

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
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");
    }
126
    return (it->second(u, type, buf));
127 128 129
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
130
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
131
                               isc::dhcp::Option::OptionCollection& options,
132 133
                               size_t* relay_msg_offset /* = 0 */,
                               size_t* relay_msg_len /* = 0 */) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
134
    size_t offset = 0;
135 136 137 138 139 140 141 142 143 144 145 146
    size_t length = buf.size();

    // Get the list of stdandard option definitions.
    const OptionDefContainer& option_defs = LibDHCP::getOptionDefs(Option::V6);
    // Get the search index #1. It allows to search for option definitions
    // using option code.
    const OptionDefContainerTypeIndex& idx = option_defs.get<1>();

    // The buffer being read comprises a set of options, each starting with
    // a two-byte type code and a two-byte length field.
    while (offset + 4 <= length) {
        uint16_t opt_type = isc::util::readUint16(&buf[offset]);
147
        offset += 2;
148

149
        uint16_t opt_len = isc::util::readUint16(&buf[offset]);
150 151
        offset += 2;

152
        if (offset + opt_len > length) {
153
            // @todo: consider throwing exception here.
154 155
            return (offset);
        }
156

157
        if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
158 159
            // remember offset of the beginning of the relay-msg option
            *relay_msg_offset = offset;
160 161 162 163 164
            *relay_msg_len = opt_len;

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

167 168 169 170
        // 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.
171 172 173
        const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
        // Get the number of returned option definitions for the option code.
        size_t num_defs = distance(range.first, range.second);
174

175
        OptionPtr opt;
176 177 178 179 180 181 182 183
        if (num_defs > 1) {
            // 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");
        } else if (num_defs == 0) {
184
            // @todo Don't crash if definition does not exist because only a few
185 186 187 188
            // 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.
            opt = OptionPtr(new Option(Option::V6, opt_type,
189 190
                                       buf.begin() + offset,
                                       buf.begin() + offset + opt_len));
191 192 193 194 195 196 197 198
        } else {
            // 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);
199
        }
200
        // add option to options
201
        options.insert(std::make_pair(opt_type, opt));
202 203 204 205 206 207
        offset += opt_len;
    }

    return (offset);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
208
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
209
                               isc::dhcp::Option::OptionCollection& options) {
210 211
    size_t offset = 0;

212 213 214 215 216 217 218 219
    // Get the list of stdandard option definitions.
    const OptionDefContainer& option_defs = LibDHCP::getOptionDefs(Option::V4);
    // Get the search index #1. It allows to search for option definitions
    // using option code.
    const OptionDefContainerTypeIndex& idx = option_defs.get<1>();

    // The buffer being read comprises a set of options, each starting with
    // a one-byte type code and a one-byte length field.
220
    while (offset + 1 <= buf.size()) {
221
        uint8_t opt_type = buf[offset++];
222

Tomek Mrugalski's avatar
Tomek Mrugalski committed
223
        // DHO_END is a special, one octet long option
224
        if (opt_type == DHO_END)
225
            return (offset); // just return. Don't need to add DHO_END option
226

Tomek Mrugalski's avatar
Tomek Mrugalski committed
227 228 229 230 231
        // DHO_PAD is just a padding after DHO_END. Let's continue parsing
        // in case we receive a message without DHO_END.
        if (opt_type == DHO_PAD)
            continue;

232
        if (offset + 1 >= buf.size()) {
233 234
            // opt_type must be cast to integer so as it is not treated as
            // unsigned char value (a number is presented in error message).
235
            isc_throw(OutOfRange, "Attempt to parse truncated option "
236
                      << static_cast<int>(opt_type));
237 238
        }

239
        uint8_t opt_len =  buf[offset++];
240
        if (offset + opt_len > buf.size()) {
241 242 243 244 245
            isc_throw(OutOfRange, "Option parse failed. Tried to parse "
                      << offset + opt_len << " bytes from " << buf.size()
                      << "-byte long buffer.");
        }

246
        // Get all definitions with the particular option code. Note that option code
247 248 249 250 251 252 253
        // 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.
        size_t num_defs = distance(range.first, range.second);

254
        OptionPtr opt;
255 256 257 258 259 260 261 262 263
        if (num_defs > 1) {
            // Multiple options of the same code are not supported right now!
            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");
        } else if (num_defs == 0) {
264
            opt = OptionPtr(new Option(Option::V4, opt_type,
265 266 267 268 269 270 271 272 273 274
                                       buf.begin() + offset,
                                       buf.begin() + offset + opt_len));
        } else {
            // 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);
275 276
        }

277
        options.insert(std::make_pair(opt_type, opt));
278 279
        offset += opt_len;
    }
280
    return (offset);
281 282
}

283 284
void
LibDHCP::packOptions(isc::util::OutputBuffer& buf,
285 286
                     const Option::OptionCollection& options) {
    for (Option::OptionCollection::const_iterator it = options.begin();
287
         it != options.end(); ++it) {
288
        it->second->pack(buf);
289 290 291
    }
}

292 293
void LibDHCP::OptionFactoryRegister(Option::Universe u,
                                    uint16_t opt_type,
294
                                    Option::Factory* factory) {
295 296
    switch (u) {
    case Option::V6: {
297
        if (v6factories_.find(opt_type) != v6factories_.end()) {
298 299 300 301
            isc_throw(BadValue, "There is already DHCPv6 factory registered "
                     << "for option type "  << opt_type);
        }
        v6factories_[opt_type]=factory;
302
        return;
303 304
    }
    case Option::V4:
305
    {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
306 307
        // 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.
308 309 310
        if (opt_type == 0) {
            isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
        }
Tomek Mrugalski's avatar
Tomek Mrugalski committed
311
        // Option 255 is never instantiated as an option object. It is special
312 313
        // (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.
314 315 316 317 318 319 320 321 322
        if (opt_type > 254) {
            isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
        }
        if (v4factories_.find(opt_type)!=v4factories_.end()) {
            isc_throw(BadValue, "There is already DHCPv4 factory registered "
                     << "for option type "  << opt_type);
        }
        v4factories_[opt_type]=factory;
        return;
323
    }
324 325
    default:
        isc_throw(BadValue, "Invalid universe type specified.");
326 327
    }

328
    return;
329
}
330

331 332
void
LibDHCP::initStdOptionDefs4() {
333 334 335 336
    v4option_defs_.clear();

    // Now let's add all option definitions.
    for (int i = 0; i < OPTION_DEF_PARAMS_SIZE4; ++i) {
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
        std::string encapsulates(OPTION_DEF_PARAMS4[i].encapsulates);
        if (!encapsulates.empty() && OPTION_DEF_PARAMS4[i].array) {
            isc_throw(isc::BadValue, "invalid standard option definition: "
                      << "option with code '" << OPTION_DEF_PARAMS4[i].code
                      << "' may not encapsulate option space '"
                      << encapsulates << "' because the definition"
                      << " indicates that this option comprises an array"
                      << " of values");
        }

        // Depending whether the option encapsulates an option space or not
        // we pick different constructor to create an instance of the option
        // definition.
        OptionDefinitionPtr definition;
        if (encapsulates.empty()) {
            // Option does not encapsulate any option space.
            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
                                                  OPTION_DEF_PARAMS4[i].code,
                                                  OPTION_DEF_PARAMS4[i].type,
                                                  OPTION_DEF_PARAMS4[i].array));

        } else {
            // Option does encapsulate an option space.
            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
                                                  OPTION_DEF_PARAMS4[i].code,
                                                  OPTION_DEF_PARAMS4[i].type,
                                                  OPTION_DEF_PARAMS4[i].encapsulates));

        }
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

        for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
            definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
        }

        // Sanity check if the option is valid.
        try {
            definition->validate();
        } catch (const Exception& ex) {
            // This is unlikely event that validation fails and may
            // be only caused by programming error. To guarantee the
            // data consistency we clear all option definitions that
            // have been added so far and pass the exception forward.
            v4option_defs_.clear();
            throw;
        }
        v4option_defs_.push_back(definition);
    }
384 385 386 387 388
}

void
LibDHCP::initStdOptionDefs6() {
    v6option_defs_.clear();
389

390
    for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
        std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
        if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
            isc_throw(isc::BadValue, "invalid standard option definition: "
                      << "option with code '" << OPTION_DEF_PARAMS6[i].code
                      << "' may not encapsulate option space '"
                      << encapsulates << "' because the definition"
                      << " indicates that this option comprises an array"
                      << " of values");
        }

        // Depending whether an option encapsulates an option space or not
        // we pick different constructor to create an instance of the option
        // definition.
        OptionDefinitionPtr definition;
        if (encapsulates.empty()) {
            // Option does not encapsulate any option space.
            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
                                                  OPTION_DEF_PARAMS6[i].code,
                                                  OPTION_DEF_PARAMS6[i].type,
                                                  OPTION_DEF_PARAMS6[i].array));
        } else {
            // Option does encapsulate an option space.
            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
                                                  OPTION_DEF_PARAMS6[i].code,
                                                  OPTION_DEF_PARAMS6[i].type,
                                                  OPTION_DEF_PARAMS6[i].encapsulates));

        }
419 420 421

        for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
            definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
422
        }
423

424 425 426
        try {
            definition->validate();
        } catch (const Exception& ex) {
427 428 429 430
            // This is unlikely event that validation fails and may
            // be only caused by programming error. To guarantee the
            // data consistency we clear all option definitions that
            // have been added so far and pass the exception forward.
431
            v6option_defs_.clear();
432
            throw;
433 434
        }
        v6option_defs_.push_back(definition);
435 436
    }
}