2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

[3033] Added DHCP-DDNS configuration paramater parsing to b10-dhcp4

Added configuration paramters to dhcp4 and its spec file to support DHCP-DDNS.
Created new classes D2ClientMgr, D2ClientConfig, and D2CientConfigParser in the
libdhcpsrv.   The new parameters are parsed, validated, and stored  but do
not yet affect behavior.  That will be implemented as a seperate ticket.
This commit is contained in:
Thomas Markwalder
2014-01-06 10:56:59 -05:00
parent be25b97a4c
commit d508d4ac19
22 changed files with 1638 additions and 16 deletions

View File

@@ -1365,6 +1365,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp4/tests/Makefile
src/bin/dhcp4/tests/marker_file.h
src/bin/dhcp4/tests/test_data_files_config.h
src/bin/dhcp4/tests/test_libraries.h
src/bin/dhcp6/Makefile
src/bin/dhcp6/spec_config.h.pre

View File

@@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
parser = new HooksLibrariesParser(config_id);
} else if (config_id.compare("echo-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
} else if (config_id.compare("dhcp-ddns") == 0) {
parser = new D2ClientConfigParser(config_id);
} else {
isc_throw(NotImplemented,
"Parser error: Global configuration parameter not supported: "
@@ -448,7 +450,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Some of the parsers alter the state of the system in a way that can't
// easily be undone. (Or alter it in a way such that undoing the change has
// the same risk of failure as doing the change.)
ParserPtr hooks_parser_;
ParserPtr hooks_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
@@ -489,7 +491,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// Executing commit will alter currently-loaded hooks
// libraries. Check if the supplied libraries are valid,
// but defer the commit until everything else has committed.
hooks_parser_ = parser;
hooks_parser = parser;
parser->build(config_pair.second);
} else {
// Those parsers should be started before other
@@ -557,8 +559,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
// This occurs last as if it succeeds, there is no easy way
// revert it. As a result, the failure to commit a subsequent
// change causes problems when trying to roll back.
if (hooks_parser_) {
hooks_parser_->commit();
if (hooks_parser) {
hooks_parser->commit();
}
}
catch (const isc::Exception& ex) {

View File

@@ -292,7 +292,110 @@
}
} ]
}
}
},
{ "item_name": "dhcp-ddns",
"item_type": "map",
"item_optional": false,
"item_default": {"enable-updates": false},
"map_item_spec": [
{
"item_name": "enable-updates",
"item_type": "boolean",
"item_optional": false,
"item_default": False,
"item_description" : "Enables DDNS update processing"
},
{
"item_name": "server_ip",
"item_type": "string",
"item_optional": true,
"item_default": "127.0.0.1",
"item_description" : "IP address of b10-dhcp-ddns"
},
{
"item_name": "server_port",
"item_type": "integer",
"item_optional": true,
"item_default": 5301,
"item_description" : "port number of b10-dhcp-ddns"
},
{
"item_name": "ncr_protocol",
"item_type": "string",
"item_optional": true,
"item_default": "UDP",
"item_description" : "Socket protocol to use with b10-dhcp-ddns"
},
{
"item_name": "ncr_format",
"item_type": "string",
"item_optional": true,
"item_default": "JSON",
"item_description" : "Format of the update request packet"
},
{
"item_name": "remove-on-renew",
"item_type": "boolean",
"item_optional": true,
"item_default": false,
"item_description": "Should server request a DNS Remove, before a DNS Update on renewals"
},
{
"item_name": "always-include-fqdn",
"item_type": "boolean",
"item_optional": true,
"item_default": False,
"item_description": "Should server always include the FQDN option in its response"
},
{
"item_name": "allow-client-update",
"item_type": "boolean",
"item_optional": true,
"item_default": False,
"item_description": "Enable AAAA RR update delegation to the client"
},
{
"item_name": "override-no-update",
"item_type": "boolean",
"item_optional": true,
"item_default": false,
"item_description": "Do update, even if client requested no updates with N flag"
},
{
"item_name": "override-client-update",
"item_type": "boolean",
"item_optional": true,
"item_default": true,
"item_description": "Server performs an update even if client requested delegation"
},
{
"item_name": "replace-client-name",
"item_type": "boolean",
"item_optional": true,
"item_default": false,
"item_description": "Should server replace the domain-name supplied by the client"
},
{
"item_name": "generated-prefix",
"item_type": "string",
"item_optional": true,
"item_default": "myhost",
"item_description": "Prefix to use when generating the client's name"
},
{
"item_name": "qualifying-suffix",
"item_type": "string",
"item_optional": true,
"item_default": "example.com",
"item_description": "Fully qualified domain-name suffix if partial name provided by client"
},
]
},
],
"commands": [
{

View File

@@ -30,6 +30,7 @@
#include "marker_file.h"
#include "test_libraries.h"
#include "test_data_files_config.h"
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
@@ -50,6 +51,22 @@ using namespace std;
namespace {
/// @brief Prepends the given name with the DHCP4 source directory
///
/// @param name file name of the desired file
/// @return string containing the absolute path of the file in the DHCP source
/// directory.
std::string specfile(const std::string& name) {
return (std::string(DHCP4_SRC_DIR) + "/" + name);
}
/// @brief Tests that the spec file is valid.
/// Verifies that the BIND10 DHCP-DDNS configuration specification file
// is valid.
TEST(Dhcp4SpecTest, basicSpec) {
ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
}
class Dhcp4ParserTest : public ::testing::Test {
public:
Dhcp4ParserTest()
@@ -321,6 +338,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
"\"dhcp-ddns\": { \"enable-updates\" : false }, "
"\"option-def\": [ ], "
"\"option-data\": [ ] }";
static_cast<void>(executeConfiguration(config,
@@ -2299,6 +2317,117 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
}
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
TEST_F(Dhcp4ParserTest, d2ClientConfig) {
ConstElementPtr status;
// Verify that the D2 configuraiton can be fetched and is set to disabled.
D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
EXPECT_FALSE(d2_client_config->getEnableUpdates());
// Verify that the convenience method agrees.
ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
string config_str = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
" \"dhcp-ddns\" : {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" },"
"\"valid-lifetime\": 4000 }";
// Convert the JSON string to configuration elements.
ElementPtr config;
ASSERT_NO_THROW(config = Element::fromJSON(config_str));
// Pass the configuration in for parsing.
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
// check if returned status is OK
checkResult(status, 0);
// Verify that DHCP-DDNS updating is enabled.
EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
// Verify that the D2 configuration can be retrieved.
d2_client_config = CfgMgr::instance().getD2ClientConfig();
ASSERT_TRUE(d2_client_config);
// Verify that the configuration values are correct.
EXPECT_TRUE(d2_client_config->getEnableUpdates());
EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
EXPECT_EQ(5301, d2_client_config->getServerPort());
EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
EXPECT_TRUE(d2_client_config->getAllowClientUpdate());
EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
EXPECT_TRUE(d2_client_config->getReplaceClientName());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
}
// This test checks the ability of the server to handle a configuration
// containing an invalid dhcp-ddns (D2ClientConfig) entry.
TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
ConstElementPtr status;
// Configuration string with an invalid D2 client config,
// "server-ip" is missing.
string config_str = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" } ],"
" \"dhcp-ddns\" : {"
" \"enable-updates\" : true, "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" },"
"\"valid-lifetime\": 4000 }";
// Convert the JSON string to configuration elements.
ElementPtr config;
ASSERT_NO_THROW(config = Element::fromJSON(config_str));
// Configuration should not throw, but should fail.
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
// check if returned status is failed.
checkResult(status, 1);
// Verify that the D2 configuraiton can be fetched and is set to disabled.
D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
EXPECT_FALSE(d2_client_config->getEnableUpdates());
// Verify that the convenience method agrees.
ASSERT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file
/// can find it reliably.
#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4"

View File

@@ -18,6 +18,32 @@
namespace isc {
namespace dhcp_ddns {
NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
if (protocol_str == "UDP") {
return NCR_UDP;
}
if (protocol_str == "TCP") {
return NCR_TCP;
}
isc_throw(BadValue, "Invalid NameChangeRequest protocol:" << protocol_str);
}
std::string ncrProtocolToString(NameChangeProtocol protocol) {
switch (protocol) {
case NCR_UDP:
return ("UDP");
case NCR_TCP:
return ("TCP");
default:
break;
}
return ("UNKNOWN");
}
//************************** NameChangeListener ***************************
NameChangeListener::NameChangeListener(RequestReceiveHandler&

View File

@@ -66,6 +66,31 @@
namespace isc {
namespace dhcp_ddns {
/// @brief Defines the list of socket protocols supported.
enum NameChangeProtocol {
NCR_UDP,
NCR_TCP
};
/// @brief Function which converts labels to NameChangeProtocol enum values.
///
/// @param protocol_str text to convert to an enum.
/// Valid string values: "UDP", "TCP"
///
/// @return NameChangeProtocol value which maps to the given string.
///
/// @throw isc::BadValue if given a string value which does not map to an
/// enum value.
extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
/// @brief Function which converts NameChangeProtocol enums to text labels.
///
/// @param protocol enum value to convert to label
///
/// @return std:string containing the text label if the value is valid, or
/// "UNKNOWN" if not.
extern std::string ncrProtocolToString(NameChangeProtocol protocol);
/// @brief Exception thrown if an NcrListenerError encounters a general error.
class NcrListenerError : public isc::Exception {
public:

View File

@@ -26,6 +26,22 @@
namespace isc {
namespace dhcp_ddns {
NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
if (fmt_str == "JSON") {
return FMT_JSON;
}
isc_throw(BadValue, "Invalid NameChangeRequest format:" << fmt_str);
}
std::string ncrFormatToString(NameChangeFormat format) {
if (format == FMT_JSON) {
return ("JSON");
}
return ("UNKNOWN");
}
/********************************* D2Dhcid ************************************/

View File

@@ -70,6 +70,25 @@ enum NameChangeFormat {
FMT_JSON
};
/// @brief Function which converts labels to NameChangeFormat enum values.
///
/// @param format_str text to convert to an enum.
/// Valid string values: "JSON"
///
/// @return NameChangeFormat value which maps to the given string.
///
/// @throw isc::BadValue if given a string value which does not map to an
/// enum value.
extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str);
/// @brief Function which converts NameChangeFormat enums to text labels.
///
/// @param format enum value to convert to label
///
/// @return std:string containing the text label if the value is valid, or
/// "UNKNOWN" if not.
extern std::string ncrFormatToString(NameChangeFormat format);
/// @brief Container class for handling the DHCID value within a
/// NameChangeRequest. It provides conversion to and from string for JSON
/// formatting, but stores the data internally as unsigned bytes.

View File

@@ -12,7 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp_ddns/ncr_io.h>
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
#include <util/time_utilities.h>
@@ -608,5 +608,23 @@ TEST(NameChangeRequestTest, ipAddresses) {
ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
}
/// @brief Tests conversion of NameChangeFormat between enum and strings.
TEST(NameChangeFormatTest, formatEnumConversion){
ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON);
ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue);
ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON");
}
/// @brief Tests conversion of NameChangeProtocol between enum and strings.
TEST(NameChangeProtocolTest, protocolEnumConversion){
ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP);
ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue);
ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP");
ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP");
}
} // end of anonymous namespace

View File

@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
libb10_dhcpsrv_la_SOURCES += d2_client.cc d2_client.h
libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
@@ -64,6 +65,7 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la

View File

@@ -348,9 +348,26 @@ CfgMgr::getUnicast(const std::string& iface) const {
return (&(*addr).second);
}
void
CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
d2_client_mgr_.setD2ClientConfig(new_config);
}
bool
CfgMgr::isDhcpDdnsEnabled() {
return (d2_client_mgr_.isDhcpDdnsEnabled());
}
const D2ClientConfigPtr&
CfgMgr::getD2ClientConfig() const {
return (d2_client_mgr_.getD2ClientConfig());
}
CfgMgr::CfgMgr()
: datadir_(DHCP_DATA_DIR),
all_ifaces_active_(false), echo_v4_client_id_(true) {
all_ifaces_active_(false), echo_v4_client_id_(true),
d2_client_mgr_() {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am

View File

@@ -19,6 +19,7 @@
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/d2_client.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
@@ -332,6 +333,24 @@ public:
return (echo_v4_client_id_);
}
/// @brief Updates the DHCP-DDNS client configuration to the given value.
///
/// @param new_config pointer to the new client configuration.
///
/// @throw Underlying method(s) will throw D2ClientError if given an empty
/// pointer.
void setD2ClientConfig(D2ClientConfigPtr& new_config);
/// @param Convenience method for checking if DHCP-DDNS updates are enabled.
///
/// @return True if the D2 configuration is enabled.
bool isDhcpDdnsEnabled();
/// @brief Fetches the DHCP-DDNS configuration pointer.
///
/// @return a reference to the current configuration pointer.
const D2ClientConfigPtr& getD2ClientConfig() const;
protected:
/// @brief Protected constructor.
@@ -411,6 +430,9 @@ private:
/// Indicates whether v4 server should send back client-id
bool echo_v4_client_id_;
/// @brief Manages the DHCP-DDNS client and its configuration.
D2ClientMgr d2_client_mgr_;
};
} // namespace isc::dhcp

View File

@@ -0,0 +1,187 @@
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcpsrv/d2_client.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <string>
using namespace std;
namespace isc {
namespace dhcp {
D2ClientConfig::D2ClientConfig(const bool enable_updates,
const isc::asiolink::IOAddress& server_ip,
const size_t server_port,
const dhcp_ddns::
NameChangeProtocol& ncr_protocol,
const dhcp_ddns::
NameChangeFormat& ncr_format,
const bool remove_on_renew,
const bool always_include_fqdn,
const bool allow_client_update,
const bool override_no_update,
const bool override_client_update,
const bool replace_client_name,
const std::string& generated_prefix,
const std::string& qualifying_suffix)
: enable_updates_(enable_updates),
server_ip_(server_ip.getAddress()),
server_port_(server_port),
ncr_protocol_(ncr_protocol),
ncr_format_(ncr_format),
remove_on_renew_(remove_on_renew),
always_include_fqdn_(always_include_fqdn),
allow_client_update_(allow_client_update),
override_no_update_(override_no_update),
override_client_update_(override_client_update),
replace_client_name_(replace_client_name),
generated_prefix_(generated_prefix),
qualifying_suffix_(qualifying_suffix) {
if (ncr_format_ != dhcp_ddns::FMT_JSON) {
isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
<< dhcp_ddns::ncrFormatToString(ncr_format)
<< " is not yet supported");
}
if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
<< dhcp_ddns::ncrProtocolToString(ncr_protocol)
<< " is not yet supported");
}
// @todo perhaps more validation we should do yet?
// Are there any invalid combinations of options we need to test against?
// For instance are allow_client_update and override_client_update mutually
// exclusive?
// Also do we care about validating contents if it's disabled?
}
D2ClientConfig::D2ClientConfig()
: enable_updates_(false),
server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
server_port_(0),
ncr_protocol_(dhcp_ddns::NCR_UDP),
ncr_format_(dhcp_ddns::FMT_JSON),
remove_on_renew_(false),
always_include_fqdn_(false),
allow_client_update_(false),
override_no_update_(false),
override_client_update_(false),
replace_client_name_(false),
generated_prefix_(""),
qualifying_suffix_("") {
}
D2ClientConfig::~D2ClientConfig(){};
bool
D2ClientConfig::operator == (const D2ClientConfig& other) const {
return ((enable_updates_ == other.enable_updates_) &&
(server_ip_ == other.server_ip_) &&
(server_port_ == other.server_port_) &&
(ncr_protocol_ == other.ncr_protocol_) &&
(ncr_format_ == other.ncr_format_) &&
(remove_on_renew_ == other.remove_on_renew_) &&
(always_include_fqdn_ == other.always_include_fqdn_) &&
(allow_client_update_ == other.allow_client_update_) &&
(override_no_update_ == other.override_no_update_) &&
(override_client_update_ == other.override_client_update_) &&
(replace_client_name_ == other.replace_client_name_) &&
(generated_prefix_ == other.generated_prefix_) &&
(qualifying_suffix_ == other.qualifying_suffix_));
}
bool
D2ClientConfig::operator != (const D2ClientConfig& other) const {
return (!(*this == other));
}
std::string
D2ClientConfig::toText() const {
std::ostringstream stream;
stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
if (enable_updates_) {
stream << ", server_ip: " << server_ip_.toText()
<< ", server_port: " << server_port_
<< ", ncr_protocol: " << ncr_protocol_
<< ", ncr_format: " << ncr_format_
<< ", remove_on_renew: " << (remove_on_renew_ ? "yes" : "no")
<< ", always_include_fqdn: " << (always_include_fqdn_ ?
"yes" : "no")
<< ", allow_client_update: " << (allow_client_update_ ?
"yes" : "no")
<< ", override_no_update: " << (override_no_update_ ?
"yes" : "no")
<< ", override_client_update: " << (override_client_update_ ?
"yes" : "no")
<< ", replace_client_name: " << (replace_client_name_ ?
"yes" : "no")
<< ", generated_prefix: [" << generated_prefix_ << "]"
<< ", qualifying_suffix: [" << qualifying_suffix_ << "]";
}
return (stream.str());
}
std::ostream&
operator<<(std::ostream& os, const D2ClientConfig& config) {
os << config.toText();
return (os);
}
D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()) {
// Default contstructor initializes with a disabled config.
}
D2ClientMgr::~D2ClientMgr(){
}
void
D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
if (!new_config) {
isc_throw(D2ClientError,
"D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
}
// @todo When NameChangeSender is integrated, we will need to handle these
// scenarios:
// 1. D2 was enabled but now it is disabled
// - destroy the sender, flush any queued
// 2. D2 is still enabled but server params have changed
// - preserve any queued, reconnect based on sender params
// 3. D2 was was disabled now it is enabled.
// - create sender
//
// For now we just update the configuration.
d2_client_config_ = new_config;
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
.arg(!isDhcpDdnsEnabled() ? "DHCP-DDNS updates disabled" :
"DHCP_DDNS updates enabled");
}
bool
D2ClientMgr::isDhcpDdnsEnabled() {
return (d2_client_config_->getEnableUpdates());
}
const D2ClientConfigPtr&
D2ClientMgr::getD2ClientConfig() const {
return (d2_client_config_);
}
}; // namespace dhcp
}; // namespace isc

280
src/lib/dhcpsrv/d2_client.h Normal file
View File

@@ -0,0 +1,280 @@
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef D2_CLIENT_H
#define D2_CLIENT_H
/// @file d2_client.h Defines the D2ClientConfig and D2ClientMgr classes.
/// This file defines the classes Kea uses to act as a client of the b10-
/// dhcp-ddns module (aka D2).
///
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_io.h>
#include <exceptions/exceptions.h>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <string>
#include <vector>
namespace isc {
namespace dhcp {
/// An exception that is thrown if an error occurs while configuring
/// the D2 DHCP DDNS client.
class D2ClientError : public isc::Exception {
public:
/// @brief constructor
///
/// @param file name of the file, where exception occurred
/// @param line line of the file, where exception occurred
/// @param what text description of the issue that caused exception
D2ClientError(const char* file, size_t line, const char* what)
: isc::Exception(file, line, what) {}
};
/// @brief Acts as a storage vault for D2 client configuration
///
/// A simple container class for storing and retrieving the configuration
/// parameters associated with DHCP-DDNS and acting as a client of D2.
/// Instances of this class may be constructed through configuration parsing.
///
class D2ClientConfig {
public:
/// @brief Constructor
///
/// @param enable_updates Enables DHCP-DDNS updates
/// @param server_ip IP address of the b10-dhcp-ddns server
/// @param server_port IP port of the b10-dhcp-ddns server
/// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
/// Currently only UDP is supported.
/// @param ncr_format Format of the b10-dhcp-ddns requests.
/// Currently only JSON format is supported.
/// @param remove_on_renew Enables DNS Removes when renewing a lease
/// If true, Kea should request an explicit DNS remove prior to requesting
/// a DNS update when renewing a lease.
/// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
/// is unnecessary).
/// @param always_include_fdqn Enables always including the FQDN option in
/// DHCP responses.
/// @param allow_client_update Enables delegation of updates to clients
/// @param override_no_update Enables updates, even if clients request no
/// updates.
/// @param override_client_update Perform updates, even if client requested
/// delegation.
/// @param replace_client_name enables replacement of the domain-name
/// supplied by the client with a generated name.
/// @param generated_prefix Prefix to use when generating domain-names.
/// @param qualifying_suffix Suffix to use to qualify partial domain-names.
///
/// @throw D2ClientError if given an invalid protocol or format.
D2ClientConfig(const bool enable_updates_,
const isc::asiolink::IOAddress& server_ip_,
const size_t server_port_,
const dhcp_ddns::NameChangeProtocol& ncr_protocol_,
const dhcp_ddns::NameChangeFormat& ncr_format_,
const bool remove_on_renew_,
const bool always_include_fqdn_,
const bool allow_client_update_,
const bool override_no_update_,
const bool override_client_update_,
const bool replace_client_name_,
const std::string& generated_prefix_,
const std::string& qualifying_suffix_);
/// @brief Default constructor
/// The default constructor creates an instance that has updates disabled.
D2ClientConfig();
/// @brief Destructor
virtual ~D2ClientConfig();
/// @brief Return whether or not DHCP-DDNS updating is enabled.
bool getEnableUpdates() const {
return(enable_updates_);
}
/// @brief Return the IP address of b10-dhcp-ddns.
const isc::asiolink::IOAddress& getServerIp() const {
return(server_ip_);
}
/// @brief Return the IP port of b10-dhcp-ddns.
size_t getServerPort() const {
return(server_port_);
}
/// @brief Return the socket protocol to use with b10-dhcp-ddns.
const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
return(ncr_protocol_);
}
/// @brief Return the b10-dhcp-ddns request format.
const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
return(ncr_format_);
}
/// @brief Return whether or not removes should be sent for lease renewals.
bool getRemoveOnRenew() const {
return(remove_on_renew_);
}
/// @brief Return whether or not FQDN is always included in DHCP responses.
bool getAlwaysIncludeFqdn() const {
return(always_include_fqdn_);
}
/// @brief Return whether or not updates can be delegated to clients.
bool getAllowClientUpdate() const {
return(allow_client_update_);
}
/// @brief Return if updates are done even if clients request no updates.
bool getOverrideNoUpdate() const {
return(override_no_update_);
}
/// @brief Return if updates are done even when clients request delegation.
bool getOverrideClientUpdate() const {
return(override_client_update_);
}
/// @brief Return whether or not client's domain-name is always replaced.
bool getReplaceClientName() const {
return(replace_client_name_);
}
/// @brief Return the prefix to use when generating domain-names.
const std::string& getGeneratedPrefix() const {
return(generated_prefix_);
}
/// @brief Return the suffix to use to qualify partial domain-names.
const std::string& getQualifyingSuffix() const {
return(qualifying_suffix_);
}
/// @brief Compares two D2ClientConfigs for equality
bool operator == (const D2ClientConfig& other) const;
/// @brief Compares two D2ClientConfigs for inequality
bool operator != (const D2ClientConfig& other) const;
/// @brief Generates a string representation of the class contents.
std::string toText() const;
private:
/// @brief Indicates whether or not DHCP DDNS updating is enabled.
bool enable_updates_;
/// @brief IP address of the b10-dhcp-ddns server.
isc::asiolink::IOAddress server_ip_;
/// @brief IP port of the b10-dhcp-ddns server.
size_t server_port_;
/// @brief The socket protocol to use with b10-dhcp-ddns.
/// Currently only UPD is supported.
dhcp_ddns::NameChangeProtocol ncr_protocol_;
/// @brief Format of the b10-dhcp-ddns requests.
/// Currently only JSON format is supported.
dhcp_ddns::NameChangeFormat ncr_format_;
/// @brief Should Kea request a DNS Remove when renewing a lease.
/// If true, Kea should request an explicit DNS remove prior to requesting
/// a DNS update when renewing a lease.
/// (Note: b10-dhcp-ddns is implemented per RFC 4703 and such a remove
/// is unnecessary).
bool remove_on_renew_;
/// @brief Should Kea always include the FQDN option in its response.
bool always_include_fqdn_;
/// @brief Should Kea permit the client to do updates.
bool allow_client_update_;
/// @brief Should Kea perform updates, even if client requested no updates.
/// Overrides the client request for no updates via the N flag.
bool override_no_update_;
/// @brief Should Kea perform updates, even if client requested delegation.
bool override_client_update_;
/// @brief Should Kea replace the domain-name supplied by the client.
bool replace_client_name_;
/// @brief Prefix Kea should use when generating domain-names.
std::string generated_prefix_;
/// @brief Suffix Kea should use when to qualify partial domain-names.
std::string qualifying_suffix_;
};
std::ostream&
operator<<(std::ostream& os, const D2ClientConfig& config);
/// @brief Defines a pointer for D2ClientConfig instances.
typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
///
/// Provides services for managing the current D2ClientConfig and managing
/// communications with D2. (@todo The latter will be added once communication
/// with D2 is implemented through the integration of
/// dhcp_ddns::NameChangeSender interface(s).
///
class D2ClientMgr {
public:
/// @brief Constructor
///
/// Default constructor which constructs an instance which has DHCP-DDNS
/// updates disabled.
D2ClientMgr();
/// @brief Destructor.
~D2ClientMgr();
/// @brief Updates the DHCP-DDNS client configuration to the given value.
///
/// @param new_config pointer to the new client configuration.
/// @throw D2ClientError if passed an empty pointer.
void setD2ClientConfig(D2ClientConfigPtr& new_config);
/// @param Convenience method for checking if DHCP-DDNS updates are enabled.
///
/// @return True if the D2 configuration is enabled.
bool isDhcpDdnsEnabled();
/// @brief Fetches the DHCP-DDNS configuration pointer.
///
/// @return a reference to the current configuration pointer.
const D2ClientConfigPtr& getD2ClientConfig() const;
private:
/// @brief Container class for DHCP-DDNS configuration parameters.
D2ClientConfigPtr d2_client_config_;
};
/// @brief Defines a pointer for D2ClientMgr instances.
typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
} // namespace isc
} // namespace dhcp
#endif

View File

@@ -1163,5 +1163,111 @@ SubnetConfigParser::getParam(const std::string& name) {
return (Triplet<uint32_t>(value));
}
//**************************** D2ClientConfigParser **********************
D2ClientConfigParser::D2ClientConfigParser(const std::string& entry_name)
: entry_name_(entry_name), boolean_values_(new BooleanStorage()),
uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
local_client_config_() {
}
D2ClientConfigParser::~D2ClientConfigParser() {
}
void
D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
BOOST_FOREACH(ConfigPair param, client_config->mapValue()) {
ParserPtr parser(createConfigParser(param.first));
parser->build(param.second);
parser->commit();
}
bool enable_updates = boolean_values_->getParam("enable-updates");
if (!enable_updates) {
// If it's not enabled, don't bother validating the rest. This
// allows for an abbreviated config entry that only contains
// the flag. The default constructor creates a disabled instance.
local_client_config_.reset(new D2ClientConfig());
return;
}
// Get all parameters that are needed to create the D2ClientConfig.
asiolink::IOAddress server_ip(string_values_->getParam("server-ip"));
uint32_t server_port = uint32_values_->getParam("server-port");
dhcp_ddns::NameChangeProtocol
ncr_protocol = dhcp_ddns:: stringToNcrProtocol(string_values_->
getParam("ncr-protocol"));
dhcp_ddns::NameChangeFormat
ncr_format = dhcp_ddns::stringToNcrFormat(string_values_->
getParam("ncr-format"));
std::string generated_prefix = string_values_->getParam("generated-prefix");
std::string qualifying_suffix = string_values_->
getParam("qualifying-suffix");
bool remove_on_renew = boolean_values_->getParam("remove-on-renew");
bool always_include_fqdn = boolean_values_->getParam("always-include-fqdn");
bool allow_client_update = boolean_values_->getParam("allow-client-update");
bool override_no_update = boolean_values_->getParam("override-no-update");
bool override_client_update = boolean_values_->
getParam("override-client-update");
bool replace_client_name = boolean_values_->getParam("replace-client-name");
// Attempt to create the new client config.
local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
server_port, ncr_protocol,
ncr_format, remove_on_renew,
always_include_fqdn,
allow_client_update,
override_no_update,
override_client_update,
replace_client_name,
generated_prefix,
qualifying_suffix));
}
isc::dhcp::ParserPtr
D2ClientConfigParser::createConfigParser(const std::string& config_id) {
DhcpConfigParser* parser = NULL;
if (config_id.compare("server-port") == 0) {
parser = new Uint32Parser(config_id, uint32_values_);
} else if ((config_id.compare("server-ip") == 0) ||
(config_id.compare("ncr-protocol") == 0) ||
(config_id.compare("ncr-format") == 0) ||
(config_id.compare("generated-prefix") == 0) ||
(config_id.compare("qualifying-suffix") == 0)) {
parser = new StringParser(config_id, string_values_);
} else if ((config_id.compare("enable-updates") == 0) ||
(config_id.compare("remove-on-renew") == 0) ||
(config_id.compare("always-include-fqdn") == 0) ||
(config_id.compare("allow-client-update") == 0) ||
(config_id.compare("override-no-update") == 0) ||
(config_id.compare("override-client-update") == 0) ||
(config_id.compare("replace-client-name") == 0)) {
parser = new BooleanParser(config_id, boolean_values_);
} else {
isc_throw(NotImplemented,
"parser error: D2ClientConfig parameter not supported: "
<< config_id);
}
return (isc::dhcp::ParserPtr(parser));
}
void
D2ClientConfigParser::commit() {
// @todo if local_client_config_ is empty then shutdown the listener...
// @todo Should this also attempt to start a listener?
// In keeping with Interface, Subnet, and Hooks parsers, then this
// should initialize the listener. Failure to init it, should cause
// rollback. This gets sticky, because who owns the listener instance?
// Does CfgMgr maintain it or does the server class? If the latter
// how do we get that value here?
// I'm thinkikng D2ClientConfig could contain the listener instance
CfgMgr::instance().setD2ClientConfig(local_client_config_);
}
}; // namespace dhcp
}; // namespace isc

View File

@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <cc/data.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/d2_client.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/subnet.h>
@@ -243,7 +244,7 @@ public:
// its value. If it doesn't we insert a new element.
storage_->setParam(param_name_, value_);
}
private:
/// Pointer to the storage where committed value is stored.
boost::shared_ptr<ValueStorage<ValueType> > storage_;
@@ -876,6 +877,77 @@ protected:
ParserContextPtr global_context_;
};
/// @brief Parser for D2ClientConfig
///
/// This class parses the configuration element "dhcp-ddns" common to the
/// spec files for both dhcp4 and dhcp6. It creates an instance of a
/// D2ClientConfig.
class D2ClientConfigParser : public isc::dhcp::DhcpConfigParser {
public:
/// @brief Constructor
///
/// @param entry_name is an arbitrary label assigned to this configuration
/// definition.
D2ClientConfigParser(const std::string& entry_name);
/// @brief Destructor
virtual ~D2ClientConfigParser();
/// @brief Performs the parsing of the given dhcp-ddns element.
///
/// The results of the parsing are retained internally for use during
/// commit.
///
/// @param client_config is the "dhcp-ddns" configuration to parse
virtual void build(isc::data::ConstElementPtr client_config);
/// @brief Creates a parser for the given "dhcp-ddns" member element id.
///
/// The elements currently supported are:
/// -# enable-updates
/// -# server-ip
/// -# server-port
/// -# ncr-protocol
/// -# ncr-format
/// -# remove-on-renew
/// -# always-include-fqdn
/// -# allow-client-update
/// -# override-no-update
/// -# override-client-update
/// -# replace-client-name
/// -# generated-prefix
/// -# qualifying-suffix
/// (see d2::D2ClientConfig for details on each.)
///
/// @param config_id is the "item_name" for a specific member element of
/// the "dns_server" specification.
///
/// @return returns a pointer to newly created parser.
virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
config_id);
/// @brief Instantiates a D2ClientConfig from internal data values
/// passes to CfgMgr singleton.
virtual void commit();
private:
/// @brief Arbitrary label assigned to this parser instance.
/// Primarily used for diagnostics.
std::string entry_name_;
/// Storage for subnet-specific boolean values.
BooleanStoragePtr boolean_values_;
/// Storage for subnet-specific integer values.
Uint32StoragePtr uint32_values_;
/// Storage for subnet-specific string values.
StringStoragePtr string_values_;
/// @brief Pointer to temporary local instance created during build.
D2ClientConfigPtr local_client_config_ ;
};
// Pointers to various parser objects.
typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;

View File

@@ -70,6 +70,9 @@ specified IPv6 subnet to its database.
A debug message issued when server is being configured to listen on all
interfaces.
% DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1
A debug message issued when the server's DHCP-DDNS settings are changed.
% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
A debug message issued when configuration manager clears the internal list
of active interfaces. This doesn't prevent the server from listening to

View File

@@ -52,6 +52,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
@@ -88,6 +89,7 @@ endif
libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la

View File

@@ -670,6 +670,48 @@ TEST_F(CfgMgrTest, echoClientId) {
EXPECT_TRUE(cfg_mgr.echoClientId());
}
// This test checks the D2ClientMgr wrapper methods.
TEST_F(CfgMgrTest, d2ClientConfig) {
CfgMgr& cfg_mgr = CfgMgr::instance();
// After CfgMgr construction, D2 configuration should be disabled.
// Fetch it and verify this is the case.
D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
ASSERT_TRUE(original_config);
EXPECT_FALSE(original_config->getEnableUpdates());
// Make sure convenience method agrees.
EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
// Verify that we cannot set the configuration to an empty pointer.
D2ClientConfigPtr new_cfg;
ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
// Create a new, enabled configuration.
ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
isc::asiolink::IOAddress("127.0.0.1"), 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
// Verify that we can assign a new, non-empty configuration.
ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg));
// Verify that we can fetch the newly assigned configuration.
D2ClientConfigPtr updated_config = CfgMgr::instance().getD2ClientConfig();
ASSERT_TRUE(updated_config);
EXPECT_TRUE(updated_config->getEnableUpdates());
// Make sure convenience method agrees with updated configuration.
EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
// Make sure the configuration we fetched is the one we assigned,
// and not the original configuration.
EXPECT_EQ(*new_cfg, *updated_config);
EXPECT_NE(*original_config, *updated_config);
}
/// @todo Add unit-tests for testing:
/// - addActiveIface() with invalid interface name
/// - addActiveIface() with the same interface twice

View File

@@ -0,0 +1,308 @@
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <dhcpsrv/d2_client.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
using namespace std;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;
using namespace isc;
namespace {
// brief Checks constructors and accessors of D2ClientConfig.
TEST(D2ClientConfigTest, constructorsAndAccessors) {
D2ClientConfigPtr d2_client_config;
// Verify default constructor creates a disabled instance.
ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig()));
EXPECT_FALSE(d2_client_config->getEnableUpdates());
d2_client_config.reset();
bool enable_updates = true;
isc::asiolink::IOAddress server_ip("127.0.0.1");
size_t server_port = 477;
dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
bool remove_on_renew = true;
bool always_include_fqdn = true;
bool allow_client_update = true;
bool override_no_update = true;
bool override_client_update = true;
bool replace_client_name = true;
std::string generated_prefix = "the_prefix";
std::string qualifying_suffix = "the.suffix.";
// Verify that we can construct a valid, enabled instance.
ASSERT_NO_THROW(d2_client_config.reset(new
D2ClientConfig(enable_updates,
server_ip,
server_port,
ncr_protocol,
ncr_format,
remove_on_renew,
always_include_fqdn,
allow_client_update,
override_no_update,
override_client_update,
replace_client_name,
generated_prefix,
qualifying_suffix)));
ASSERT_TRUE(d2_client_config);
// Verify that the accessors return the expected values.
EXPECT_EQ(d2_client_config->getEnableUpdates(), enable_updates);
EXPECT_EQ(d2_client_config->getServerIp(), server_ip);
EXPECT_EQ(d2_client_config->getServerPort(), server_port);
EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
EXPECT_EQ(d2_client_config->getRemoveOnRenew(), remove_on_renew);
EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
EXPECT_EQ(d2_client_config->getAllowClientUpdate(), allow_client_update);
EXPECT_EQ(d2_client_config->getOverrideNoUpdate(), override_no_update);
EXPECT_EQ(d2_client_config->getOverrideClientUpdate(),
override_client_update);
EXPECT_EQ(d2_client_config->getReplaceClientName(), replace_client_name);
EXPECT_EQ(d2_client_config->getGeneratedPrefix(), generated_prefix);
EXPECT_EQ(d2_client_config->getQualifyingSuffix(), qualifying_suffix);
// Verify that toText called by << operator doesn't bomb.
ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
*d2_client_config << std::endl);
// Verify that constructor does not allow use of NCR_TCP.
// @todo obviously this becomes invalid once TCP is supported.
ASSERT_THROW(d2_client_config.reset(new
D2ClientConfig(enable_updates,
server_ip,
server_port,
dhcp_ddns::NCR_TCP,
ncr_format,
remove_on_renew,
always_include_fqdn,
allow_client_update,
override_no_update,
override_client_update,
replace_client_name,
generated_prefix,
qualifying_suffix)),
D2ClientError);
// @todo if additional validation is added to ctor, this test needs to
// expand accordingly.
}
// Tests the equality and inequality operators of D2ClientConfig.
TEST(D2ClientConfigTest, equalityOperator) {
D2ClientConfigPtr ref_config;
D2ClientConfigPtr test_config;
isc::asiolink::IOAddress ref_address("127.0.0.1");
isc::asiolink::IOAddress test_address("127.0.0.2");
// Create an instance to use as a reference.
ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(ref_config);
// Check a configuration that is identical to reference configuration.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_TRUE(*ref_config == *test_config);
EXPECT_FALSE(*ref_config != *test_config);
// Check a configuration that differs only by enable flag.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by server ip.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
test_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by server port.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 333,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by remove_on_renew.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
false, true, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by always_include_fqdn.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, false, true, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by allow_client_update.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, false, true, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by override_no_update.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, false, true, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by override_client_update.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, false, true,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by replace_client_name.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, false,
"pre-fix", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by generated_prefix.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"bogus", "suf-fix")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
// Check a configuration that differs only by qualifying_suffix.
ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
ref_address, 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "bogus")));
ASSERT_TRUE(test_config);
EXPECT_FALSE(*ref_config == *test_config);
EXPECT_TRUE(*ref_config != *test_config);
}
// This test checks the D2ClientMgr constructor.
TEST(D2ClientMgr, constructor) {
D2ClientMgrPtr d2_client_mgr;
// Verify we can construct with the default constructor.
ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
// After construction, D2 configuration should be disabled.
// Fetch it and verify this is the case.
D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
ASSERT_TRUE(original_config);
EXPECT_FALSE(original_config->getEnableUpdates());
// Make sure convenience method agrees.
EXPECT_FALSE(d2_client_mgr->isDhcpDdnsEnabled());
}
// This test checks passing the D2ClientMgr a valid D2 client configuration.
// @todo Once NameChangeSender is integrated, this test needs to expand, and
// additional scenario tests will need to be written.
TEST(D2ClientMgr, validConfig) {
D2ClientMgrPtr d2_client_mgr;
// Construct the manager and fetch its initial configuration.
ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
ASSERT_TRUE(original_config);
// Verify that we cannot set the config to an empty pointer.
D2ClientConfigPtr new_cfg;
ASSERT_THROW(d2_client_mgr->setD2ClientConfig(new_cfg), D2ClientError);
// Create a new, enabled config.
ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
isc::asiolink::IOAddress("127.0.0.1"), 477,
dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
true, true, true, true, true, true,
"pre-fix", "suf-fix")));
// Verify that we can assign a new, non-empty configuration.
ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg));
// Verify that we can fetch the newly assigned configuration.
D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig();
ASSERT_TRUE(updated_config);
EXPECT_TRUE(updated_config->getEnableUpdates());
// Make sure convenience method agrees with the updated configuration.
EXPECT_TRUE(d2_client_mgr->isDhcpDdnsEnabled());
// Make sure the configuration we fetched is the one we assigned,
// and not the original configuration.
EXPECT_EQ(*new_cfg, *updated_config);
EXPECT_NE(*original_config, *updated_config);
}
} // end of anonymous namespace

View File

@@ -367,8 +367,8 @@ public:
///
/// Note that the method currently it only supports option-defs, option-data
/// and hooks-libraries.
///
/// @param config_id is the name of the configuration element.
///
/// @param config_id is the name of the configuration element.
///
/// @return returns a shared pointer to DhcpConfigParser.
///
@@ -376,20 +376,21 @@ public:
ParserPtr createConfigParser(const std::string& config_id) {
ParserPtr parser;
if (config_id.compare("option-data") == 0) {
parser.reset(new OptionDataListParser(config_id,
parser_context_->options_,
parser.reset(new OptionDataListParser(config_id,
parser_context_->options_,
parser_context_,
UtestOptionDataParser::factory));
} else if (config_id.compare("option-def") == 0) {
parser.reset(new OptionDefListParser(config_id,
parser.reset(new OptionDefListParser(config_id,
parser_context_->option_defs_));
} else if (config_id.compare("hooks-libraries") == 0) {
parser.reset(new HooksLibrariesParser(config_id));
hooks_libraries_parser_ =
boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
} else if (config_id.compare("dhcp-ddns") == 0) {
parser.reset(new D2ClientConfigParser(config_id));
} else {
isc_throw(NotImplemented,
"Parser error: configuration parameter not supported: "
@@ -399,8 +400,8 @@ public:
return (parser);
}
/// @brief Convenience method for parsing a configuration
///
/// @brief Convenience method for parsing a configuration
///
/// Given a configuration string, convert it into Elements
/// and parse them.
/// @param config is the configuration string to parse
@@ -491,6 +492,10 @@ public:
// Ensure no hooks libraries are loaded.
HooksManager::unloadLibraries();
// Set it to minimal, disabled config
D2ClientConfigPtr tmp(new D2ClientConfig());
CfgMgr::instance().setD2ClientConfig(tmp);
}
/// @brief Parsers used in the parsing of the configuration
@@ -703,6 +708,226 @@ TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
"Error text returned from parse failure is " << error_text_;
}
/// @brief Checks that a valid, enabled D2 client configuration works correctly.
TEST_F(ParseConfigTest, validD2Config) {
// Configuration string. This contains a set of valid libraries.
std::string config_str =
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}";
// Verify that the configuration string parses.
int rcode = parseConfiguration(config_str);
ASSERT_TRUE(rcode == 0) << error_text_;
// Verify that DHCP-DDNS is enabled and we can fetch the configuration.
EXPECT_TRUE(CfgMgr::instance().isDhcpDdnsEnabled());
D2ClientConfigPtr d2_client_config;
ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
ASSERT_TRUE(d2_client_config);
// Verify that the configuration values are as expected.
EXPECT_TRUE(d2_client_config->getEnableUpdates());
EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
EXPECT_EQ(5301, d2_client_config->getServerPort());
EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
EXPECT_TRUE(d2_client_config->getRemoveOnRenew());
EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
EXPECT_TRUE(d2_client_config->getAllowClientUpdate());
EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
EXPECT_TRUE(d2_client_config->getReplaceClientName());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
}
/// @brief Checks that D2 client can be configured with enable flag of
/// false only.
TEST_F(ParseConfigTest, validDisabledD2Config) {
// Configuration string. This contains a set of valid libraries.
std::string config_str =
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : false"
" }"
"}";
// Verify that the configuration string parses.
int rcode = parseConfiguration(config_str);
ASSERT_TRUE(rcode == 0) << error_text_;
// Verify that DHCP-DDNS is disabled.
EXPECT_FALSE(CfgMgr::instance().isDhcpDdnsEnabled());
// Make sure fetched config agrees.
D2ClientConfigPtr d2_client_config;
ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
EXPECT_TRUE(d2_client_config);
EXPECT_FALSE(d2_client_config->getEnableUpdates());
}
/// @brief Check various invalid D2 client configurations.
TEST_F(ParseConfigTest, invalidD2Config) {
std::string invalid_configs[] = {
// only the enable flag of true
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true"
" }"
"}",
// Missing server ip value
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
//" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// Invalid server ip value
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"x192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// Unknown protocol
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"Bogus\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// Unsupported protocol
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"TCP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// Unknown format
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
" \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"Bogus\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// Missig Port
"{ \"dhcp-ddns\" :"
" {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"192.168.2.1\", "
// " \"server-port\" : 5301, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"remove-on-renew\" : true, "
" \"always-include-fqdn\" : true, "
" \"allow-client-update\" : true, "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : true, "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" "
" }"
"}",
// stop
""
};
// Fetch the original config.
D2ClientConfigPtr original_config;
ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig());
// Iterate through the invalid configuration strings, attempting to
// parse each one. They should fail to parse, but fail gracefully.
D2ClientConfigPtr current_config;
int i = 0;
while (!invalid_configs[i].empty()) {
// Verify that the configuration string parses without throwing.
int rcode = parseConfiguration(invalid_configs[i]);
// Verify that parse result indicates a parsing error.
ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i
<< " should not have passed!";
// Verify that the "official" config still matches the original config.
ASSERT_NO_THROW(current_config =
CfgMgr::instance().getD2ClientConfig());
EXPECT_EQ(*original_config, *current_config);
++i;
}
}
/// @brief DHCP Configuration Parser Context test fixture.
class ParserContextTest : public ::testing::Test {
public: