config_parser_unittest.cc 122 KB
Newer Older
1
// Copyright (C) 2012-2014 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
28
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
29
#include <dhcpsrv/testutils/config_result_check.h>
30
31
#include <hooks/hooks_manager.h>

32
#include "test_data_files_config.h"
33
34
#include "test_libraries.h"
#include "marker_file.h"
35

36
#include <boost/foreach.hpp>
37
#include <gtest/gtest.h>
38

39
40
#include <fstream>
#include <iostream>
41
#include <fstream>
42
#include <sstream>
43
44
#include <string>
#include <vector>
45

46
#include <arpa/inet.h>
47
#include <unistd.h>
48
49
50
51

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

namespace {

60
61
62
63
64
65
66
67
68
69
70
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")));
}

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

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

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

        valid_iface_ = ifaces.begin()->getName();
        bogus_iface_ = "nonexisting0";
89
90
91

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

        // Reset configuration for each test.
        resetConfiguration();
98
99
    }

100
101
102
103
104
105
106
107
108
    // 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());
    }

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

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

118
119
120
121
122
123
124
125
126
    // 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_);
    }

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

174
175
176
177
178
179
180
181
    /// @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
182
183
184
    std::string createConfigWithOption(const std::map<std::string,
                                       std::string>& params)
    {
185
        std::ostringstream stream;
186
        stream << "{ \"interfaces\": [ \"*\" ],"
187
188
189
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
190
191
192
193
194
195
196
197
198
            "\"option-def\": [ {"
            "  \"name\": \"bool-option\","
            "  \"code\": 1000,"
            "  \"type\": \"boolean\","
            "  \"array\": False,"
            "  \"record-types\": \"\","
            "  \"space\": \"dhcp6\","
            "  \"encapsulate\": \"\""
            "} ],"
199
            "\"subnet6\": [ { "
200
            "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
201
202
            "    \"subnet\": \"2001:db8:1::/64\", "
            "    \"option-data\": [ {";
203
204
205
206
207
208
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
209
                // cppcheck-suppress unreadVariable
210
211
212
213
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
214
215
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
216
            } else if (param.first == "code") {
217
                stream << "\"code\": " << param.second;;
218
219
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
220
221
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
222
            }
223
224
225
226
227
228
229
230
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    /// @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.
246
    OptionDescriptor
247
248
249
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
250
251
        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
                                                          classify_);
252
253
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()
                          << "does not exist in Config Manager";
        }
258
        OptionContainerPtr options =
259
            subnet->getCfgOption()->getAll("dhcp6");
260
261
262
263
264
265
266
267
        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.
268
        const OptionContainerTypeIndex& idx = options->get<1>();
269
270
271
272

        // 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.
273
274
        std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> range =
275
276
277
278
279
280
281
            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) {
282
            return (OptionDescriptor(OptionPtr(), false));
283
284
285
286
287
        }

        return (*range.first);
    }

288
    /// @brief Parse and Execute configuration
289
    ///
290
291
292
293
294
295
296
297
298
299
300
    /// 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) {
301
302
303
        ConstElementPtr status;
        try {
            ElementPtr json = Element::fromJSON(config);
304
            status = configureDhcp6Server(srv_, json);
305

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

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

        // Store the answer if we need it.

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

        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() {
347
        string config = "{ \"interfaces\": [ \"*\" ],"
348
349
350
351
352
353
            "\"hooks-libraries\": [ ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"valid-lifetime\": 4000, "
            "\"subnet6\": [ ], "
354
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
355
356
357
358
            "\"option-def\": [ ], "
            "\"option-data\": [ ] }";
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
359
360
361
362
        // 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.
363
        CfgMgr::instance().clear();
364
365
        // Create fresh context.
        globalContext()->copyContext(ParserContext(Option::V6));
366
367
    }

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

389
390
391
392
393
394
395
396
397
398
399
400
401
    /// @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));
402
403
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
404
        CfgMgr::instance().clear();
405
406
    }

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

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) {
        std::string config = createConfigWithOption(params);
        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
474

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

483
484
485
486
487
    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)
488
    isc::dhcp::ClientClasses classify_; ///< used in client classification
489
490
};

491
492
493
// 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.
494
495
496
497
TEST_F(Dhcp6ParserTest, version) {

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
498
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
499
500
501
                    Element::fromJSON("{\"version\": 0}")));

    // returned value must be 0 (configuration accepted)
502
    checkResult(x, 0);
503
504
}

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

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
511
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
512
513
514
                    Element::fromJSON("{\"bogus\": 5}")));

    // returned value must be 1 (configuration parse error)
515
    checkResult(x, 1);
516
517
}

518
519
/// The goal of this test is to verify if configuration without any
/// subnets defined can be accepted.
520
TEST_F(Dhcp6ParserTest, emptySubnet) {
521

522
    ConstElementPtr status;
523

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

532
    // returned value should be 0 (success)
533
    checkResult(status, 0);
534
535
}

536
537
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
538
TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
539

540
    ConstElementPtr status;
541

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

    ElementPtr json = Element::fromJSON(config);

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

555
    // check if returned status is OK
556
    checkResult(status, 0);
557

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

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

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

    int cnt = 0; // Number of reconfigurations

602
    ElementPtr json = Element::fromJSON(config);
603

604
    do {
605
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
606
        checkResult(x, 0);
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622

        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
        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);
623
624
}

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

    int cnt = 0; // Number of reconfigurations

657
    ElementPtr json = Element::fromJSON(config);
658

659
    do {
660
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
661
        checkResult(x, 0);
662
663
664
665
666
667
668
669
670
671
672
673
674

        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
        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.
675
    } while (++cnt < 3);
676
677
678
679
680
681
682
683
684
685
686
}

// 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\": [ { "
687
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
688
689
690
691
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
692
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
693
694
695
696
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
697
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
698
699
700
701
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
702
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
703
704
705
706
707
708
709
710
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
711
    checkResult(x, 1);
712
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
713
714
715
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
716
717
718
719
720
721
722
723
724
725
726
// 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\": [ { "
727
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
728
729
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
730
731
        " },"
        " {"
732
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
733
734
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
735
736
        " },"
        " {"
737
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
738
739
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
740
741
        " },"
        " {"
742
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
743
744
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
745
746
747
748
749
750
751
752
753
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
754
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
755
756
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
757
758
        " },"
        " {"
759
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
760
761
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
762
763
        " },"
        " {"
764
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
765
766
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
767
768
769
770
771
772
773
774
775
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
776
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
777
778
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
779
780
        " },"
        " {"
781
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
782
783
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
784
785
        " },"
        " {"
786
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
787
788
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
789
790
791
792
793
794
795
796
        " } ],"
        "\"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));
797
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
798
799
800
801
802
803
804
805

    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
    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));
806
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
807
808
809
810
811
812
813
814
815
816
817
818
819
820

    subnets = CfgMgr::instance().getSubnets6();
    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));
821
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
822
823
824
825

    // Do reconfiguration
    json = Element::fromJSON(config_second_removed);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
826
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
827
828
829
830
831
832
833
834
835
836
837
838
839

    subnets = CfgMgr::instance().getSubnets6();
    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());
}



840
841
// This test checks if it is possible to override global values
// on a per subnet basis.
842
TEST_F(Dhcp6ParserTest, subnetLocal) {
843

844
    ConstElementPtr status;
845

846
    string config = "{ \"interfaces\": [ \"*\" ],"
847
848
849
850
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
851
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
852
853
854
855
856
857
858
859
860
        "    \"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
861
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
862

863
    // returned value should be 0 (configuration success)
864
    checkResult(status, 0);
865

866
867
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
868
869
870
871
872
873
874
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1, subnet->getT1());
    EXPECT_EQ(2, subnet->getT2());
    EXPECT_EQ(3, subnet->getPreferred());
    EXPECT_EQ(4, subnet->getValid());
}

875
876
877
878
879
880
// 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
881
882
    // There should be at least one interface

883
    string config = "{ \"interfaces\": [ \"*\" ],"
884
885
886
887
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
888
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
889
        "    \"interface\": \"" + valid_iface_ + "\","
890
891
892
893
894
895
896
897
898
        "    \"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)
899
    checkResult(status, 0);
900

901
902
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
903
    ASSERT_TRUE(subnet);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
904
905
906
907
908
909
910
911
912
913
914
    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

915
    string config = "{ \"interfaces\": [ \"*\" ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
916
917
918
919
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
920
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
921
922
923
924
925
926
927
928
929
930
        "    \"interface\": \"" + bogus_iface_ + "\","
        "    \"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 1 (configuration error)
931
932
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
933

934
935
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
936
    EXPECT_FALSE(subnet);
937
938
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
939

940
941
942
943
944
945
// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceGlobal) {

    ConstElementPtr status;

946
    string config = "{ \"interfaces\": [ \"*\" ],"
947
948
949
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
950
        "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
951
        "\"subnet6\": [ { "
952
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
953
954
955
956
957
958
959
960
961
        "    \"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 1 (parse error)
962
963
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
964
965
}

966
967
968
969
970
971
972
973
974
975

// This test checks if it is possible to define a subnet with an
// interface-id option defined.
TEST_F(Dhcp6ParserTest, subnetInterfaceId) {

    const string valid_interface_id = "foobar";
    const string bogus_interface_id = "blah";

    // There should be at least one interface

976
    const string config = "{ "
977
978
979
980
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
981
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
982
983
984
985
986
987
        "    \"interface-id\": \"" + valid_interface_id + "\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

988
    ConstElementPtr status;
989
990
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

991
    // Returned value should be 0 (configuration success)
992
    checkResult(status, 0);
993

994
    // Try to get a subnet based on bogus interface-id option
995
996
    OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
    OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
997
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
998
999
    EXPECT_FALSE(subnet);

1000
    // Now try to get subnet for valid interface-id value