+1560. [bug] fdupont
+ -- Applied via patch ---
+ kea-dhcp4 now permits option code values of 0 and 255 for
+ defined in option spaces other than the "dhcp4" space.
+ (Gitlab #564,!300, git 7a0a0b84d91893f08c0ee6f236daa05bede65166)
+
Kea 1.5.0 released on Dec 14, 2018
1506. [build] marcin
dhcp4_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
dhcp4_unittests_SOURCES += shared_network_unittest.cc
dhcp4_unittests_SOURCES += host_unittest.cc
+dhcp4_unittests_SOURCES += vendor_opts_unittest.cc
nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
--- /dev/null
+// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// This file is dedicated to testing vendor options. There are several
+// vendor options in DHCPv4:
+//
+// vivso (125) - vendor independent vendor specific option. This is by far the
+// most popular
+// vendor specific (43) - this is probably the second most popular.
+// Unfortunately, its definition is blurry, so there are many
+// similar, but not exact implementations that do things in
+// different ways.
+// vivco (124) - vendor indepdent vendor class option.
+// class identifier (60) - not exactly vendor specific. It's a string, but the
+// content of that string identifies what kind of vendor device
+// this is.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <cc/command_interpreter.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/tests/dhcp4_client.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/dhcp4.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::config;
+using namespace isc::dhcp::test;
+
+/// @brief Class dedicated to testing vendor options in DHCPv4
+///
+/// For the time being it does not provide any additional functionality, but it
+/// groups all vendor related tests under a single name. There were too many
+/// tests in Dhcpv4SrvTest class anyway.
+class VendorOptsTest : public Dhcpv4SrvTest {
+
+};
+
+/// Checks that it's possible to define and use a suboption 0.
+TEST_F(VendorOptsTest, vendorOpsSubOption0) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // Zero Touch provisioning
+ string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"option-def\": ["
+ " {"
+ " \"name\": \"vendor-encapsulated-options\","
+ " \"code\": 43,"
+ " \"type\": \"empty\","
+ " \"encapsulate\": \"ZTP\""
+ " },"
+ " {"
+ " \"name\": \"config-file-name\","
+ " \"code\": 1,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"image-file-name\","
+ " \"code\": 0,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"image-file-type\","
+ " \"code\": 2,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"transfer-mode\","
+ " \"code\": 3,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"all-image-file-name\","
+ " \"code\": 4,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " },"
+ " {"
+ " \"name\": \"http-port\","
+ " \"code\": 5,"
+ " \"space\": \"ZTP\","
+ " \"type\": \"string\""
+ " }"
+ " ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"vendor-encapsulated-options\""
+ " },"
+ " {"
+ " \"name\": \"image-file-name\","
+ " \"data\": \"/dist/images/jinstall-ex.tgz\","
+ " \"space\": \"ZTP\""
+ " }"
+ " ],"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Create a packet with enough to select the subnet and go through
+ // the DISCOVER processing
+ Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+ query->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ query->addOption(clientid);
+ query->setIface("eth1");
+
+ // Create and add a PRL option to the query
+ OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+ DHO_DHCP_PARAMETER_REQUEST_LIST));
+ ASSERT_TRUE(prl);
+ prl->addValue(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ prl->addValue(DHO_VENDOR_CLASS_IDENTIFIER);
+ query->addOption(prl);
+
+ srv.classifyPacket(query);
+ ASSERT_NO_THROW(srv.deferredUnpack(query));
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(query);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Processing should add a vendor-encapsulated-options (code 43)
+ OptionPtr opt = offer->getOption(DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ const OptionCollection& opts = opt->getOptions();
+ ASSERT_EQ(1, opts.size());
+ OptionPtr sopt = opts.begin()->second;
+ ASSERT_TRUE(sopt);
+ EXPECT_EQ(0, sopt->getType());
+
+ // Check suboption 0 content.
+ OptionStringPtr sopt0 =
+ boost::dynamic_pointer_cast<OptionString>(sopt);
+ ASSERT_TRUE(sopt0);
+ EXPECT_EQ("/dist/images/jinstall-ex.tgz", sopt0->getValue());
+}
size_t offset = 0;
size_t last_offset = 0;
+ // Special case when option_space is dhcp4.
+ bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);
+
// Get the list of standard option definitions.
const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
// Runtime option definitions for non standard option space and if
uint8_t opt_type = buf[offset++];
// DHO_END is a special, one octet long option
- if (opt_type == DHO_END) {
+ if (space_is_dhcp4 && (opt_type == DHO_END)) {
// just return. Don't need to add DHO_END option
// Don't return offset because it makes this condition
// and partial parsing impossible to recognize.
// DHO_PAD is just a padding after DHO_END. Let's continue parsing
// in case we receive a message without DHO_END.
- if (opt_type == DHO_PAD)
+ if (space_is_dhcp4 && (opt_type == DHO_PAD)) {
continue;
+ }
if (offset + 1 > buf.size()) {
// We peeked at the option header of the next option, but
Option::Option(Universe u, uint16_t type)
:universe_(u), type_(type) {
-
- // END option (type 255 is forbidden as well)
- if ((u == V4) && ((type == 0) || (type > 254))) {
- isc_throw(BadValue, "Can't create V4 option of type "
- << type << ", V4 options are in range 1..254");
- }
+ check();
}
Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText());
}
+// Verifies that options 0 (PAD) and 255 (END) are handled as PAD and END
+// in and only in the dhcp4 space.
+TEST_F(LibDhcpTest, unpackPadEnd) {
+ // Create option definition for the container.
+ OptionDefinitionPtr opt_def(new OptionDefinition("container", 200,
+ "empty", "my-space"));
+ // Create option definition for option 0.
+ OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, "uint8"));
+
+ // Create option definition for option 255.
+ OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, "uint8"));
+
+ // Create option definition for another option.
+ OptionDefinitionPtr opt_def2(new OptionDefinition("my-option", 1,
+ "string"));
+
+ // Register created option definitions as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def, DHCP4_OPTION_SPACE));
+ ASSERT_NO_THROW(defs.addItem(opt_def0, "my-space"));
+ ASSERT_NO_THROW(defs.addItem(opt_def255, "my-space"));
+ ASSERT_NO_THROW(defs.addItem(opt_def2, "my-space"));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of options.
+ const uint8_t raw_data[] = {
+ // Add a PAD
+ 0x00, // option code = 0 (PAD)
+ // Container option starts here.
+ 0xc8, // option code = 200 (container)
+ 0x0b, // option length = 11
+ // Suboption 0.
+ 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0
+ // Suboption 255.
+ 0xff, 0x01, 0xff, // code = 255, length = 1, content = 255
+ // Suboption 1.
+ 0x01, 0x03, 0x66, 0x6f, 0x6f, // code = 1, length = 2, content = "foo"
+ // END
+ 0xff,
+ // Extra bytes at tail.
+ 0x01, 0x02, 0x03, 0x04
+ };
+ size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
+ OptionBuffer buf(raw_data, raw_data + raw_data_len);
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ size_t offset = 0;
+ ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE,
+ options, deferred));
+
+ // Returned offset should point to the END.
+ EXPECT_EQ(0xff, raw_data[offset]);
+
+ // There should be one top level option.
+ ASSERT_EQ(1, options.size());
+
+ // Get it.
+ OptionPtr option = options.begin()->second;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(200, option->getType());
+
+ // There should be 3 suboptions.
+ EXPECT_EQ(3, option->getOptions().size());
+
+ // Get suboption 0.
+ boost::shared_ptr<OptionInt<uint8_t> > sub0 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (option->getOption(0));
+ ASSERT_TRUE(sub0);
+ EXPECT_EQ(0, sub0->getType());
+ EXPECT_EQ(0, sub0->getValue());
+
+ // Get suboption 255.
+ boost::shared_ptr<OptionInt<uint8_t> > sub255 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (option->getOption(255));
+ ASSERT_TRUE(sub255);
+ EXPECT_EQ(255, sub255->getType());
+ EXPECT_EQ(255, sub255->getValue());
+
+ // Get suboption 1.
+ boost::shared_ptr<OptionString> sub =
+ boost::dynamic_pointer_cast<OptionString>(option->getOption(1));
+ ASSERT_TRUE(sub);
+ EXPECT_EQ(1, sub->getType());
+ EXPECT_EQ("foo", sub->getValue());
+}
+
} // end of anonymous space
EXPECT_NO_THROW(opt.reset());
// V4 options have type 0...255
- EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), BadValue);
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), OutOfRange);
- // 0 is a special PAD option
- EXPECT_THROW(opt.reset(new Option(Option::V4, 0)), BadValue);
-
- // 255 is a special END option
- EXPECT_THROW(opt.reset(new Option(Option::V4, 255)), BadValue);
+ // 0 (PAD) and 255 (END) are no longer forbidden
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 0)));
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 255)));
}
const uint8_t dummyPayload[] =
// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <config.h>
+// License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include <config.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_option.h>
<< getPosition("space", option_def) << ")");
}
+ // Protect against definition of options 0 (PAD) or 255 (END)
+ // in (and only in) the dhcp4 space.
+ if (space == DHCP4_OPTION_SPACE) {
+ if (code == DHO_PAD) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved for PAD ("
+ << getPosition("code", option_def) << ")");
+ } else if (code == DHO_END) {
+ isc_throw(DhcpConfigError, "invalid option code '255': "
+ << "reserved for END ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
+ // For dhcp6 space the value 0 is reserved.
+ if (space == DHCP6_OPTION_SPACE) {
+ if (code == 0) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved value ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
// Create option definition.
OptionDefinitionPtr def;
// We need to check if user has set encapsulated option space
#include <exceptions/exceptions.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp4.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/cfgmgr.h>
return (OptionalValue<uint32_t>());
}
- if (code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero "
- "(" << getPosition("code", parent) << ")");
-
- } else if (address_family_ == AF_INET &&
- code > std::numeric_limits<uint8_t>::max()) {
+ if (address_family_ == AF_INET &&
+ code > std::numeric_limits<uint8_t>::max()) {
isc_throw(DhcpConfigError, "invalid option code '" << code
- << "', it must not be greater than '"
+ << "', it must not be greater than '"
<< static_cast<int>(std::numeric_limits<uint8_t>::max())
<< "' (" << getPosition("code", parent)
<< ")");
} else if (address_family_ == AF_INET6 &&
code > std::numeric_limits<uint16_t>::max()) {
isc_throw(DhcpConfigError, "invalid option code '" << code
- << "', it must not exceed '"
+ << "', it must not exceed '"
<< std::numeric_limits<uint16_t>::max()
<< "' (" << getPosition("code", parent)
<< ")");
}
}
- OptionPtr option;
OptionDescriptor desc(false);
if (!def) {
}
}
+ // Check PAD and END in (and only in) dhcp4 space.
+ if (space_param == DHCP4_OPTION_SPACE) {
+ if (desc.option_->getType() == DHO_PAD) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved for PAD ("
+ << option_data->getPosition() << ")");
+ } else if (desc.option_->getType() == DHO_END) {
+ isc_throw(DhcpConfigError, "invalid option code '255': "
+ << "reserved for END ("
+ << option_data->getPosition() << ")");
+ }
+ }
+
+ // For dhcp6 space the value 0 is reserved.
+ if (space_param == DHCP6_OPTION_SPACE) {
+ if (desc.option_->getType() == 0) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved value ("
+ << option_data->getPosition() << ")");
+ }
+ }
+
// Add user context
if (user_context) {
desc.setContext(user_context);
cfg.runCfgOptionsTest(family_, config);
}
+/// @brief Check parsing of option definitions using invalid code fails.
+TEST_F(ParseConfigTest, badCodeOptionDefTest) {
+
+ {
+ SCOPED_TRACE("conflict with PAD");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"zero\","
+ " \"code\": 0,"
+ " \"type\": \"ip-address\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with END");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"max\","
+ " \"code\": 255,"
+ " \"type\": \"ip-address\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with reserved");
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"zero\","
+ " \"code\": 0,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+}
+
/// @brief Check parsing of option definitions using invalid space fails.
TEST_F(ParseConfigTest, badSpaceOptionDefTest) {
cfg.runCfgOptionsTest(family_, expected);
}
+/// @brief Check parsing of options with code 0.
+TEST_F(ParseConfigTest, optionDataTest0) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00000, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of options with code 255.
+TEST_F(ParseConfigTest, optionDataTest255) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 255,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 255,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 255);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00255, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
/// @brief Check parsing of unknown options fails.
TEST_F(ParseConfigTest, unknownOptionDataTest) {