Commit 38aebe95 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac4204fd'

parents 00186aa5 7b46431e
......@@ -22,7 +22,6 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/parsers/dbaccess_parser.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <dhcpsrv/parsers/expiration_config_parser.h>
......@@ -514,6 +513,12 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Remove any existing timers.
TimerMgr::instance()->unregisterTimers();
// Revert any runtime option definitions configured so far and not committed.
LibDHCP::revertRuntimeOptionDefs();
// Let's set empty container in case a user hasn't specified any configuration
// for option definitions. This is equivalent to commiting empty container.
LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet4 structure
// depend on the global values. Also, option values configuration
......@@ -700,6 +705,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Rollback changes as the configuration parsing failed.
if (rollback) {
globalContext().reset(new ParserContext(original_context));
// Revert to original configuration of runtime option definitions
// in the libdhcp++.
LibDHCP::revertRuntimeOptionDefs();
return (answer);
}
......
......@@ -1346,6 +1346,11 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
ASSERT_TRUE(status);
checkResult(status, 0);
// We need to commit option definitions because later in this test we
// will be checking if they get removed when "option-def" parameter
// is removed from a configuration.
LibDHCP::commitRuntimeOptionDefs();
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
ASSERT_TRUE(def);
......@@ -1356,6 +1361,25 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
EXPECT_TRUE(def->getEncapsulatedSpace().empty());
// The copy of the option definition should be available in the libdhcp++.
OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
ASSERT_TRUE(def_libdhcp);
// Both definitions should be held in distinct pointers but they should
// be equal.
EXPECT_TRUE(def_libdhcp != def);
EXPECT_TRUE(*def_libdhcp == *def);
// Let's apply empty configuration. This removes the option definitions
// configuration and should result in removal of the option 100 from the
// libdhcp++.
config = "{ }";
json = Element::fromJSON(config);
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
checkResult(status, 0);
EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
}
// The goal of this test is to check whether an option definition
......@@ -1468,6 +1492,14 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
// The goal of this test is to verify that the duplicated option
// definition is not accepted.
TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
// Preconfigure libdhcp++ with option definitions. The new configuration
// should override it, but when the new configuration fails, it should
// revert to this original configuration.
OptionDefSpaceContainer defs;
OptionDefinitionPtr def(new OptionDefinition("bar", 233, "string"));
defs.addItem(def, "isc");
LibDHCP::setRuntimeOptionDefs(defs);
LibDHCP::commitRuntimeOptionDefs();
// Configuration string. Both option definitions have
// the same code and belong to the same option space.
......@@ -1498,6 +1530,15 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
ASSERT_TRUE(status);
checkResult(status, 1);
EXPECT_TRUE(errorContainsPosition(status, "<string>"));
// The new configuration should have inserted option 100, but
// once configuration failed (on the duplicate option definition)
// the original configuration in libdhcp++ should be reverted.
EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
def = LibDHCP::getRuntimeOptionDef("isc", 233);
ASSERT_TRUE(def);
EXPECT_EQ("bar", def->getName());
EXPECT_EQ(233, def->getCode());
}
// The goal of this test is to verify that the option definition
......
......@@ -1766,6 +1766,101 @@ TEST_F(Dhcpv4SrvTest, matchClassification) {
EXPECT_FALSE(opt3);
}
// Checks if client packets are classified properly using match expressions
// using option names
TEST_F(Dhcpv4SrvTest, matchClassificationOptionName) {
NakedDhcpv4Srv srv(0);
// The router class matches incoming packets with foo in a host-name
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ] }, "
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ "
"{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
" \"subnet\": \"192.0.2.0/24\" } ], "
"\"client-classes\": [ "
"{ \"name\": \"router\", "
" \"test\": \"option[host-name].text == 'foo'\" } ] }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
ASSERT_TRUE(status);
comment_ = config::parseAnswer(rcode_, status);
ASSERT_EQ(0, rcode_);
CfgMgr::instance().commit();
// Create a packet with enough to select the subnet
Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
query->setRemoteAddr(IOAddress("192.0.2.1"));
// Create and add a host-name option to the query
OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
ASSERT_TRUE(hostname);
query->addOption(hostname);
// Classify packets
srv.classifyPacket(query);
// The queey should be in the router class
EXPECT_TRUE(query->inClass("router"));
}
// Checks if client packets are classified properly using match expressions
// using option names and definitions
TEST_F(Dhcpv4SrvTest, matchClassificationOptionDef) {
NakedDhcpv4Srv srv(0);
// The router class matches incoming packets with foo in a defined
// option
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ] }, "
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ "
"{ \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
" \"subnet\": \"192.0.2.0/24\" } ], "
"\"client-classes\": [ "
"{ \"name\": \"router\", "
" \"test\": \"option[my-host-name].text == 'foo'\" } ], "
"\"option-def\": [ {"
" \"name\": \"my-host-name\", "
" \"code\": 250, "
" \"type\": \"string\" } ] }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
ASSERT_TRUE(status);
comment_ = config::parseAnswer(rcode_, status);
ASSERT_EQ(0, rcode_);
CfgMgr::instance().commit();
// Create a packet with enough to select the subnet
Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
query->setRemoteAddr(IOAddress("192.0.2.1"));
// Create and add a my-host-name option to the query
OptionStringPtr hostname(new OptionString(Option::V4, 250, "foo"));
ASSERT_TRUE(hostname);
query->addOption(hostname);
// Classify packets
srv.classifyPacket(query);
// The queey should be in the router class
EXPECT_TRUE(query->inClass("router"));
}
// Checks subnet options have the priority over class options
TEST_F(Dhcpv4SrvTest, subnetClassPriority) {
IfaceMgrTestConfig test_config(true);
......
......@@ -15,6 +15,7 @@
#include <config.h>
#include <cc/data.h>
#include <config/command_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
......@@ -244,6 +245,10 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
}
}
// Finally, we can commit runtime option definitions in libdhcp++. This is
// exception free.
LibDHCP::commitRuntimeOptionDefs();
return (answer);
}
......
......@@ -755,6 +755,12 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
// Remove any existing timers.
TimerMgr::instance()->unregisterTimers();
// Revert any runtime option definitions configured so far and not committed.
LibDHCP::revertRuntimeOptionDefs();
// Let's set empty container in case a user hasn't specified any configuration
// for option definitions. This is equivalent to commiting empty container.
LibDHCP::setRuntimeOptionDefs(OptionDefSpaceContainer());
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet6 structure
// depend on the global values. Also, option values configuration
......@@ -948,6 +954,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
// Rollback changes as the configuration parsing failed.
if (rollback) {
globalContext().reset(new ParserContext(original_context));
// Revert to original configuration of runtime option definitions
// in the libdhcp++.
LibDHCP::revertRuntimeOptionDefs();
return (answer);
}
......
......@@ -1583,6 +1583,12 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// We need to commit option definitions because later in this test we
// will be checking if they get removed when "option-def" parameter
// is removed from a configuration.
LibDHCP::commitRuntimeOptionDefs();
// The option definition should now be available in the CfgMgr.
def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
......@@ -1593,6 +1599,25 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
EXPECT_EQ(100, def->getCode());
EXPECT_FALSE(def->getArrayType());
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
// The copy of the option definition should be available in the libdhcp++.
OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
ASSERT_TRUE(def_libdhcp);
// Both definitions should be held in distinct pointers but they should
// be equal.
EXPECT_TRUE(def_libdhcp != def);
EXPECT_TRUE(*def_libdhcp == *def);
// Let's apply empty configuration. This removes the option definitions
// configuration and should result in removal of the option 100 from the
// libdhcp++.
config = "{ }";
json = Element::fromJSON(config);
ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
checkResult(status, 0);
EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
}
// The goal of this test is to check whether an option definition
......@@ -1702,6 +1727,14 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
// The goal of this test is to verify that the duplicated option
// definition is not accepted.
TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
// Preconfigure libdhcp++ with option definitions. The new configuration
// should override it, but when the new configuration fails, it should
// revert to this original configuration.
OptionDefSpaceContainer defs;
OptionDefinitionPtr def(new OptionDefinition("bar", 233, "string"));
defs.addItem(def, "isc");
LibDHCP::setRuntimeOptionDefs(defs);
LibDHCP::commitRuntimeOptionDefs();
// Configuration string. Both option definitions have
// the same code and belong to the same option space.
......@@ -1732,6 +1765,15 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
ASSERT_TRUE(status);
checkResult(status, 1);
EXPECT_TRUE(errorContainsPosition(status, "<string>"));
// The new configuration should have inserted option 100, but
// once configuration failed (on the duplicate option definition)
// the original configuration in libdhcp++ should be reverted.
EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("isc", 100));
def = LibDHCP::getRuntimeOptionDef("isc", 233);
ASSERT_TRUE(def);
EXPECT_EQ("bar", def->getName());
EXPECT_EQ(233, def->getCode());
}
// The goal of this test is to verify that the option definition
......
......@@ -1805,6 +1805,7 @@ TEST_F(Dhcpv6SrvTest, docsisClientClassification) {
}
// Checks if client packets are classified properly using match expressions.
// Note option names and definitions are used.
TEST_F(Dhcpv6SrvTest, matchClassification) {
IfaceMgrTestConfig test_config(true);
......@@ -1834,7 +1835,7 @@ TEST_F(Dhcpv6SrvTest, matchClassification) {
" \"option-data\": ["
" { \"name\": \"ipv6-forwarding\", "
" \"data\": \"true\" } ], "
" \"test\": \"option[1234].text == 'foo'\" } ] }";
" \"test\": \"option[host-name].text == 'foo'\" } ] }";
ASSERT_NO_THROW(configure(config));
// Create packets with enough to select the subnet
......
......@@ -44,6 +44,7 @@ libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h
libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opaque_data_tuples.h
libkea_dhcp___la_SOURCES += option_space.cc option_space.h
libkea_dhcp___la_SOURCES += option_space_container.h
libkea_dhcp___la_SOURCES += option_string.cc option_string.h
libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
libkea_dhcp___la_SOURCES += pkt.cc pkt.h
......
......@@ -32,6 +32,8 @@
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <list>
using namespace std;
using namespace isc::dhcp;
using namespace isc::util;
......@@ -52,6 +54,10 @@ VendorOptionDefContainers LibDHCP::vendor4_defs_;
VendorOptionDefContainers LibDHCP::vendor6_defs_;
// Static container with option definitions created in runtime.
StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
// Those two vendor classes are used for cable modems:
/// DOCSIS3.0 compatible cable modem
......@@ -194,6 +200,66 @@ LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
return (OptionDefinitionPtr());
}
OptionDefinitionPtr
LibDHCP::getRuntimeOptionDef(const std::string& space, const uint16_t code) {
OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
const OptionDefContainerTypeIndex& index = container->get<1>();
const OptionDefContainerTypeRange& range = index.equal_range(code);
if (range.first != range.second) {
return (*range.first);
}
return (OptionDefinitionPtr());
}
OptionDefinitionPtr
LibDHCP::getRuntimeOptionDef(const std::string& space, const std::string& name) {
OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
const OptionDefContainerNameIndex& index = container->get<2>();
const OptionDefContainerNameRange& range = index.equal_range(name);
if (range.first != range.second) {
return (*range.first);
}
return (OptionDefinitionPtr());
}
OptionDefContainerPtr
LibDHCP::getRuntimeOptionDefs(const std::string& space) {
return (runtime_option_defs_.getValue().getItems(space));
}
void
LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
OptionDefSpaceContainer defs_copy;
std::list<std::string> option_space_names = defs.getOptionSpaceNames();
for (std::list<std::string>::const_iterator name = option_space_names.begin();
name != option_space_names.end(); ++name) {
OptionDefContainerPtr container = defs.getItems(*name);
for (OptionDefContainer::const_iterator def = container->begin();
def != container->end(); ++def) {
OptionDefinitionPtr def_copy(new OptionDefinition(**def));
defs_copy.addItem(def_copy, *name);
}
}
runtime_option_defs_ = defs_copy;
}
void
LibDHCP::clearRuntimeOptionDefs() {
runtime_option_defs_.reset();
}
void
LibDHCP::revertRuntimeOptionDefs() {
runtime_option_defs_.revert();
}
void
LibDHCP::commitRuntimeOptionDefs() {
runtime_option_defs_.commit();
}
bool
LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
if (u == Option::V6) {
......@@ -260,7 +326,14 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
OptionDefContainer option_defs;
if (option_space == "dhcp6") {
option_defs = LibDHCP::getOptionDefs(Option::V6);
} else {
OptionDefContainerPtr option_defs_ptr =
LibDHCP::getRuntimeOptionDefs(option_space);
if (option_defs_ptr) {
option_defs = *option_defs_ptr;
}
}
// @todo Once we implement other option spaces we should add else clause
// here and gather option definitions for them. For now leaving option_defs
// empty will imply creation of generic Option.
......
......@@ -16,8 +16,10 @@
#define LIBDHCP_H
#include <dhcp/option_definition.h>
#include <dhcp/option_space_container.h>
#include <dhcp/pkt6.h>
#include <util/buffer.h>
#include <util/staged_value.h>
#include <iostream>
#include <string>
......@@ -90,6 +92,36 @@ public:
const uint32_t vendor_id,
const std::string& name);
/// @brief Returns runtime (non-standard) option definition by space and
/// option code.
///
/// @param space Option space name.
/// @param code Option code.
///
/// @return Pointer to option definition or NULL if it doesn't exist.
static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space,
const uint16_t code);
/// @brief Returns runtime (non-standard) option definition by space and
/// option name.
///
/// @param space Option space name.
/// @param name Option name.
///
/// @return Pointer to option definition or NULL if it doesn't exist.
static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space,
const std::string& name);
/// @brief Returns runtime (non-standard) option definitions for specified
/// option space name.
///
/// @param space Option space name.
///
/// @return Pointer to the container holding option definitions or NULL.
static OptionDefContainerPtr
getRuntimeOptionDefs(const std::string& space);
/// @brief Check if the specified option is a standard option.
///
/// @param u universe (V4 or V6)
......@@ -256,6 +288,27 @@ public:
const OptionBuffer& buf,
isc::dhcp::OptionCollection& options);
/// @brief Copies option definitions created at runtime.
///
/// Copied option definitions will be used as "runtime" option definitions.
/// A typical use case is to set option definitions specified by the user
/// in the server configuration. These option definitions should be removed
/// or replaced with new option definitions upon reconfiguration.
///
/// @param defs Const reference to a container holding option definitions
/// grouped by option spaces.
static void setRuntimeOptionDefs(const OptionDefSpaceContainer& defs);
/// @brief Removes runtime option definitions.
static void clearRuntimeOptionDefs();
/// @brief Reverts uncommited changes to runtime option definitions.
static void revertRuntimeOptionDefs();
/// @brief Commits runtime option definitions.
static void commitRuntimeOptionDefs();
private:
/// Initialize standard DHCPv4 option definitions.
......@@ -301,6 +354,9 @@ private:
/// Container for v6 vendor option definitions
static VendorOptionDefContainers vendor6_defs_;
/// Container for additional option defnitions created in runtime.
static util::StagedValue<OptionDefSpaceContainer> runtime_option_defs_;
};
}
......
......@@ -220,8 +220,8 @@ Option::toString() {
return (toText(0));
}
std::string
Option::toHexString(const bool include_header) {
std::vector<uint8_t>
Option::toBinary(const bool include_header) {
OutputBuffer buf(len());
try {
// If the option is too long, exception will be thrown. We allow
......@@ -233,12 +233,18 @@ Option::toHexString(const bool include_header) {
" of option " << getType() << ": " << ex.what());
}
const uint8_t* option_data = static_cast<const uint8_t*>(buf.getData());
std::vector<uint8_t> option_vec;
// Assign option data to a vector, with or without option header depending
// on the value of "include_header" flag.
option_vec.assign(option_data + (include_header ? 0 : getHeaderLen()),
option_data + buf.getLength());
std::vector<uint8_t> option_vec(option_data + (include_header ? 0 : getHeaderLen()),
option_data + buf.getLength());
return (option_vec);
}
std::string
Option::toHexString(const bool include_header) {
// Prepare binary version of the option.
std::vector<uint8_t> option_vec = toBinary(include_header);
// Return hexadecimal representation prepended with 0x or empty string
// if option has no payload and the header fields are excluded.
......
......@@ -216,6 +216,15 @@ public:
/// @return string that represents the value of the option.
virtual std::string toString();
/// @brief Returns binary representation of the option.
///
/// @param include_header Boolean flag which indicates if the output should
/// also contain header fields. The default is that it shouldn't include
/// header fields.
///
/// @return Vector holding binary representation of the option.
virtual std::vector<uint8_t> toBinary(const bool include_header = false);
/// @brief Returns string containing hexadecimal representation of option.
///
/// @param include_header Boolean flag which indicates if the output should
......
......@@ -17,6 +17,7 @@
#include <dhcp/option.h>
#include <dhcp/option_data_types.h>
#include <dhcp/option_space_container.h>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
......@@ -766,6 +767,10 @@ typedef OptionDefContainer::nth_index<2>::type OptionDefContainerNameIndex;
typedef std::pair<OptionDefContainerNameIndex::const_iterator,
OptionDefContainerNameIndex::const_iterator> OptionDefContainerNameRange;
typedef OptionSpaceContainer<
OptionDefContainer, OptionDefinitionPtr, std::string
> OptionDefSpaceContainer;
} // namespace isc::dhcp
} // namespace isc
......
......@@ -147,6 +147,60 @@ public:
return (OptionBuffer(opt_data, opt_data + sizeof(opt_data)));
}
/// @brief Create option definitions and store in the container.
///
/// @param spaces_num Number of option spaces to be created.
/// @param defs_num Number of option definitions to be created for
/// each option space.
/// @param [out] defs Container to which option definitions should be
/// added.
static void createRuntimeOptionDefs(const uint16_t spaces_num,
const uint16_t defs_num,