Commit 77256964 authored by Francis Dupont's avatar Francis Dupont
Browse files

[540-recommend-moving-hostname-char-set-and-hostname-char-replacement-out-of-d...

[540-recommend-moving-hostname-char-set-and-hostname-char-replacement-out-of-dhcp-ddns] Checkpoint between DHCPv6 regen
parent f0229ae3
......@@ -373,6 +373,12 @@ ControlledDhcpv4Srv::commandConfigSetHandler(const string&,
if (rcode == 0) {
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
// Update the fetch globals callback.
auto cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
});
// Use new configuration.
CfgMgr::instance().commit();
} else {
......
......@@ -368,6 +368,9 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
// early.
Dhcp4ConfigParser global_parser;
// D2 client configuration.
D2ClientConfigPtr d2_client_cfg;
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
mutable_cfg->mapValue();
......@@ -445,8 +448,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
// Apply defaults
D2ClientConfigParser::setAllDefaults(config_pair.second);
D2ClientConfigParser parser;
D2ClientConfigPtr cfg = parser.parse(config_pair.second);
srv_cfg->setD2ClientConfig(cfg);
d2_client_cfg = parser.parse(config_pair.second);
continue;
}
......@@ -557,7 +559,9 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
(config_pair.first == "calculate-tee-times") ||
(config_pair.first == "t1-percent") ||
(config_pair.first == "t2-percent") ||
(config_pair.first == "loggers")) {
(config_pair.first == "loggers") ||
(config_pair.first == "hostname-char-set") ||
(config_pair.first == "hostname-char-replacement")) {
CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
config_pair.second);
......@@ -584,6 +588,16 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
// defined as part of shared networks.
global_parser.sanityChecks(srv_cfg, mutable_cfg);
// Validate D2 client confuguration.
if (!d2_client_cfg) {
d2_client_cfg.reset(new D2ClientConfig());
d2_client_cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
});
}
d2_client_cfg->validateContents();
srv_cfg->setD2ClientConfig(d2_client_cfg);
} catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
.arg(config_pair.first).arg(ex.what());
......
......@@ -4170,6 +4170,165 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
EXPECT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
EXPECT_FALSE(d2_client_config->getHostnameCharSet().unspecified());
EXPECT_EQ("[^A-Za-z0-9_-]", d2_client_config->getHostnameCharSet().get());
EXPECT_FALSE(d2_client_config->getHostnameCharReplacement().unspecified());
EXPECT_EQ("x", d2_client_config->getHostnameCharReplacement().get());
EXPECT_TRUE(d2_client_config->getHostnameSanitizer());
}
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry with
// hostname-char-* at the global scope.
TEST_F(Dhcp4ParserTest, d2ClientConfigGlobal) {
ConstElementPtr status;
// Verify that the D2 configuration 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().ddnsEnabled());
string config_str = "{ " + genIfaceConfig() + "," +
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { \"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\" : 777, "
" \"sender-ip\" : \"192.168.2.2\", "
" \"sender-port\" : 778, "
" \"max-queue-size\" : 2048, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : \"when-present\", "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" },"
"\"hostname-char-set\" : \"[^A-Za-z0-9_-]\", "
"\"hostname-char-replacement\" : \"x\", "
"\"valid-lifetime\": 4000 }";
// Convert the JSON string to configuration elements.
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP4(config_str, true));
extractConfig(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().ddnsEnabled());
// 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(777, d2_client_config->getServerPort());
EXPECT_EQ("192.168.2.2", d2_client_config->getSenderIp().toText());
EXPECT_EQ(778, d2_client_config->getSenderPort());
EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
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->getOverrideNoUpdate());
EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
EXPECT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
EXPECT_FALSE(d2_client_config->getHostnameCharSet().unspecified());
EXPECT_EQ("[^A-Za-z0-9_-]", d2_client_config->getHostnameCharSet().get());
EXPECT_FALSE(d2_client_config->getHostnameCharReplacement().unspecified());
EXPECT_EQ("x", d2_client_config->getHostnameCharReplacement().get());
EXPECT_TRUE(d2_client_config->getHostnameSanitizer());
}
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry with
// hostname-char-* at the local and global scopes (local has the priority).
TEST_F(Dhcp4ParserTest, d2ClientConfigBoth) {
ConstElementPtr status;
// Verify that the D2 configuration 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().ddnsEnabled());
string config_str = "{ " + genIfaceConfig() + "," +
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { \"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\" : 777, "
" \"sender-ip\" : \"192.168.2.2\", "
" \"sender-port\" : 778, "
" \"max-queue-size\" : 2048, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : \"when-present\", "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\", "
" \"hostname-char-set\" : \"[^A-Za-z0-9_-]\", "
" \"hostname-char-replacement\" : \"x\" }, "
"\"hostname-char-set\" : \"[^A-Z]\", "
"\"hostname-char-replacement\" : \"z\", "
"\"valid-lifetime\": 4000 }";
// Convert the JSON string to configuration elements.
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP4(config_str, true));
extractConfig(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().ddnsEnabled());
// 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(777, d2_client_config->getServerPort());
EXPECT_EQ("192.168.2.2", d2_client_config->getSenderIp().toText());
EXPECT_EQ(778, d2_client_config->getSenderPort());
EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
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->getOverrideNoUpdate());
EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
EXPECT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
EXPECT_FALSE(d2_client_config->getHostnameCharSet().unspecified());
EXPECT_EQ("[^A-Za-z0-9_-]", d2_client_config->getHostnameCharSet().get());
EXPECT_FALSE(d2_client_config->getHostnameCharReplacement().unspecified());
EXPECT_EQ("x", d2_client_config->getHostnameCharReplacement().get());
EXPECT_TRUE(d2_client_config->getHostnameSanitizer());
}
// This test checks the ability of the server to handle a configuration
......
......@@ -635,6 +635,10 @@ Dhcpv4SrvTest::configure(const std::string& config, NakedDhcpv4Srv& srv,
} );
if (commit) {
auto cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
});
CfgMgr::instance().commit();
}
}
......@@ -680,6 +684,10 @@ Dhcpv4SrvTest::configureWithStatus(const std::string& config, NakedDhcpv4Srv& sr
cfg_db->createManagers();
} );
if (commit) {
auto cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
});
CfgMgr::instance().commit();
}
}
......
......@@ -197,6 +197,31 @@ const char* CONFIGS[] = {
"\"qualifying-suffix\": \"example.org\""
"}"
"}",
// 7
// Configuration which enables DNS updates and hostname sanitization.
// the second at the global scope.
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 3000,"
"\"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\", "
" \"id\": 1,"
" \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
" \"option-data\": [ {"
" \"name\": \"routers\","
" \"data\": \"10.0.0.200,10.0.0.201\""
" } ],"
" \"reservations\": ["
" {"
" \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
" \"hostname\": \"unique-xxx-host.example.org\""
" }"
" ]"
" }],"
"\"hostname-char-set\" : \"[^A-Za-z0-9.-]\","
"\"hostname-char-replacement\" : \"x\""
"}"
};
class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
......@@ -503,11 +528,11 @@ public:
" \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
" }],"
"\"dhcp-ddns\": {"
"\"enable-updates\": true,"
"\"qualifying-suffix\": \"fake-suffix.isc.org.\","
"\"hostname-char-set\": \"[^A-Za-z0-9.-]\","
"\"hostname-char-replacement\": \"x\","
"\"replace-client-name\": \"%s\""
" \"enable-updates\": true,"
" \"qualifying-suffix\": \"fake-suffix.isc.org.\","
" \"hostname-char-set\": \"[^A-Za-z0-9.-]\","
" \"hostname-char-replacement\": \"x\","
" \"replace-client-name\": \"%s\""
"}}";
// Create the configuration and configure the server
......@@ -1798,6 +1823,68 @@ TEST_F(NameDhcpv4SrvTest, sanitizeHost) {
}
}
// Verifies that setting global hostname-char-set sanitizes Hostname option
// values received from clients.
TEST_F(NameDhcpv4SrvTest, sanitizeHostGlobal) {
Dhcp4Client client(Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(CONFIGS[7], *client.getServer());
// Make sure that DDNS is not enabled.
ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
struct Scenario {
std::string description_;
std::string original_;
std::string sanitized_;
};
std::vector<Scenario> scenarios = {
{
"unqualified host name with invalid characters",
"one-&$_-host",
"one-xxx-host"
},
{
"qualified host name with invalid characters",
"two-&$_-host.other.org",
"two-xxx-host.other.org"
},
{
"unqualified host name with all valid characters",
"three-ok-host",
"three-ok-host"
},
{
"qualified host name with valid characters",
"four-ok-host.other.org",
"four-ok-host.other.org"
}
};
Pkt4Ptr resp;
OptionStringPtr hostname;
for (auto scenario : scenarios) {
SCOPED_TRACE((scenario).description_);
{
// Set the hostname option.
ASSERT_NO_THROW(client.includeHostname((scenario).original_));
// Send the DHCPDISCOVER and make sure that the server responded.
ASSERT_NO_THROW(client.doDiscover());
resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
// Make sure the response hostname is what we expect.
hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
ASSERT_TRUE(hostname);
EXPECT_EQ((scenario).sanitized_, hostname->getValue());
}
}
}
// Verifies that setting hostname-char-set sanitizes FQDN option
// values received from clients.
TEST_F(NameDhcpv4SrvTest, sanitizeFqdn) {
......@@ -1867,5 +1954,72 @@ TEST_F(NameDhcpv4SrvTest, sanitizeFqdn) {
}
}
// Verifies that setting global hostname-char-set sanitizes FQDN option
// values received from clients.
TEST_F(NameDhcpv4SrvTest, sanitizeFqdnGlobal) {
Dhcp4Client client(Dhcp4Client::SELECTING);
// Configure DHCP server.
configure(CONFIGS[7], *client.getServer());
// Make sure that DDNS is not enabled.
ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
struct Scenario {
std::string description_;
std::string original_;
Option4ClientFqdn::DomainNameType name_type_;
std::string sanitized_;
};
std::vector<Scenario> scenarios = {
{
"unqualified FQDN with invalid characters",
"one-&*_-host",
Option4ClientFqdn::PARTIAL,
"one-xxx-host."
},
{
"qualified FQDN with invalid characters",
"two-&*_-host.other.org",
Option4ClientFqdn::FULL,
"two-xxx-host.other.org."
},
{
"unqualified FQDN name with all valid characters",
"three-ok-host",
Option4ClientFqdn::PARTIAL,
"three-ok-host."
},
{
"qualified FQDN name with valid characters",
"four-ok-host.other.org",
Option4ClientFqdn::FULL,
"four-ok-host.other.org."
}
};
Pkt4Ptr resp;
Option4ClientFqdnPtr fqdn;
for (auto scenario = scenarios.begin(); scenario != scenarios.end(); ++scenario) {
SCOPED_TRACE((*scenario).description_);
{
// Set the hostname option.
ASSERT_NO_THROW(client.includeHostname((*scenario).original_));
ASSERT_NO_THROW(client.includeFQDN(0, (*scenario).original_, (*scenario).name_type_));
// Send the DHCPDISCOVER and make sure that the server responded.
ASSERT_NO_THROW(client.doDiscover());
resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
// Make sure the response fqdn is what we expect.
fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
ASSERT_TRUE(fqdn);
EXPECT_EQ((*scenario).sanitized_, fqdn->getDomainName());
}
}
}
} // end of anonymous namespace
This diff is collapsed.
......@@ -375,6 +375,12 @@ ControlledDhcpv6Srv::commandConfigSetHandler(const string&,
if (rcode == CONTROL_RESULT_SUCCESS) {
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
// Update the fetch globals callback.
auto cfg = CfgMgr::instance().getStagingCfg()->getD2ClientConfig();
cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
});
// Use new configuration.
CfgMgr::instance().commit();
} else {
......
......@@ -473,6 +473,9 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
// early.
Dhcp6ConfigParser global_parser;
// D2 client configuration.
D2ClientConfigPtr d2_client_cfg;
BOOST_FOREACH(config_pair, values_map) {
// In principle we could have the following code structured as a series
// of long if else if clauses. That would give a marginal performance
......@@ -567,8 +570,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
// Apply defaults
D2ClientConfigParser::setAllDefaults(config_pair.second);
D2ClientConfigParser parser;
D2ClientConfigPtr cfg = parser.parse(config_pair.second);
srv_config->setD2ClientConfig(cfg);
d2_client_cfg = parser.parse(config_pair.second);
continue;
}
......@@ -674,7 +676,9 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
(config_pair.first == "calculate-tee-times") ||
(config_pair.first == "t1-percent") ||
(config_pair.first == "t2-percent") ||
(config_pair.first == "loggers")) {
(config_pair.first == "loggers") ||
(config_pair.first == "hostname-char-set") ||
(config_pair.first == "hostname-char-replacement")) {
CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
config_pair.second);
......@@ -706,6 +710,16 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
// defined as part of shared networks.
global_parser.sanityChecks(srv_config, mutable_cfg);
// Validate D2 client confuguration.
if (!d2_client_cfg) {
d2_client_cfg.reset(new D2ClientConfig());
d2_client_cfg->setFetchGlobalsFn([]() -> ConstElementPtr {
return (CfgMgr::instance().getStagingCfg()->getConfiguredGlobals());
});
}
d2_client_cfg->validateContents();
srv_config->setD2ClientConfig(d2_client_cfg);
} catch (const isc::Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
.arg(config_pair.first).arg(ex.what());
......
......@@ -4627,8 +4627,167 @@ TEST_F(Dhcp6ParserTest, d2ClientConfig) {
EXPECT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
EXPECT_FALSE(d2_client_config->getHostnameCharSet().unspecified());
EXPECT_EQ("[^A-Za-z0-9_-]", d2_client_config->getHostnameCharSet().get());
EXPECT_FALSE(d2_client_config->getHostnameCharReplacement().unspecified());
EXPECT_EQ("x", d2_client_config->getHostnameCharReplacement().get());
EXPECT_TRUE(d2_client_config->getHostnameSanitizer());
}
// This test checks the ability of the server to parse a configuration
// containing a full, valid dhcp-ddns (D2ClientConfig) entry with
// hostname-char-* at the global scope.
TEST_F(Dhcp6ParserTest, d2ClientConfigGlobal) {
// Verify that the D2 configuration 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().ddnsEnabled());
string config_str = "{ " + genIfaceConfig() + ","
"\"preferred-lifetime\": 3000,"
"\"valid-lifetime\": 4000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
" \"subnet\": \"2001:db8:1::/64\" } ], "
" \"dhcp-ddns\" : {"
" \"enable-updates\" : true, "
" \"server-ip\" : \"3001::1\", "
" \"server-port\" : 777, "
" \"sender-ip\" : \"3001::2\", "
" \"sender-port\" : 778, "
" \"max-queue-size\" : 2048, "
" \"ncr-protocol\" : \"UDP\", "
" \"ncr-format\" : \"JSON\", "
" \"override-no-update\" : true, "
" \"override-client-update\" : true, "
" \"replace-client-name\" : \"when-present\", "
" \"generated-prefix\" : \"test.prefix\", "
" \"qualifying-suffix\" : \"test.suffix.\" }, "
"\"hostname-char-set\" : \"[^A-Za-z0-9_-]\", "
"\"hostname-char-replacement\" : \"x\", "
"\"valid-lifetime\": 4000 }";
// Convert the JSON string to configuration elements.
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP6(config_str));
extractConfig(config_str);
// Pass the configuration in for parsing.
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
// check if returned status is OK
checkResult(status, 0);
// Verify that DHCP-DDNS updating is enabled.
EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
// 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("3001::1", d2_client_config->getServerIp().toText());
EXPECT_EQ(777, d2_client_config->getServerPort());
EXPECT_EQ("3001::2", d2_client_config->getSenderIp().toText());
EXPECT_EQ(778, d2_client_config->getSenderPort());
EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
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->getOverrideNoUpdate());
EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
EXPECT_EQ(D2ClientConfig::RCM_WHEN_PRESENT, d2_client_config->getReplaceClientNameMode());
EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
EXPECT_FALSE(d2_client_config->getHostnameCharSet().unspecified());
EXPECT_EQ("[^A-Za-z0-9_-]", d2_client_config->getHostnameCharSet().get());
EXPECT_FALSE(d2_client_config->getHostnameCharReplacement().unspecified());
EXPECT_EQ("x", d2_client_config->getHostnameCharReplacement().get());
EXPECT_TRUE(d2_client_config->getHostnameSanitizer());