config_parser_unittest.cc 135 KB
Newer Older
1
// Copyright (C) 2012-2015 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

#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
20
#include <dhcp/iface_mgr.h>
21 22
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
23
#include <dhcp/tests/iface_mgr_test_config.h>
24
#include <dhcp6/json_config_parser.h>
25
#include <dhcp6/dhcp6_srv.h>
26
#include <dhcpsrv/addr_utilities.h>
27
#include <dhcpsrv/cfgmgr.h>
28
#include <dhcpsrv/cfg_hosts.h>
29
#include <dhcpsrv/subnet.h>
30
#include <dhcpsrv/subnet_selector.h>
31
#include <dhcpsrv/testutils/config_result_check.h>
32 33
#include <hooks/hooks_manager.h>

34
#include "test_data_files_config.h"
35 36
#include "test_libraries.h"
#include "marker_file.h"
37

38
#include <boost/foreach.hpp>
39
#include <gtest/gtest.h>
40

41 42
#include <fstream>
#include <iostream>
43
#include <fstream>
44
#include <sstream>
45 46
#include <string>
#include <vector>
47

48
#include <arpa/inet.h>
49
#include <unistd.h>
50 51 52 53

using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
54 55
using namespace isc::data;
using namespace isc::dhcp;
56
using namespace isc::dhcp::test;
57
using namespace isc::hooks;
58
using namespace std;
59 60 61

namespace {

62 63 64 65 66 67 68 69 70 71 72
std::string specfile(const std::string& name) {
    return (std::string(DHCP6_SRC_DIR) + "/" + name);
}

/// @brief Tests that the spec file is valid.
/// Verifies that the DHCP6 configuration specification file is valid.
TEST(Dhcp6SpecTest, basicSpec) {
    ASSERT_NO_THROW(isc::config::
                    moduleSpecFromFile(specfile("dhcp6.spec")));
}

73 74
class Dhcp6ParserTest : public ::testing::Test {
public:
Stephen Morris's avatar
Stephen Morris committed
75 76
    Dhcp6ParserTest() :rcode_(-1), srv_(0) {
        // srv_(0) means to not open any sockets. We don't want to
77 78
        // deal with sockets here, just check if configuration handling
        // is sane.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
79

80 81
        const IfaceMgr::IfaceCollection& ifaces =
            IfaceMgr::instance().getIfaces();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
82 83 84

        // There must be some interface detected
        if (ifaces.empty()) {
85
            // We can't use ASSERT in constructor
Tomek Mrugalski's avatar
Tomek Mrugalski committed
86 87 88 89 90
            ADD_FAILURE() << "No interfaces detected.";
        }

        valid_iface_ = ifaces.begin()->getName();
        bogus_iface_ = "nonexisting0";
91 92 93

        if (IfaceMgr::instance().getIface(bogus_iface_)) {
            ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
94 95
                          << " while the test assumes that it doesn't, to execute"
                          << " some negative scenarios. Can't continue this test.";
96
        }
97 98 99

        // Reset configuration for each test.
        resetConfiguration();
100 101
    }

102 103 104 105 106 107 108 109 110
    // Check that no hooks libraries are loaded.  This is a pre-condition for
    // a number of tests, so is checked in one place.  As this uses an
    // ASSERT call - and it is not clear from the documentation that Gtest
    // predicates can be used in a constructor - the check is placed in SetUp.
    void SetUp() {
        std::vector<std::string> libraries = HooksManager::getLibraryNames();
        ASSERT_TRUE(libraries.empty());
    }

111
    ~Dhcp6ParserTest() {
112 113
        // Reset configuration database after each test.
        resetConfiguration();
114 115 116 117

        // ... and delete the hooks library marker files if present
        unlink(LOAD_MARKER_FILE);
        unlink(UNLOAD_MARKER_FILE);
118 119
    };

120 121 122 123 124 125 126 127 128
    // Checks if config_result (result of DHCP server configuration) has
    // expected code (0 for success, other for failures).
    // Also stores result in rcode_ and comment_.
    void checkResult(ConstElementPtr status, int expected_code) {
        ASSERT_TRUE(status);
        comment_ = parseAnswer(rcode_, status);
        EXPECT_EQ(expected_code, rcode_);
    }

129 130 131
    /// @brief Create the simple configuration with single option.
    ///
    /// This function allows to set one of the parameters that configure
132 133
    /// option value. These parameters are: "name", "code", "data" and
    /// "csv-format".
134
    ///
135
    /// @param param_value string holding option parameter value to be
136 137 138 139 140
    /// 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) {
141 142 143
        std::map<std::string, std::string> params;
        if (parameter == "name") {
            params["name"] = param_value;
144 145
            params["space"] = "dhcp6";
            params["code"] = "38";
146
            params["data"] = "ABCDEF0105";
147
            params["csv-format"] = "False";
148
        } else if (parameter == "space") {
149 150 151
            params["name"] = "subscriber-id";
            params["space"] = param_value;
            params["code"] = "38";
152
            params["data"] = "ABCDEF0105";
153
            params["csv-format"] = "False";
154
        } else if (parameter == "code") {
155 156
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
157
            params["code"] = param_value;
158
            params["data"] = "ABCDEF0105";
159
            params["csv-format"] = "False";
160
        } else if (parameter == "data") {
161 162 163
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
            params["code"] = "38";
164
            params["data"] = param_value;
165 166
            params["csv-format"] = "False";
        } else if (parameter == "csv-format") {
167 168 169
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
            params["code"] = "38";
170
            params["data"] = "ABCDEF0105";
171
            params["csv-format"] = param_value;
172 173 174 175
        }
        return (createConfigWithOption(params));
    }

176 177 178 179 180 181 182 183
    /// @brief Create simple configuration with single option.
    ///
    /// This function creates a configuration for a single option with
    /// custom values for all parameters that describe the option.
    ///
    /// @params params map holding parameters and their values.
    /// @return configuration string containing custom values of parameters
    /// describing an option.
Mukund Sivaraman's avatar
Mukund Sivaraman committed
184 185 186
    std::string createConfigWithOption(const std::map<std::string,
                                       std::string>& params)
    {
187
        std::ostringstream stream;
188
        stream << "{ \"interfaces\": [ \"*\" ],"
189 190 191
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
192 193 194 195 196 197 198 199 200
            "\"option-def\": [ {"
            "  \"name\": \"bool-option\","
            "  \"code\": 1000,"
            "  \"type\": \"boolean\","
            "  \"array\": False,"
            "  \"record-types\": \"\","
            "  \"space\": \"dhcp6\","
            "  \"encapsulate\": \"\""
            "} ],"
201
            "\"subnet6\": [ { "
202
            "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
203 204
            "    \"subnet\": \"2001:db8:1::/64\", "
            "    \"option-data\": [ {";
205 206 207 208 209 210
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
211
                // cppcheck-suppress unreadVariable
212 213 214 215
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
216 217
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
218
            } else if (param.first == "code") {
219
                stream << "\"code\": " << param.second;;
220 221
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
222 223
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
224
            }
225 226 227 228 229 230 231 232
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    /// @brief Returns an option from the subnet.
    ///
    /// This function returns an option from a subnet to which the
    /// specified subnet address belongs. The option is identified
    /// by its code.
    ///
    /// @param subnet_address Address which belongs to the subnet from
    /// which the option is to be returned.
    /// @param option_code Code of the option to be returned.
    /// @param expected_options_count Expected number of options in
    /// the particular subnet.
    ///
    /// @return Descriptor of the option. If the descriptor holds a
    /// NULL option pointer, it means that there was no such option
    /// in the subnet.
248
    OptionDescriptor
249 250 251
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
252 253
        Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
            selectSubnet(subnet_address, classify_);
254 255 256 257
        if (!subnet) {
            /// @todo replace toText() with the use of operator <<.
            ADD_FAILURE() << "A subnet for the specified address "
                          << subnet_address.toText()
258
                          << " does not exist in Config Manager";
259
        }
260
        OptionContainerPtr options =
261
            subnet->getCfgOption()->getAll("dhcp6");
262 263 264 265 266 267 268 269
        if (expected_options_count != options->size()) {
            ADD_FAILURE() << "The number of options in the subnet '"
                          << subnet_address.toText() << "' is different "
                " than expected number of options '"
                          << expected_options_count << "'";
        }

        // Get the search index. Index #1 is to search using option code.
270
        const OptionContainerTypeIndex& idx = options->get<1>();
271 272 273 274

        // 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.
275 276
        std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> range =
277 278 279 280 281 282 283
            idx.equal_range(option_code);
        if (std::distance(range.first, range.second) > 1) {
            ADD_FAILURE() << "There is more than one option having the"
                " option code '" << option_code << "' in a subnet '"
                          << subnet_address.toText() << "'. Expected "
                " at most one option";
        } else if (std::distance(range.first, range.second) == 0) {
284
            return (OptionDescriptor(OptionPtr(), false));
285 286 287 288 289
        }

        return (*range.first);
    }

290
    /// @brief Parse and Execute configuration
291
    ///
292 293 294 295 296 297 298 299 300 301 302
    /// Parses a configuration and executes a configuration of the server.
    /// If the operation fails, the current test will register a failure.
    ///
    /// @param config Configuration to parse
    /// @param operation Operation being performed.  In the case of an error,
    ///        the error text will include the string "unable to <operation>.".
    ///
    /// @return true if the configuration succeeded, false if not.  In the
    ///         latter case, a failure will have been added to the current test.
    bool
    executeConfiguration(const std::string& config, const char* operation) {
303 304 305
        ConstElementPtr status;
        try {
            ElementPtr json = Element::fromJSON(config);
306
            status = configureDhcp6Server(srv_, json);
307

308
        } catch (const std::exception& ex) {
309 310
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The following configuration was used: " << std::endl
311 312 313
                   << config << std::endl
                   << " and the following error message was returned:"
                   << ex.what() << std::endl;
314
            return (false);
315 316
        }

317
        // The status object must not be NULL
318
        if (!status) {
319 320 321
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned a null pointer.";
            return (false);
322
        }
323 324 325 326

        // Store the answer if we need it.

        // Returned value should be 0 (configuration success)
327 328
        comment_ = parseAnswer(rcode_, status);
        if (rcode_ != 0) {
329 330 331 332 333 334 335 336
            string reason = "";
            if (comment_) {
                reason = string(" (") + comment_->stringValue() + string(")");
            }
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned error code "
                   << rcode_ << reason;
            return (false);
337
        }
338 339 340 341 342 343 344 345 346 347 348

        return (true);
    }

    /// @brief Reset configuration database.
    ///
    /// This function resets configuration data base by removing all subnets
    /// option-data, and hooks libraries. The reset must be performed after each
    /// test to make sure that contents of the database do not affect the
    /// results of subsequent tests.
    void resetConfiguration() {
349
        string config = "{ \"interfaces\": [ \"*\" ],"
350 351 352 353 354 355
            "\"hooks-libraries\": [ ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"valid-lifetime\": 4000, "
            "\"subnet6\": [ ], "
356
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
357 358 359 360
            "\"option-def\": [ ], "
            "\"option-data\": [ ] }";
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
361 362 363 364
        // The default setting is to listen on all interfaces. In order to
        // properly test interface configuration we disable listening on
        // all interfaces before each test and later check that this setting
        // has been overriden by the configuration used in the test.
365
        CfgMgr::instance().clear();
366 367
        // Create fresh context.
        globalContext()->copyContext(ParserContext(Option::V6));
368 369
    }

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    /// @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
385
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
386 387
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
388
        CfgMgr::instance().clear();
389 390
    }

391 392 393 394 395 396 397 398 399 400 401 402 403
    /// @brief Test invalid option paramater 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 params Map of parameters defining an option.
    void
    testInvalidOptionParam(const std::map<std::string, std::string>& params) {
        ConstElementPtr x;
        std::string config = createConfigWithOption(params);
        ElementPtr json = Element::fromJSON(config);
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
404 405
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
406
        CfgMgr::instance().clear();
407 408
    }

409 410 411 412 413 414 415
    /// @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.
416 417
    /// @param extra_data if true extra data is allowed in an option
    /// after tested data.
418
    void testOption(const OptionDescriptor& option_desc,
419
                    uint16_t expected_code, const uint8_t* expected_data,
420 421
                    size_t expected_data_len,
                    bool extra_data = false) {
422
        // Check if option descriptor contains valid option pointer.
423
        ASSERT_TRUE(option_desc.option_);
424
        // Verify option type.
425
        EXPECT_EQ(expected_code, option_desc.option_->getType());
426 427 428 429 430
        // 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.
431 432
        util::OutputBuffer buf(option_desc.option_->getData().size());
        option_desc.option_->pack(buf);
433 434 435 436 437
        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.
438
            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
439 440
                      expected_data_len);
        } else {
441
            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
442 443
                      expected_data_len);
        }
444
        // Verify that the data is correct. Do not verify suboptions and a header.
445
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
446
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
447
                            expected_data_len));
448 449
    }

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    /// @brief Test option configuration.
    ///
    /// This function creates a configuration for a specified option using
    /// a map of parameters specified as the argument. The map holds
    /// name/value pairs which identifies option's configuration parameters:
    /// - name
    /// - space
    /// - code
    /// - data
    /// - csv-format.
    /// This function applies a new server configuration and checks that the
    /// option being configured is inserted into CfgMgr. The raw contents of
    /// this option are compared with the binary data specified as expected
    /// data passed to this function.
    ///
    /// @param params Map of parameters defining an option.
    /// @param option_code Option code.
    /// @param expected_data Array containing binary data expected to be stored
    /// in the configured option.
    /// @param expected_data_len Length of the array holding reference data.
    void testConfiguration(const std::map<std::string, std::string>& params,
                           const uint16_t option_code,
                           const uint8_t* expected_data,
                           const size_t expected_data_len) {
474 475
        CfgMgr::instance().clear();

476 477
        std::string config = createConfigWithOption(params);
        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
478

479
        // The subnet should now hold one option with the specified code.
480
        OptionDescriptor desc =
481
            getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
482
        ASSERT_TRUE(desc.option_);
483
        testOption(desc, option_code, expected_data, expected_data_len);
484
        CfgMgr::instance().clear();
485 486
    }

487 488 489 490 491
    int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
    Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
    ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
    string valid_iface_; ///< Valid network interface name (present in system)
    string bogus_iface_; ///< invalid network interface name (not in system)
492
    isc::dhcp::ClientClasses classify_; ///< used in client classification
493 494
};

495 496 497
// 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.
498 499 500 501
TEST_F(Dhcp6ParserTest, version) {

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
502
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
503 504 505
                    Element::fromJSON("{\"version\": 0}")));

    // returned value must be 0 (configuration accepted)
506
    checkResult(x, 0);
507 508
}

509 510
/// The goal of this test is to verify that the code accepts only
/// valid commands and malformed or unsupported parameters are rejected.
511
TEST_F(Dhcp6ParserTest, bogusCommand) {
512 513 514

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
515
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
516 517 518
                    Element::fromJSON("{\"bogus\": 5}")));

    // returned value must be 1 (configuration parse error)
519
    checkResult(x, 1);
520 521
}

522 523
/// The goal of this test is to verify if configuration without any
/// subnets defined can be accepted.
524
TEST_F(Dhcp6ParserTest, emptySubnet) {
525

526
    ConstElementPtr status;
527

Stephen Morris's avatar
Stephen Morris committed
528
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
529
                    Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
530 531 532 533 534 535
                                      "\"preferred-lifetime\": 3000,"
                                      "\"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"subnet6\": [  ], "
                                      "\"valid-lifetime\": 4000 }")));

536
    // returned value should be 0 (success)
537
    checkResult(status, 0);
538 539
}

540 541
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
542
TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
543

544
    ConstElementPtr status;
545

546
    string config = "{ \"interfaces\": [ \"*\" ],"
547 548 549 550
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
551
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
552 553 554 555 556
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

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

559
    // check if returned status is OK
560
    checkResult(status, 0);
561

562 563
    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
564 565
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
566 567 568 569 570
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
571 572 573 574 575

    // Check that subnet-id is 1
    EXPECT_EQ(1, subnet->getID());
}

576
// This test checks that multiple subnets can be defined and handled properly.
577 578
TEST_F(Dhcp6ParserTest, multipleSubnets) {
    ConstElementPtr x;
579 580
    // Collection of four subnets for which ids should be autogenerated
    // - ids are unspecified or set to 0.
581 582 583 584 585
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
586
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
587 588 589
        "    \"subnet\": \"2001:db8:1::/64\" "
        " },"
        " {"
590
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
591 592
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 0"
593 594
        " },"
        " {"
595
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
596 597 598
        "    \"subnet\": \"2001:db8:3::/64\" "
        " },"
        " {"
599
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
600 601 602 603 604 605
        "    \"subnet\": \"2001:db8:4::/64\" "
        " } ],"
        "\"valid-lifetime\": 4000 }";

    int cnt = 0; // Number of reconfigurations

606
    ElementPtr json = Element::fromJSON(config);
607

608
    do {
609
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
610
        checkResult(x, 0);
611

612 613 614 615
        CfgMgr::instance().commit();

        const Subnet6Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
616 617 618 619 620 621 622 623 624 625 626 627 628 629
        ASSERT_TRUE(subnets);
        ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

        // Check subnet-ids of each subnet (it should be monotonously increasing)
        EXPECT_EQ(1, subnets->at(0)->getID());
        EXPECT_EQ(2, subnets->at(1)->getID());
        EXPECT_EQ(3, subnets->at(2)->getID());
        EXPECT_EQ(4, subnets->at(3)->getID());

        // Repeat reconfiguration process 10 times and check that the subnet-id
        // is set to the same value. Technically, just two iterations would be
        // sufficient, but it's nice to have a test that exercises reconfiguration
        // a bit.
    } while (++cnt < 10);
630 631
}

632 633 634
// This checks that it is possible to assign arbitrary ids for subnets.
TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
    ConstElementPtr x;
635
    // Four subnets with arbitrary subnet ids.
636 637 638 639 640
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
641
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
642 643 644 645
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
646
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
647 648 649 650
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
651
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
652 653 654 655
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1"
        " },"
        " {"
656
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
657 658 659 660 661 662 663
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    int cnt = 0; // Number of reconfigurations

664
    ElementPtr json = Element::fromJSON(config);
665

666
    do {
667
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
668
        checkResult(x, 0);
669

670 671 672 673
        CfgMgr::instance().commit();

        const Subnet6Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
674 675 676 677 678 679 680 681 682 683 684
        ASSERT_TRUE(subnets);
        ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

        // Check that subnet ids are as expected.
        EXPECT_EQ(1024, subnets->at(0)->getID());
        EXPECT_EQ(100, subnets->at(1)->getID());
        EXPECT_EQ(1, subnets->at(2)->getID());
        EXPECT_EQ(34, subnets->at(3)->getID());

        // Repeat reconfiguration process 10 times and check that the subnet-id
        // is set to the same value.
685
    } while (++cnt < 3);
686 687 688 689 690 691 692 693 694 695 696
}

// CHeck that the configuration with two subnets having the same id is rejected.
TEST_F(Dhcp6ParserTest, multipleSubnetsOverlapingIDs) {
    ConstElementPtr x;
    // Four subnets, two of them have the same id.
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
697
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
698 699 700 701
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
702
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
703 704 705 706
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
707
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
708 709 710 711
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
712
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
713 714 715 716 717 718 719 720
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
721
    checkResult(x, 1);
722
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
723 724 725
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
726 727 728 729 730 731 732 733 734 735 736
// Goal of this test is to verify that a previously configured subnet can be
// deleted in subsequent reconfiguration.
TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
    ConstElementPtr x;

    // All four subnets
    string config4 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
737
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
738 739
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
740 741
        " },"
        " {"
742
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
743 744
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
745 746
        " },"
        " {"
747
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
748 749
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
750 751
        " },"
        " {"
752
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
753 754
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
755 756 757 758 759 760 761 762 763
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
764
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
765 766
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
767 768
        " },"
        " {"
769
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
770 771
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
772 773
        " },"
        " {"
774
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
775 776
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
777 778 779 780 781 782 783 784 785
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
786
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
787 788
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
789 790
        " },"
        " {"
791
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
792 793
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
794 795
        " },"
        " {"
796
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
797 798
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
799 800 801 802 803 804 805 806
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // CASE 1: Configure 4 subnets, then reconfigure and remove the
    // last one.

    ElementPtr json = Element::fromJSON(config4);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
807
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
808

809 810 811 812
    CfgMgr::instance().commit();

    const Subnet6Collection* subnets =
        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
813 814 815 816 817 818
    ASSERT_TRUE(subnets);
    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

    // Do the reconfiguration (the last subnet is removed)
    json = Element::fromJSON(config_first3);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
819
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
820

821 822 823
    CfgMgr::instance().commit();

    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
824 825 826 827 828 829 830 831 832 833 834 835
    ASSERT_TRUE(subnets);
    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)

    EXPECT_EQ(1, subnets->at(0)->getID());
    EXPECT_EQ(2, subnets->at(1)->getID());
    EXPECT_EQ(3, subnets->at(2)->getID());

    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
    /// from in between (not first, not last)

    json = Element::fromJSON(config4);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
836
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
837

838 839
    CfgMgr::instance().commit();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
840 841 842
    // Do reconfiguration
    json = Element::fromJSON(config_second_removed);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
843
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
844

845 846 847
    CfgMgr::instance().commit();

    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
848 849 850 851 852 853 854 855 856 857 858
    ASSERT_TRUE(subnets);
    ASSERT_EQ(3, subnets->size()); // We expect 4 subnets

    EXPECT_EQ(1, subnets->at(0)->getID());
    // The second subnet (with subnet-id = 2) is no longer there
    EXPECT_EQ(3, subnets->at(1)->getID());
    EXPECT_EQ(4, subnets->at(2)->getID());
}



859 860
// This test checks if it is possible to override global values
// on a per subnet basis.
861
TEST_F(Dhcp6ParserTest, subnetLocal) {
862

863
    ConstElementPtr status;
864

865
    string config = "{ \"interfaces\": [ \"*\" ],"
866 867 868 869
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
870
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
871 872 873 874 875 876 877 878 879
        "    \"renew-timer\": 1, "
        "    \"rebind-timer\": 2, "
        "    \"preferred-lifetime\": 3,"
        "    \"valid-lifetime\": 4,"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

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

882
    // returned value should be 0 (configuration success)
883
    checkResult(status, 0);
884

885 886
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
887 888 889 890 891 892 893
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1, subnet->getT1());
    EXPECT_EQ(2, subnet->getT2());
    EXPECT_EQ(3, subnet->getPreferred());
    EXPECT_EQ(4, subnet->getValid());
}

894 895 896 897 898 899
// This test checks if it is possible to define a subnet with an
// interface defined.
TEST_F(Dhcp6ParserTest, subnetInterface) {

    ConstElementPtr status;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
900 901
    // There should be at least one interface

902
    string config = "{ \"interfaces\": [ \"*\" ],"
903 904 905 906
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
907
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
908
        "    \"interface\": \"" + valid_iface_ + "\","
909 910 911 912 913 914 915 916 917
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

    // returned value should be 0 (configuration success)
918
    checkResult(status, 0);
919

920 921
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
922
    ASSERT_TRUE(subnet);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
923 924 925 926 927 928 929 930 931 932 933
    EXPECT_EQ(valid_iface_, subnet->getIface());
}

// This test checks if invalid interface name will be rejected in
// Subnet6 definition.
TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {

    ConstElementPtr status;

    // There should be at least one interface