config_parser_unittest.cc 29.9 KB
Newer Older
1
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14 15
//
// 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.

#include <config.h>
16 17 18 19 20 21 22 23

#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
24

25
#include <boost/foreach.hpp>
26
#include <gtest/gtest.h>
27

28 29 30 31
#include <fstream>
#include <iostream>
#include <sstream>

32 33 34 35 36 37 38 39 40 41 42 43 44
#include <arpa/inet.h>

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::config;

namespace {

class Dhcp6ParserTest : public ::testing::Test {
public:
Stephen Morris's avatar
Stephen Morris committed
45 46
    Dhcp6ParserTest() :rcode_(-1), srv_(0) {
        // srv_(0) means to not open any sockets. We don't want to
47 48
        // deal with sockets here, just check if configuration handling
        // is sane.
49 50 51
    }

    ~Dhcp6ParserTest() {
52 53
        // Reset configuration database after each test.
        resetConfiguration();
54 55
    };

56 57 58
    /// @brief Create the simple configuration with single option.
    ///
    /// This function allows to set one of the parameters that configure
59 60
    /// option value. These parameters are: "name", "code", "data" and
    /// "csv-format".
61 62 63 64 65 66 67
    ///
    /// @param param_value string holiding option parameter value to be
    /// injected into the configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param value.
    std::string createConfigWithOption(const std::string& param_value,
                                       const std::string& parameter) {
68 69 70 71 72
        std::map<std::string, std::string> params;
        if (parameter == "name") {
            params["name"] = param_value;
            params["code"] = "80";
            params["data"] = "AB CDEF0105";
73
            params["csv-format"] = "False";
74 75 76 77
        } else if (parameter == "code") {
            params["name"] = "option_foo";
            params["code"] = param_value;
            params["data"] = "AB CDEF0105";
78
            params["csv-format"] = "False";
79 80 81 82
        } else if (parameter == "data") {
            params["name"] = "option_foo";
            params["code"] = "80";
            params["data"] = param_value;
83 84 85 86 87 88
            params["csv-format"] = "False";
        } else if (parameter == "csv-format") {
            params["name"] = "option_foo";
            params["code"] = "80";
            params["data"] = "AB CDEF0105";
            params["csv-format"] = param_value;
89 90 91 92
        }
        return (createConfigWithOption(params));
    }

Mukund Sivaraman's avatar
Mukund Sivaraman committed
93 94 95
    std::string createConfigWithOption(const std::map<std::string,
                                       std::string>& params)
    {
96 97 98 99 100 101 102 103 104
        std::ostringstream stream;
        stream << "{ \"interface\": [ \"all\" ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"subnet6\": [ { "
            "    \"pool\": [ \"2001:db8:1::/80\" ],"
            "    \"subnet\": \"2001:db8:1::/64\", "
            "    \"option-data\": [ {";
105 106 107 108 109 110
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
111
                // cppcheck-suppress unreadVariable
112 113 114 115 116
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
            } else if (param.first == "code") {
117
                stream << "\"code\": " << param.second;;
118 119
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
120 121
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
122
            }
123 124 125 126 127 128 129 130
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    /// @brief Reset configuration database.
    ///
    /// This function resets configuration data base by
    /// removing all subnets and option-data. Reset must
    /// be performed after each test to make sure that
    /// contents of the database do not affect result of
    /// subsequent tests.
    void resetConfiguration() {
        ConstElementPtr status;

        string config = "{ \"interface\": [ \"all\" ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"valid-lifetime\": 4000, "
            "\"subnet6\": [ ], "
            "\"option-data\": [ ] }";

        try {
            ElementPtr json = Element::fromJSON(config);
151
            status = configureDhcp6Server(srv_, json);
152 153 154 155 156 157 158 159 160
        } catch (const std::exception& ex) {
            FAIL() << "Fatal error: unable to reset configuration database"
                   << " after the test. The following configuration was used"
                   << " to reset database: " << std::endl
                   << config << std::endl
                   << " and the following error message was returned:"
                   << ex.what() << std::endl;
        }

161
        // status object must not be NULL
162 163 164 165 166 167
        if (!status) {
            FAIL() << "Fatal error: unable to reset configuration database"
                   << " after the test. Configuration function returned"
                   << " NULL pointer" << std::endl;
        }
        comment_ = parseAnswer(rcode_, status);
168
        // returned value should be 0 (configuration success)
169 170 171 172 173 174 175
        if (rcode_ != 0) {
            FAIL() << "Fatal error: unable to reset configuration database"
                   << " after the test. Configuration function returned"
                   << " error code " << rcode_ << std::endl;
        }
    }

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    /// @brief Test invalid option parameter value.
    ///
    /// This test function constructs the simple configuration
    /// string and injects invalid option configuration into it.
    /// It expects that parser will fail with provided option code.
    ///
    /// @param param_value string holding invalid option parameter value
    /// to be injected into configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param_value (can be any of "name", "code", "data")
    void testInvalidOptionParam(const std::string& param_value,
                                const std::string& parameter) {
        ConstElementPtr x;
        std::string config = createConfigWithOption(param_value, parameter);
        ElementPtr json = Element::fromJSON(config);
Stephen Morris's avatar
Stephen Morris committed
191
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
192 193 194 195 196
        ASSERT_TRUE(x);
        comment_ = parseAnswer(rcode_, x);
        ASSERT_EQ(1, rcode_);
    }

197 198 199 200 201 202 203
    /// @brief Test option against given code and data.
    ///
    /// @param option_desc option descriptor that carries the option to
    /// be tested.
    /// @param expected_code expected code of the option.
    /// @param expected_data expected data in the option.
    /// @param expected_data_len length of the reference data.
204 205
    /// @param extra_data if true extra data is allowed in an option
    /// after tested data.
206 207
    void testOption(const Subnet::OptionDescriptor& option_desc,
                    uint16_t expected_code, const uint8_t* expected_data,
208 209
                    size_t expected_data_len,
                    bool extra_data = false) {
210 211 212 213 214 215 216 217 218 219 220
        // Check if option descriptor contains valid option pointer.
        ASSERT_TRUE(option_desc.option);
        // Verify option type.
        EXPECT_EQ(expected_code, option_desc.option->getType());
        // We may have many different option types being created. Some of them
        // have dedicated classes derived from Option class. In such case if
        // we want to verify the option contents against expected_data we have
        // to prepare raw buffer with the contents of the option. The easiest
        // way is to call pack() which will prepare on-wire data.
        util::OutputBuffer buf(option_desc.option->getData().size());
        option_desc.option->pack(buf);
221 222 223 224 225 226 227 228 229 230 231
        if (extra_data) {
            // The length of the buffer must be at least equal to size of the
            // reference data but it can sometimes be greater than that. This is
            // because some options carry suboptions that increase the overall
            // length.
            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
                      expected_data_len);
        } else {
            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
                      expected_data_len);
        }
232
        // Verify that the data is correct. Do not verify suboptions and a header.
233
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
234 235
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
                            expected_data_len));
236 237
    }

Stephen Morris's avatar
Stephen Morris committed
238
    Dhcpv6Srv srv_;
239 240 241 242 243

    int rcode_;
    ConstElementPtr comment_;
};

244 245 246
// Goal of this test is a verification if a very simple config update
// with just a bumped version number. That's the simplest possible
// config update.
247 248 249 250
TEST_F(Dhcp6ParserTest, version) {

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
251
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
252 253 254 255 256 257 258 259
                    Element::fromJSON("{\"version\": 0}")));

    // returned value must be 0 (configuration accepted)
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    EXPECT_EQ(0, rcode_);
}

260 261
/// The goal of this test is to verify that the code accepts only
/// valid commands and malformed or unsupported parameters are rejected.
262
TEST_F(Dhcp6ParserTest, bogusCommand) {
263 264 265

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
266
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
267 268 269 270 271 272 273 274
                    Element::fromJSON("{\"bogus\": 5}")));

    // returned value must be 1 (configuration parse error)
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    EXPECT_EQ(1, rcode_);
}

275 276 277
/// The goal of this test is to verify if wrongly defined subnet will
/// be rejected. Properly defined subnet must include at least one
/// pool definition.
278
TEST_F(Dhcp6ParserTest, emptySubnet) {
279

280
    ConstElementPtr status;
281

Stephen Morris's avatar
Stephen Morris committed
282
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
283 284 285 286 287 288 289
                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
                                      "\"preferred-lifetime\": 3000,"
                                      "\"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"subnet6\": [  ], "
                                      "\"valid-lifetime\": 4000 }")));

290 291 292
    // returned value should be 0 (success)
    ASSERT_TRUE(status);
    comment_ = parseAnswer(rcode_, status);
293 294 295
    EXPECT_EQ(0, rcode_);
}

296 297
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
298
TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
299

300
    ConstElementPtr status;
301 302 303 304 305 306 307 308 309 310 311 312 313

    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
314
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
315

316 317 318
    // check if returned status is OK
    ASSERT_TRUE(status);
    comment_ = parseAnswer(rcode_, status);
319 320
    EXPECT_EQ(0, rcode_);

321 322
    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
323 324 325 326 327 328 329 330
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
}

331 332
// This test checks if it is possible to override global values
// on a per subnet basis.
333
TEST_F(Dhcp6ParserTest, subnetLocal) {
334

335
    ConstElementPtr status;
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352

    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
        "    \"renew-timer\": 1, "
        "    \"rebind-timer\": 2, "
        "    \"preferred-lifetime\": 3,"
        "    \"valid-lifetime\": 4,"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
353
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
354

355 356 357
    // returned value should be 0 (configuration success)
    ASSERT_TRUE(status);
    comment_ = parseAnswer(rcode_, status);
358 359 360 361 362 363 364 365 366 367
    EXPECT_EQ(0, rcode_);

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1, subnet->getT1());
    EXPECT_EQ(2, subnet->getT2());
    EXPECT_EQ(3, subnet->getPreferred());
    EXPECT_EQ(4, subnet->getValid());
}

368 369
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
370
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
371

372
    ConstElementPtr status;
373 374 375 376 377 378 379 380 381 382 383 384 385

    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pool\": [ \"4001:db8:1::/80\" ],"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
386
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
387

388
    // returned value must be 1 (values error)
389
    // as the pool does not belong to that subnet
390 391
    ASSERT_TRUE(status);
    comment_ = parseAnswer(rcode_, status);
392 393

    EXPECT_EQ(1, rcode_);
394 395
}

396 397 398
// Goal of this test is to verify if pools can be defined
// using prefix/length notation. There is no separate test for min-max
// notation as it was tested in several previous tests.
399
TEST_F(Dhcp6ParserTest, poolPrefixLen) {
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414

    ConstElementPtr x;

    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::/80\" ],"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
415
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
416 417 418 419 420 421 422 423 424 425 426 427 428 429

    // returned value must be 1 (configuration parse error)
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    EXPECT_EQ(0, rcode_);

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
}

430 431 432
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
433 434 435 436 437 438 439 440 441
TEST_F(Dhcp6ParserTest, optionDataDefaults) {
    ConstElementPtr x;
    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000,"
        "\"renew-timer\": 1000,"
        "\"option-data\": [ {"
        "    \"name\": \"option_foo\","
        "    \"code\": 100,"
442 443
        "    \"data\": \"AB CDEF0105\","
        "    \"csv-format\": False"
444 445 446 447
        " },"
        " {"
        "    \"name\": \"option_foo2\","
        "    \"code\": 101,"
448 449
        "    \"data\": \"01\","
        "    \"csv-format\": False"
450 451 452 453 454 455 456 457 458
        " } ],"
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::/80\" ],"
        "    \"subnet\": \"2001:db8:1::/64\""
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
459
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
460 461 462 463 464 465
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    ASSERT_EQ(0, rcode_);

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
466 467
    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
    ASSERT_EQ(2, options->size());
468 469

    // Get the search index. Index #1 is to search using option code.
470
    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

    // Get the options for specified index. Expecting one option to be
    // returned but in theory we may have multiple options with the same
    // code so we get the range.
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range =
        idx.equal_range(100);
    // Expect single option with the code equal to 100.
    ASSERT_EQ(1, std::distance(range.first, range.second));
    const uint8_t foo_expected[] = {
        0xAB, 0xCD, 0xEF, 0x01, 0x05
    };
    // Check if option is valid in terms of code and carried data.
    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));

    range = idx.equal_range(101);
    ASSERT_EQ(1, std::distance(range.first, range.second));
    // Do another round of testing with second option.
    const uint8_t foo2_expected[] = {
        0x01
    };
    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
493 494 495

    // Check that options with other option codes are not returned.
    for (uint16_t code = 102; code < 110; ++code) {
496
        range = idx.equal_range(code);
497 498
        EXPECT_EQ(0, std::distance(range.first, range.second));
    }
499 500
}

501 502 503 504
// Goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
505
TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
506 507 508 509 510
    ConstElementPtr x;
    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
511 512 513
        "\"option-data\": [ {"
        "      \"name\": \"option_foo\","
        "      \"code\": 100,"
514 515
        "      \"data\": \"AB\","
        "      \"csv-format\": False"
516
        " } ],"
517 518 519
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::/80\" ],"
        "    \"subnet\": \"2001:db8:1::/64\", "
520
        "    \"option-data\": [ {"
521 522
        "          \"name\": \"option_foo\","
        "          \"code\": 100,"
523 524
        "          \"data\": \"AB CDEF0105\","
        "          \"csv-format\": False"
525 526 527 528
        "        },"
        "        {"
        "          \"name\": \"option_foo2\","
        "          \"code\": 101,"
529 530
        "          \"data\": \"01\","
        "          \"csv-format\": False"
531
        "        } ]"
532 533 534 535 536
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
537
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
538
    ASSERT_TRUE(x);
539
    comment_ = parseAnswer(rcode_, x);
540
    ASSERT_EQ(0, rcode_);
541 542 543

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
544 545
    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
    ASSERT_EQ(2, options->size());
546 547

    // Get the search index. Index #1 is to search using option code.
548
    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570

    // Get the options for specified index. Expecting one option to be
    // returned but in theory we may have multiple options with the same
    // code so we get the range.
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range =
        idx.equal_range(100);
    // Expect single option with the code equal to 100.
    ASSERT_EQ(1, std::distance(range.first, range.second));
    const uint8_t foo_expected[] = {
        0xAB, 0xCD, 0xEF, 0x01, 0x05
    };
    // Check if option is valid in terms of code and carried data.
    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));

    range = idx.equal_range(101);
    ASSERT_EQ(1, std::distance(range.first, range.second));
    // Do another round of testing with second option.
    const uint8_t foo2_expected[] = {
        0x01
    };
    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
571 572
}

573 574
// Goal of this test is to verify options configuration
// for multiple subnets.
575
TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
576 577 578 579 580 581 582 583 584 585 586
    ConstElementPtr x;
    string config = "{ \"interface\": [ \"all\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pool\": [ \"2001:db8:1::/80\" ],"
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"option-data\": [ {"
        "          \"name\": \"option_foo\","
        "          \"code\": 100,"
587 588
        "          \"data\": \"0102030405060708090A\","
        "          \"csv-format\": False"
589 590 591 592 593 594 595 596
        "        } ]"
        " },"
        " {"
        "    \"pool\": [ \"2001:db8:2::/80\" ],"
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"option-data\": [ {"
        "          \"name\": \"option_foo2\","
        "          \"code\": 101,"
597 598
        "          \"data\": \"FFFEFDFCFB\","
        "          \"csv-format\": False"
599 600 601 602 603 604
        "        } ]"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
605
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
606 607 608 609 610 611
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    ASSERT_EQ(0, rcode_);

    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet1);
612 613
    Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
    ASSERT_EQ(1, options1->size());
614 615

    // Get the search index. Index #1 is to search using option code.
616
    const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635

    // Get the options for specified index. Expecting one option to be
    // returned but in theory we may have multiple options with the same
    // code so we get the range.
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range1 =
        idx1.equal_range(100);
    // Expect single option with the code equal to 100.
    ASSERT_EQ(1, std::distance(range1.first, range1.second));
    const uint8_t foo_expected[] = {
        0x01, 0x02, 0x03, 0x04, 0x05,
        0x06, 0x07, 0x08, 0x09, 0x0A
    };
    // Check if option is valid in terms of code and carried data.
    testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));

    // Test another subnet in the same way.
    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
    ASSERT_TRUE(subnet2);
636 637
    Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
    ASSERT_EQ(1, options2->size());
638

639
    const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
640 641 642 643 644 645 646 647 648 649 650
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range2 =
        idx2.equal_range(101);
    ASSERT_EQ(1, std::distance(range2.first, range2.second));

    const uint8_t foo2_expected[] = {
        0xFF, 0xFE, 0xFD, 0xFC, 0xFB
    };
    testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
}

651
// Verify that empty option name is rejected in the configuration.
652 653 654 655 656
TEST_F(Dhcp6ParserTest, optionNameEmpty) {
    // Empty option names not allowed.
    testInvalidOptionParam("", "name");
}

657 658
// Verify that empty option name with spaces is rejected
// in the configuration.
659 660 661 662 663
TEST_F(Dhcp6ParserTest, optionNameSpaces) {
    // Spaces in option names not allowed.
    testInvalidOptionParam("option foo", "name");
}

664
// Verify that negative option code is rejected in the configuration.
665 666 667 668 669
TEST_F(Dhcp6ParserTest, optionCodeNegative) {
    // Check negative option code -4. This should fail too.
    testInvalidOptionParam("-4", "code");
}

670
// Verify that out of bounds option code is rejected in the configuration.
671 672 673 674 675 676 677
TEST_F(Dhcp6ParserTest, optionCodeNonUint16) {
    // The valid option codes are uint16_t values so passing
    // uint16_t maximum value incremented by 1 should result
    // in failure.
    testInvalidOptionParam("65536", "code");
}

678
// Verify that out of bounds option code is rejected in the configuration.
679 680 681 682 683 684
TEST_F(Dhcp6ParserTest, optionCodeHighNonUint16) {
    // Another check for uint16_t overflow but this time
    // let's pass even greater option code value.
    testInvalidOptionParam("70000", "code");
}

685
// Verify that zero option code is rejected in the configuration.
686 687 688 689 690 691
TEST_F(Dhcp6ParserTest, optionCodeZero) {
    // Option code 0 is reserved and should not be accepted
    // by configuration parser.
    testInvalidOptionParam("0", "code");
}

692 693
// Verify that option data which contains non hexadecimal characters
// is rejected by the configuration.
694 695 696 697 698 699
TEST_F(Dhcp6ParserTest, optionDataInvalidChar) {
    // Option code 0 is reserved and should not be accepted
    // by configuration parser.
    testInvalidOptionParam("01020R", "data");
}

700 701
// Verify that option data containins '0x' prefix is rejected
// by the configuration.
702 703 704 705 706 707
TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
    // Option code 0 is reserved and should not be accepted
    // by configuration parser.
    testInvalidOptionParam("0x0102", "data");
}

708 709
// Verify that option data consisting od an odd number of
// hexadecimal digits is rejected in the configuration.
710 711 712 713 714 715
TEST_F(Dhcp6ParserTest, optionDataOddLength) {
    // Option code 0 is reserved and should not be accepted
    // by configuration parser.
    testInvalidOptionParam("123", "data");
}

716 717
// Verify that either lower or upper case characters are allowed
// to specify the option data.
718 719 720 721 722
TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
    ConstElementPtr x;
    std::string config = createConfigWithOption("0a0b0C0D", "data");
    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
723
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
724 725 726 727 728 729
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    ASSERT_EQ(0, rcode_);

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
730 731
    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
    ASSERT_EQ(1, options->size());
732 733

    // Get the search index. Index #1 is to search using option code.
734
    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749

    // Get the options for specified index. Expecting one option to be
    // returned but in theory we may have multiple options with the same
    // code so we get the range.
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range =
        idx.equal_range(80);
    // Expect single option with the code equal to 100.
    ASSERT_EQ(1, std::distance(range.first, range.second));
    const uint8_t foo_expected[] = {
        0x0A, 0x0B, 0x0C, 0x0D
    };
    // Check if option is valid in terms of code and carried data.
    testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
}
750

751 752 753 754 755 756 757 758
// Verify that specific option object is returned for standard
// option which has dedicated option class derived from Option.
TEST_F(Dhcp6ParserTest, stdOptionData) {
    ConstElementPtr x;
    std::map<std::string, std::string> params;
    params["name"] = "OPTION_IA_NA";
    // Option code 3 means OPTION_IA_NA.
    params["code"] = "3";
759 760
    params["data"] = "12345, 6789, 1516";
    params["csv-format"] = "True";
761

762 763 764
    std::string config = createConfigWithOption(params);
    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
765
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
766 767 768 769 770 771
    ASSERT_TRUE(x);
    comment_ = parseAnswer(rcode_, x);
    ASSERT_EQ(0, rcode_);

    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
    ASSERT_TRUE(subnet);
772 773
    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
    ASSERT_EQ(1, options->size());
774 775

    // Get the search index. Index #1 is to search using option code.
776
    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800

    // Get the options for specified index. Expecting one option to be
    // returned but in theory we may have multiple options with the same
    // code so we get the range.
    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
              Subnet::OptionContainerTypeIndex::const_iterator> range =
        idx.equal_range(D6O_IA_NA);
    // Expect single option with the code equal to IA_NA option code.
    ASSERT_EQ(1, std::distance(range.first, range.second));
    // The actual pointer to the option is held in the option field
    // in the structure returned.
    OptionPtr option = range.first->option;
    ASSERT_TRUE(option);
    // Option object returned for here is expected to be Option6IA
    // which is derived from Option. This class is dedicated to
    // represent standard option IA_NA.
    boost::shared_ptr<Option6IA> optionIA =
        boost::dynamic_pointer_cast<Option6IA>(option);
    // If cast is unsuccessful than option returned was of a
    // differnt type than Option6IA. This is wrong.
    ASSERT_TRUE(optionIA);
    // If cast was successful we may use accessors exposed by
    // Option6IA to validate that the content of this option
    // has been set correctly.
801 802 803
    EXPECT_EQ(12345, optionIA->getIAID());
    EXPECT_EQ(6789, optionIA->getT1());
    EXPECT_EQ(1516, optionIA->getT2());
804 805
}

806
};