diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc index 5851a6d623..212028eb96 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@ -880,6 +880,7 @@ private: << ", code: " << option_code << "): " << ex.what()); } + } // All went good, so we can set the option space name. option_space_ = option_space; @@ -1139,7 +1140,7 @@ private: } } catch (const Exception& ex) { isc_throw(DhcpConfigError, "invalid record type values" - << " specified for the option definition: " + << " specified for the option definition: " << ex.what()); } } diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc index f5e132a7ea..294f18a439 100644 --- a/src/bin/dhcp6/config_parser.cc +++ b/src/bin/dhcp6/config_parser.cc @@ -103,7 +103,6 @@ OptionStorage option_defaults; /// @brief Global storage for option definitions. OptionDefStorage option_def_intermediate; - /// @brief a dummy configuration parser /// /// This is a debugging parser. It does not configure anything, @@ -1037,8 +1036,8 @@ public: BOOST_FOREACH(ConfigPair param, option_def->mapValue()) { std::string entry(param.first); ParserPtr parser; - if (entry == "name" || entry == "type" || - entry == "record-types" || entry == "space") { + if (entry == "name" || entry == "type" || entry == "record-types" || + entry == "space" || entry == "encapsulate") { StringParserPtr str_parser(dynamic_cast(StringParser::factory(entry))); if (str_parser) { @@ -1122,9 +1121,36 @@ private: uint32_t code = getParam("code", uint32_values_); std::string type = getParam("type", string_values_); bool array_type = getParam("array", boolean_values_); + std::string encapsulates = getParam("encapsulate", + string_values_); + + // Create option definition. + OptionDefinitionPtr def; + // We need to check if user has set encapsulated option space + // name. If so, different constructor will be used. + if (!encapsulates.empty()) { + // Arrays can't be used together with sub-options. + if (array_type) { + isc_throw(DhcpConfigError, "option '" << space << "." + << "name" << "', comprising an array of data" + << " fields may not encapsulate any option space"); + + } else if (encapsulates == space) { + isc_throw(DhcpConfigError, "option must not encapsulate" + << " an option space it belongs to: '" + << space << "." << name << "' is set to" + << " encapsulate '" << space << "'"); + + } else { + def.reset(new OptionDefinition(name, code, type, + encapsulates.c_str())); + } + + } else { + def.reset(new OptionDefinition(name, code, type, array_type)); + + } - OptionDefinitionPtr def(new OptionDefinition(name, code, - type, array_type)); // The record-types field may carry a list of comma separated names // of data types that form a record. std::string record_types = getParam("record-types", @@ -1142,7 +1168,7 @@ private: } } catch (const Exception& ex) { isc_throw(DhcpConfigError, "invalid record type values" - << " specified for the option definition: " + << " specified for the option definition: " << ex.what()); } } diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec index b719ebb4ef..c7e9ff7c3c 100644 --- a/src/bin/dhcp6/dhcp6.spec +++ b/src/bin/dhcp6/dhcp6.spec @@ -86,6 +86,12 @@ "item_type": "string", "item_optional": false, "item_default": "" + }, + + { "item_name": "encapsulate", + "item_type": "string", + "item_optional": false, + "item_default": "" } ] } }, diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index b6ef97ffe7..6c677acc4b 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/src/bin/dhcp6/tests/config_parser_unittest.cc @@ -461,7 +461,8 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) { " \"type\": \"ipv6-address\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -499,7 +500,8 @@ TEST_F(Dhcp6ParserTest, optionDefRecord) { " \"type\": \"record\"," " \"array\": False," " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -546,7 +548,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) { " \"type\": \"uint32\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " }," " {" " \"name\": \"foo-2\"," @@ -554,7 +557,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) { " \"type\": \"ipv4-address\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -604,7 +608,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) { " \"type\": \"uint32\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " }," " {" " \"name\": \"foo-2\"," @@ -612,7 +617,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) { " \"type\": \"ipv4-address\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -640,7 +646,8 @@ TEST_F(Dhcp6ParserTest, optionDefArray) { " \"type\": \"uint32\"," " \"array\": True," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -666,6 +673,47 @@ TEST_F(Dhcp6ParserTest, optionDefArray) { EXPECT_TRUE(def->getArrayType()); } +// The purpose of this test to verify that encapsulated option +// space name may be specified. +TEST_F(Dhcp6ParserTest, optionDefEncapsulate) { + + // Configuration string. Included the encapsulated + // option space name. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 100," + " \"type\": \"uint32\"," + " \"array\": False," + " \"record-types\": \"\"," + " \"space\": \"isc\"," + " \"encapsulate\": \"sub-opts-space\"" + " } ]" + "}"; + ElementPtr json = Element::fromJSON(config); + + // Make sure that the particular option definition does not exist. + OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100); + ASSERT_FALSE(def); + + // Use the configuration string to create new option definition. + ConstElementPtr status; + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + ASSERT_TRUE(status); + checkResult(status, 0); + + // The option definition should now be available in the CfgMgr. + def = CfgMgr::instance().getOptionDef("isc", 100); + ASSERT_TRUE(def); + + // Check the option data. + EXPECT_EQ("foo", def->getName()); + EXPECT_EQ(100, def->getCode()); + EXPECT_EQ(OPT_UINT32_TYPE, def->getType()); + EXPECT_FALSE(def->getArrayType()); + EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace()); +} + /// The purpose of this test is to verify that the option definition /// with invalid name is not accepted. TEST_F(Dhcp6ParserTest, optionDefInvalidName) { @@ -678,7 +726,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidName) { " \"type\": \"string\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -703,7 +752,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidType) { " \"type\": \"sting\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -728,7 +778,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) { " \"type\": \"record\"," " \"array\": False," " \"record-types\": \"uint32,uint8,sting\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -741,6 +792,85 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) { checkResult(status, 1); } +/// The goal of this test is to verify that the invalid encapsulated +/// option space name is not accepted. +TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) { + // Configuration string. The encapsulated option space + // name is invalid (% character is not allowed). + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 100," + " \"type\": \"uint32\"," + " \"array\": False," + " \"record-types\": \"\"," + " \"space\": \"isc\"," + " \"encapsulate\": \"invalid%space%name\"" + " } ]" + "}"; + ElementPtr json = Element::fromJSON(config); + + // Use the configuration string to create new option definition. + ConstElementPtr status; + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + ASSERT_TRUE(status); + // Expecting parsing error (error code 1). + checkResult(status, 1); +} + +/// The goal of this test is to verify that the encapsulated +/// option space name can't be specified for the option that +/// comprises an array of data fields. +TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) { + // Configuration string. The encapsulated option space + // name is set to non-empty value and the array flag + // is set. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 100," + " \"type\": \"uint32\"," + " \"array\": True," + " \"record-types\": \"\"," + " \"space\": \"isc\"," + " \"encapsulate\": \"valid-space-name\"" + " } ]" + "}"; + ElementPtr json = Element::fromJSON(config); + + // Use the configuration string to create new option definition. + ConstElementPtr status; + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + ASSERT_TRUE(status); + // Expecting parsing error (error code 1). + checkResult(status, 1); +} + +/// The goal of this test is to verify that the option may not +/// encapsulate option space it belongs to. +TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) { + // Configuration string. Option is set to encapsulate + // option space it belongs to. + std::string config = + "{ \"option-def\": [ {" + " \"name\": \"foo\"," + " \"code\": 100," + " \"type\": \"uint32\"," + " \"array\": False," + " \"record-types\": \"\"," + " \"space\": \"isc\"," + " \"encapsulate\": \"isc\"" + " } ]" + "}"; + ElementPtr json = Element::fromJSON(config); + + // Use the configuration string to create new option definition. + ConstElementPtr status; + EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); + ASSERT_TRUE(status); + // Expecting parsing error (error code 1). + checkResult(status, 1); +} /// The purpose of this test is to verify that it is not allowed /// to override the standard option (that belongs to dhcp6 option @@ -759,7 +889,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { " \"type\": \"string\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"dhcp6\"" + " \"space\": \"dhcp6\"," + " \"encapsulate\": \"\"" " } ]" "}"; ElementPtr json = Element::fromJSON(config); @@ -794,7 +925,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) { " \"type\": \"string\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"dhcp6\"" + " \"space\": \"dhcp6\"," + " \"encapsulate\": \"\"" " } ]" "}"; json = Element::fromJSON(config); @@ -916,7 +1048,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) { " \"type\": \"uint32\"," " \"array\": False," " \"record-types\": \"\"," - " \"space\": \"isc\"" + " \"space\": \"isc\"," + " \"encapsulate\": \"\"" " } ]," "\"subnet6\": [ { " " \"pool\": [ \"2001:db8:1::/80\" ],"