Commit b3f483e5 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[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. ...@@ -183,6 +183,11 @@ the lease is acquired for the client.
This debug message is logged when server includes an DHCPv6 Client FQDN Option This debug message is logged when server includes an DHCPv6 Client FQDN Option
in its response to the client. 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 % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
This message is printed when DHCPv6 server disables an interface from being 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 used to receive DHCPv6 traffic. Sockets on this interface will not be opened
......
...@@ -1063,17 +1063,34 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer, ...@@ -1063,17 +1063,34 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
void void
Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer,
AllocEngine::ClientContext6& ctx) { AllocEngine::ClientContext6& ctx) {
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
// Get Client FQDN Option from the client's message. If this option hasn't // Get Client FQDN Option from the client's message. If this option hasn't
// been included, do nothing. // been included, do nothing.
Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast< Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN)); Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
if (!fqdn) { if (!fqdn) {
// No FQDN so lease hostname comes from host reservation if one D2ClientConfig::ReplaceClientNameMode replace_name_mode =
if (ctx.host_) { d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
ctx.hostname_ = ctx.host_->getHostname(); 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) LOG_DEBUG(ddns6_logger, DBG_DHCP6_DETAIL, DHCP6_DDNS_RECEIVE_FQDN)
...@@ -1086,12 +1103,10 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer, ...@@ -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 // Set the server S, N, and O flags based on client's flags and
// current configuration. // current configuration.
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp); d2_mgr.adjustFqdnFlags<Option6ClientFqdn>(*fqdn, *fqdn_resp);
// If there's a reservation and it has a hostname specified, use it! // If there's a reservation and it has a hostname specified, use it!
if (ctx.host_ && !ctx.host_->getHostname().empty()) { if (ctx.host_ && !ctx.host_->getHostname().empty()) {
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
// Add the qualifying suffix. // Add the qualifying suffix.
// After #3765, this will only occur if the suffix is not empty. // After #3765, this will only occur if the suffix is not empty.
fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(), fqdn_resp->setDomainName(d2_mgr.qualifyName(ctx.host_->getHostname(),
......
...@@ -53,6 +53,20 @@ public: ...@@ -53,6 +53,20 @@ public:
static const uint16_t OVERRIDE_CLIENT_UPDATE = 4; static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
static const uint16_t REPLACE_CLIENT_NAME = 8; 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 // Type used to indicate whether or not forward updates are expected
struct ExpFwd { struct ExpFwd {
ExpFwd(bool exp_fwd) : value_(exp_fwd){}; ExpFwd(bool exp_fwd) : value_(exp_fwd){};
...@@ -112,7 +126,8 @@ public: ...@@ -112,7 +126,8 @@ public:
(mask & ALWAYS_INCLUDE_FQDN), (mask & ALWAYS_INCLUDE_FQDN),
(mask & OVERRIDE_NO_UPDATE), (mask & OVERRIDE_NO_UPDATE),
(mask & OVERRIDE_CLIENT_UPDATE), (mask & OVERRIDE_CLIENT_UPDATE),
((mask & REPLACE_CLIENT_NAME) ? D2ClientConfig::RCM_WHEN_PRESENT ((mask & REPLACE_CLIENT_NAME) ?
D2ClientConfig::RCM_WHEN_PRESENT
: D2ClientConfig::RCM_NEVER), : D2ClientConfig::RCM_NEVER),
"myhost", "example.com"))); "myhost", "example.com")));
ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg)); ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
...@@ -368,6 +383,84 @@ public: ...@@ -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 /// @brief Tests that the client's message holding an FQDN is processed
/// and that lease is acquired. /// and that lease is acquired.
/// ///
...@@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) { ...@@ -1234,4 +1327,44 @@ TEST_F(FqdnDhcpv6SrvTest, hostnameReservationDdnsDisabled) {
IOAddress("2001:db8:1:1::babe")); 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 } // end of anonymous namespace
...@@ -457,7 +457,8 @@ void ...@@ -457,7 +457,8 @@ void
D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) { D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp) {
// If we're configured to replace it or the supplied name is blank // If we're configured to replace it or the supplied name is blank
// set the response name to 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.getDomainName().empty()) {
fqdn_resp.setDomainName("", T::PARTIAL); fqdn_resp.setDomainName("", T::PARTIAL);
} else { } 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