Commit b3f483e5 authored by Thomas Markwalder's avatar Thomas Markwalder

[4259] kea-dhcp6 now supports replace-client-name modes

    src/bin/dhcp6/dhcp6_messages.mes
        - Added new log message, DHCP6_DDNS_SUPPLY_FQDN

    src/bin/dhcp6/dhcp6_srv.cc
        - Dhcpv6Srv::processClientFqdn() - modified to support the name
        replacement modes

    src/bin/dhcp6/tests/fqdn_unittest.cc
        - FqdnDhcpv6SrvTest::testReplaceClientNameMode() new method which tests
        a server's handling of a single client packet for a given
        replace-client-name mode.

        - TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) - new test which
        exercises the permutations of client packets and replace-client-name
        modes.
parent bc8768e9
......@@ -183,6 +183,11 @@ the lease is acquired for the client.
This debug message is logged when server includes an DHCPv6 Client FQDN Option
in its response to the client.
% DHCP6_DDNS_SUPPLY_FQDN %1: client did not send a FQDN option, will supply one for them
This debug message is issued when the server did not receive a FQDN option
from the client and client name replacement is enabled. This provides a means
to create DNS entries for unsophisticated clients.
% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
This message is printed when DHCPv6 server disables an interface from being
used to receive DHCPv6 traffic. Sockets on this interface will not be opened
......
......@@ -1063,17 +1063,34 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
void
Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
AllocEngine::ClientContext6& ctx) {
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
// Get Client FQDN Option from the client's message. If this option hasn't
// been included, do nothing.
Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
if (!fqdn) {
// No FQDN so lease hostname comes from host reservation if one
if (ctx.host_) {
ctx.hostname_ = ctx.host_->getHostname();
}
D2ClientConfig::ReplaceClientNameMode replace_name_mode =
d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
if (d2_mgr.ddnsEnabled() &&
(replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
replace_name_mode == D2ClientConfig::RCM_WHEN_NOT_PRESENT)) {
// Fabricate an empty "client" FQDN with flags requesting
// the server do all the updates. The flags will get modified
// below according the configuration options, the name will
// be supplied later on.
fqdn.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
Option6ClientFqdn::PARTIAL));
LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_SUPPLY_FQDN)
.arg(question->getLabel());
} else {
// No FQDN so lease hostname comes from host reservation if one
if (ctx.host_) {
ctx.hostname_ = ctx.host_->getHostname();
}
return;
return;
}
}
LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_RECEIVE_FQDN)
......@@ -1086,12 +1103,10 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
// Set the server S, N, and O flags based on client's flags and
// current configuration.
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
// If there's a reservation and it has a hostname specified, use it!
if (ctx.host_ && !ctx.host_->getHostname().empty()) {
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
// Add the qualifying suffix.
// After #3765, this will only occur if the suffix is not empty.
fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),
......
......@@ -53,6 +53,20 @@ public:
static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
static const uint16_t REPLACE_CLIENT_NAME = 8;
// Enum used to specify whether a client (packet) should include
// the hostname option
enum ClientNameFlag {
CLIENT_NAME_PRESENT,
CLIENT_NAME_NOT_PRESENT
};
// Enum used to specify whether the server should replace/supply
// the hostname or not
enum ReplacementFlag {
NAME_REPLACED,
NAME_NOT_REPLACED
};
// Type used to indicate whether or not forward updates are expected
struct ExpFwd {
ExpFwd(bool exp_fwd) : value_(exp_fwd){};
......@@ -112,7 +126,8 @@ public:
(mask & ALWAYS_INCLUDE_FQDN),
(mask & OVERRIDE_NO_UPDATE),
(mask & OVERRIDE_CLIENT_UPDATE),
((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT
((mask & REPLACE_CLIENT_NAME) ?
D2ClientConfig::RCM_WHEN_PRESENT
: D2ClientConfig::RCM_NEVER),
"myhost", "example.com")));
ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
......@@ -368,6 +383,84 @@ public:
}
}
// Test that the server processes the FQDN option (or lack thereof)
// in a client request correctly, according to the replace-client-name
// mode configuration parameter.
//
// @param mode - value to use client-name-replacment parameter - for
// mode labels such as NEVER and ALWAYS must incluce enclosing quotes:
// "\"NEVER\"". This allows us to also pass in boolean literals which
// are unquoted.
// @param client_name_flag - specifies whether or not the client request
// should contain a hostname option
// @param exp_replacement_flag - specifies whether or not the server is
// expected to replace (or supply) the FQDN/name in its response
void testReplaceClientNameMode(const char* mode,
enum ClientNameFlag client_name_flag,
enum ReplacementFlag exp_replacement_flag) {
// Configuration "template" with a replaceable mode parameter
const char* config_template =
"{ \"interfaces-config\": { \n"
" \"interfaces\": [ \"eth0\" ] \n"
"}, \n"
"\"valid-lifetime\": 4000, \n"
"\"preferred-lifetime\": 3000, \n"
"\"rebind-timer\": 2000, \n"
"\"renew-timer\": 1000, \n"
"\"subnet6\": [ { \n"
" \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], \n"
" \"subnet\": \"2001:db8:1::/48\", \n"
" \"interface-id\": \"\", \n"
" \"interface\": \"eth0\" \n"
" } ], \n"
"\"dhcp-ddns\": { \n"
"\"enable-updates\": true, \n"
"\"qualifying-suffix\": \"fake-suffix.isc.org.\", \n"
"\"replace-client-name\": %s \n"
"}} \n";
// Create the configuration and configure the server
char config_buf[1024];
sprintf(config_buf, config_template, mode);
configure(config_buf, *srv_);
// Build our client packet
Pkt6Ptr query;
if (client_name_flag == CLIENT_NAME_PRESENT) {
query = generateMessage(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
"my.example.com.", Option6ClientFqdn::FULL,
true);
} else {
query = generateMessageWithIds(DHCPV6_SOLICIT);
}
Pkt6Ptr answer = generateMessageWithIds(DHCPV6_ADVERTISE);
AllocEngine::ClientContext6 ctx;
ASSERT_NO_THROW(srv_->processClientFqdn(query, answer, ctx));
Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
// Verify the contents (or lack thereof) of the FQDN
if (exp_replacement_flag == NAME_REPLACED) {
ASSERT_TRUE(answ_fqdn);
EXPECT_TRUE(answ_fqdn->getDomainName().empty());
EXPECT_EQ(Option6ClientFqdn::PARTIAL,
answ_fqdn->getDomainNameType());
} else {
if (client_name_flag == CLIENT_NAME_PRESENT) {
ASSERT_TRUE(answ_fqdn);
EXPECT_EQ("my.example.com.", answ_fqdn->getDomainName());
EXPECT_EQ(Option6ClientFqdn::FULL,
answ_fqdn->getDomainNameType());
} else {
ASSERT_FALSE(answ_fqdn);
}
}
}
/// @brief Tests that the client's message holding an FQDN is processed
/// and that lease is acquired.
///
......@@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
IOAddress("2001:db8:1:1::babe"));
}
// Verifies that the replace-client-name behavior is correct for each of
// the supported modes.
TEST_F(FqdnDhcpv6SrvTest, replaceClientNameModeTest) {
isc::dhcp::test::IfaceMgrTestConfig test_config(true);
// We pass mode labels in with enclosing quotes so we can also test
// unquoted boolean literals true/false
testReplaceClientNameMode("\"NEVER\"",
CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
testReplaceClientNameMode("\"NEVER\"",
CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
testReplaceClientNameMode("\"ALWAYS\"",
CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
testReplaceClientNameMode("\"ALWAYS\"",
CLIENT_NAME_PRESENT, NAME_REPLACED);
testReplaceClientNameMode("\"WHEN_PRESENT\"",
CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
testReplaceClientNameMode("\"WHEN_PRESENT\"",
CLIENT_NAME_PRESENT, NAME_REPLACED);
testReplaceClientNameMode("\"WHEN_NOT_PRESENT\"",
CLIENT_NAME_NOT_PRESENT, NAME_REPLACED);
testReplaceClientNameMode("\"WHEN_NOT_PRESENT\"",
CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
// Verify that boolean false produces the same result as RCM_NEVER
testReplaceClientNameMode("false",
CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
testReplaceClientNameMode("false",
CLIENT_NAME_PRESENT, NAME_NOT_REPLACED);
// Verify that boolean true produces the same result as RCM_WHEN_PRESENT
testReplaceClientNameMode("true",
CLIENT_NAME_NOT_PRESENT, NAME_NOT_REPLACED);
testReplaceClientNameMode("true",
CLIENT_NAME_PRESENT, NAME_REPLACED);
}
} // end of anonymous namespace
......@@ -457,7 +457,8 @@ void
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
// If we're configured to replace it or the supplied name is blank
// set the response name to blank.
if ((d2_client_config_->getReplaceClientNameMode() != D2ClientConfig::RCM_NEVER) ||
if ((d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_ALWAYS ||
d2_client_config_->getReplaceClientNameMode() == D2ClientConfig::RCM_WHEN_PRESENT) ||
fqdn.getDomainName().empty()) {
fqdn_resp.setDomainName("", T::PARTIAL);
} else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment