Commit 357c297f authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

parents d8a4ee26 c661badf
bind10-1.0.0-beta released on December 20, 2012
532. [func] marcin
Implemented configuration of DHCPv4 option values using
the configuration manager. In order to set values for the
data fields carried by a particular option, the user
specifies a string of hexadecimal digits that is converted
to binary data and stored in the option buffer. A more
user-friendly way of specifying option content is planned.
(Trac #2544, git fed1aab5a0f813c41637807f8c0c5f8830d71942)
531. [func] tomek
b10-dhcp6: Added support for expired leases. Leases for IPv6
addresses that are past their valid lifetime may be recycled, i.e.
......
......@@ -58,6 +58,7 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
......@@ -65,6 +66,5 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp4dir = $(pkgdatadir)
b10_dhcp4_DATA = dhcp4.spec
This diff is collapsed.
......@@ -14,6 +14,7 @@
#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <stdint.h>
#include <string>
#ifndef DHCP4_CONFIG_PARSER_H
......
......@@ -34,6 +34,37 @@
"item_default": 4000
},
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec":
{
"item_name": "single-option-data",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "name",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "code",
"item_type": "integer",
"item_optional": false,
"item_default": 0
},
{ "item_name": "data",
"item_type": "string",
"item_optional": false,
"item_default": ""
} ]
}
},
{ "item_name": "subnet4",
"item_type": "list",
"item_optional": false,
......@@ -80,9 +111,40 @@
"item_optional": false,
"item_default": ""
}
}
]
}
},
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec":
{
"item_name": "single-option-data",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{
"item_name": "name",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{
"item_name": "code",
"item_type": "integer",
"item_optional": false,
"item_default": 0
},
{
"item_name": "data",
"item_type": "string",
"item_optional": false,
"item_default": ""
} ]
}
} ]
}
}
],
"commands": [
......
......@@ -50,6 +50,11 @@ change is committed by the administrator.
A debug message indicating that the IPv4 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
This warning message is issued on an attempt to configure multiple options with the
same option code for the particular subnet. Adding multiple options is uncommon
for DHCPv4, but it is not prohibited.
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
......
......@@ -66,13 +66,13 @@ dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp4_unittests_LDADD = $(GTEST_LDADD)
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
endif
noinst_PROGRAMS = $(TESTS)
......@@ -22,6 +22,7 @@
#include <config/ccsession.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
......@@ -73,9 +74,188 @@ public:
}
~Dhcp4ParserTest() {
resetConfiguration();
delete srv_;
};
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
/// option value. These parameters are: "name", "code" and "data".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
/// @param parameter name of the parameter to be configured with
/// param value.
/// @return configuration string containing custom values of parameters
/// describing an option.
std::string createConfigWithOption(const std::string& param_value,
const std::string& parameter) {
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
params["code"] = "56";
params["data"] = "AB CDEF0105";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "56";
params["data"] = param_value;
}
return (createConfigWithOption(params));
}
/// @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.
std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {";
bool first = true;
typedef std::pair<std::string, std::string> ParamPair;
BOOST_FOREACH(ParamPair param, params) {
if (!first) {
stream << ", ";
} else {
// cppcheck-suppress unreadVariable
first = false;
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
}
}
stream <<
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
return (stream.str());
}
/// @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);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(1, rcode_);
}
/// @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.
/// @param extra_data if true extra data is allowed in an option
/// after tested data.
void testOption(const Subnet::OptionDescriptor& option_desc,
uint16_t expected_code, const uint8_t* expected_data,
size_t expected_data_len,
bool extra_data = false) {
// 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);
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);
}
// Verify that the data is correct. Do not verify suboptions and a header.
const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
expected_data_len));
}
/// @brief Reset configuration database.
///
/// This function resets configuration data base by
/// removing all subnets and option-data. Reset must
/// be performed after each test to make sure that
/// contents of the database do not affect result of
/// subsequent tests.
void resetConfiguration() {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
"\"option-data\": [ ] }";
try {
ElementPtr json = Element::fromJSON(config);
status = configureDhcp4Server(*srv_, json);
} catch (const std::exception& ex) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. The following configuration was used"
<< " to reset database: " << std::endl
<< config << std::endl
<< " and the following error message was returned:"
<< ex.what() << std::endl;
}
// status object must not be NULL
if (!status) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
<< " NULL pointer" << std::endl;
}
comment_ = parseAnswer(rcode_, status);
// returned value should be 0 (configuration success)
if (rcode_ != 0) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
<< " error code " << rcode_ << std::endl;
}
}
Dhcpv4Srv* srv_;
int rcode_;
......@@ -248,6 +428,302 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
TEST_F(Dhcp4ParserTest, optionDataDefaults) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\""
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"01\""
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\""
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
const Subnet::OptionContainer& options = subnet->getOptions();
ASSERT_EQ(2, options.size());
// Get the search index. Index #1 is to search using option code.
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// 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.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(56);
// Expect single option with the code equal to 56.
ASSERT_EQ(1, std::distance(range.first, range.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
range = idx.equal_range(23);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
const uint8_t foo2_expected[] = {
0x01
};
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
// Goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB\""
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"AB CDEF0105\""
" },"
" {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"01\""
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
ASSERT_TRUE(subnet);
const Subnet::OptionContainer& options = subnet->getOptions();
ASSERT_EQ(2, options.size());
// Get the search index. Index #1 is to search using option code.
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// 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.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(56);
// Expect single option with the code equal to 100.
ASSERT_EQ(1, std::distance(range.first, range.second));
const uint8_t foo_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
range = idx.equal_range(23);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
const uint8_t foo2_expected[] = {
0x01
};
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
// Goal of this test is to verify options configuration
// for multiple subnets.
TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
" \"name\": \"option_foo\","
" \"code\": 56,"
" \"data\": \"0102030405060708090A\""
" } ]"
" },"
" {"
" \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
" \"subnet\": \"192.0.3.0/24\", "
" \"option-data\": [ {"
" \"name\": \"option_foo2\","
" \"code\": 23,"
" \"data\": \"FF\""
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
ASSERT_TRUE(subnet1);
const Subnet::OptionContainer& options1 = subnet1->getOptions();
ASSERT_EQ(1, options1.size());
// Get the search index. Index #1 is to search using option code.
const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
// 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.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range1 =
idx1.equal_range(56);
// Expect single option with the code equal to 56.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
const uint8_t foo_expected[] = {
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A
};
// Check if option is valid in terms of code and carried data.
testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
// Test another subnet in the same way.
Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
ASSERT_TRUE(subnet2);
const Subnet::OptionContainer& options2 = subnet2->getOptions();
ASSERT_EQ(1, options2.size());
const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(23);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
const uint8_t foo2_expected[] = { 0xFF };
testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
}
// Verify that empty option name is rejected in the configuration.
TEST_F(Dhcp4ParserTest, optionNameEmpty) {
// Empty option names not allowed.
testInvalidOptionParam("", "name");
}
// Verify that empty option name with spaces is rejected
// in the configuration.
TEST_F(Dhcp4ParserTest, optionNameSpaces) {
// Spaces in option names not allowed.
testInvalidOptionParam("option foo", "name");
}
// Verify that negative option code is rejected in the configuration.
TEST_F(Dhcp4ParserTest, optionCodeNegative) {
// Check negative option code -4. This should fail too.
testInvalidOptionParam("-4", "code");
}
// Verify that out of bounds option code is rejected in the configuration.
TEST_F(Dhcp4ParserTest, optionCodeNonUint8) {
// The valid option codes are uint16_t values so passing
// uint16_t maximum value incremented by 1 should result
// in failure.
testInvalidOptionParam("257", "code");
}
// Verify that zero option code is rejected in the configuration.
TEST_F(Dhcp4ParserTest, optionCodeZero) {
// Option code 0 is reserved and should not be accepted
// by configuration parser.
testInvalidOptionParam("0", "code");
}
// Verify that option data which contains non hexadecimal characters
// is rejected by the configuration.
TEST_F(Dhcp4ParserTest, optionDataInvalidChar) {
// Option code 0 is reserved and should not be accepted
// by configuration parser.
testInvalidOptionParam("01020R", "data");
}
// Verify that option data containins '0x' prefix is rejected
// by the configuration.
TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
// Option code 0 is reserved and should not be accepted
// by configuration parser.
testInvalidOptionParam("0x0102", "da