Commit 705e7bb6 authored by Thomas Markwalder's avatar Thomas Markwalder

[master] Remerge with 566

    Merge branch 'master' of gitlab.isc.org:isc-projects/kea
parents 093eb1cf 753aedd5
1560. [func] tmark
1561. [func] tmark
kea-dhcp6 now automatically deletes configuration elements
that have been deleted from configuration backends.
(Gitlab #566,!304, git 2e85376f1b57187b822c662144380e04372cffff)
1560. [bug] fdupont
kea-dhcp4 now permits option code values of 0 and 255 for
options defined in option spaces other than the "dhcp4" space.
(Gitlab #564,!300, git 7a0a0b84d91893f08c0ee6f236daa05bede65166)
1559. [func] fdupont
Added DHCPv6 support to the MySQL Config Backend hook.
(Gitlab #397,!244, git 980091ecd717e41a61f0d7f6808213e450647d8e)
......
......@@ -1499,3 +1499,129 @@ TEST_F(Dhcpv4SrvTest, truncatedVIVSOOption) {
Pkt4Ptr offer = srv.fake_sent_.front();
ASSERT_TRUE(offer);
}
/// 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());
}
......@@ -472,6 +472,9 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
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
......@@ -493,7 +496,7 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
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.
......@@ -502,8 +505,9 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
// 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
......@@ -530,7 +534,7 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
// rather than the dropping the whole packet. We do not have a
// way to log this from here but meh... a PCAP will show it arriving,
// and we know we drop it.
if (opt_len == 0 && opt_type == DHO_HOST_NAME) {
if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
continue;
}
......
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-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
......@@ -35,12 +35,7 @@ Option::factory(Option::Universe u,
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)
......
......@@ -551,12 +551,10 @@ TEST_F(LibDhcpTest, unpackEmptyOption6) {
LibDHCP::commitRuntimeOptionDefs();
// Create the buffer holding the structure of the empty option.
const uint8_t raw_data[] = {
OptionBuffer buf = {
0x04, 0x00, // option code = 1024
0x00, 0x00 // option length = 0
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
OptionCollection options;
......@@ -599,7 +597,7 @@ TEST_F(LibDhcpTest, unpackSubOptions6) {
LibDHCP::commitRuntimeOptionDefs();
// Create the buffer holding the structure of options.
const char raw_data[] = {
OptionBuffer buf = {
// First option starts here.
0x00, 0x01, // option code = 1
0x00, 0x0F, // option length = 15
......@@ -613,7 +611,6 @@ TEST_F(LibDhcpTest, unpackSubOptions6) {
0x00, 0x01, // option length = 1
0x00 // This option carries a single uint8 value and has no sub options.
};
OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
// Parse options.
OptionCollection options;
......@@ -919,12 +916,10 @@ TEST_F(LibDhcpTest, unpackEmptyOption4) {
LibDHCP::commitRuntimeOptionDefs();
// Create the buffer holding the structure of the empty option.
const uint8_t raw_data[] = {
OptionBuffer buf = {
0xFE, // option code = 254
0x00 // option length = 0
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
OptionCollection options;
......@@ -969,7 +964,7 @@ TEST_F(LibDhcpTest, unpackSubOptions4) {
LibDHCP::commitRuntimeOptionDefs();
// Create the buffer holding the structure of options.
const uint8_t raw_data[] = {
OptionBuffer buf = {
// First option starts here.
0x01, // option code = 1
0x0B, // option length = 11
......@@ -984,8 +979,6 @@ TEST_F(LibDhcpTest, unpackSubOptions4) {
0x00 // This option carries a single uint8
// value and has no sub options.
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
OptionCollection options;
......@@ -1016,6 +1009,95 @@ TEST_F(LibDhcpTest, unpackSubOptions4) {
EXPECT_EQ(0x0, option_bar->getValue());
}
// 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.
OptionBuffer buf = {
// 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
};
// 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, buf[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.
ASSERT_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());
}
// Verifies that an Host Name (option 12), will be dropped when empty,
// while subsequent options will still be unpacked.
TEST_F(LibDhcpTest, emptyHostName) {
......
......@@ -70,13 +70,11 @@ TEST_F(OptionTest, v4_basic) {
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[] =
......
......@@ -6,6 +6,7 @@
#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>
......@@ -157,6 +158,29 @@ OptionDefParser::parse(ConstElementPtr option_def) {
<< 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
......
......@@ -7,6 +7,7 @@
#include <config.h>
#include <exceptions/exceptions.h>
#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
......@@ -59,14 +60,10 @@ OptionDataParser::extractCode(ConstElementPtr parent) const {
return (Optional<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)
<< ")");
......@@ -74,7 +71,7 @@ OptionDataParser::extractCode(ConstElementPtr parent) const {
} 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)
<< ")");
......@@ -306,7 +303,6 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
}
}
OptionPtr option;
OptionDescriptor desc(false);
if (!def) {
......@@ -354,6 +350,29 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
}
}
// 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);
......
......@@ -723,7 +723,6 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
TEST_F(ParseConfigTest, badCodeOptionDefTest) {
{
SCOPED_TRACE("negative code");
std::string config =
"{ \"option-def\": [ {"
......@@ -769,6 +768,56 @@ TEST_F(ParseConfigTest, badCodeOptionDefTest) {
int rcode = parseConfiguration(config, false);
ASSERT_NE(0, rcode);
}
{
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.
......@@ -833,6 +882,84 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
cfg.runCfgOptionsTest(family_, config);
}
/// @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\""
" } ], "