Commit 022c43ec authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[5005] Send reserved hostaname even if DNS updates disabled.

parent 93ab4a40
......@@ -600,6 +600,12 @@ and the lease. The first argument includes the client and the
transaction identification information. The second argument specifies
the leased address.
% DHCP4_RESERVED_HOSTNAME_ASSIGNED %1: server assigned reserved hostname %2
This debug message is issued when the server found hostname reservation
for a client and uses this reservation in a hostname option sent back
to this client. The reserved hostname is qualified with a value
of 'qualifying-suffix' parameter, if this parameter is specified.
% DHCP4_RESPONSE_DATA %1: responding with packet %2 (type %3), packet details: %4
A debug message including the detailed data about the packet being sent
to the client. The first argument contains the client and the transaction
......
......@@ -1400,18 +1400,79 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
// Fetch D2 configuration.
D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
// Do nothing if the DNS updates are disabled.
if (!d2_mgr.ddnsEnabled()) {
return;
// Obtain the Hostname option from the client's message.
OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
(ex.getQuery()->getOption(DHO_HOST_NAME));
if (opt_hostname) {
LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_CLIENT_HOSTNAME_DATA)
.arg(ex.getQuery()->getLabel())
.arg(opt_hostname->getValue());
}
// Hold the pointer to the context. This makes calls to the members and
// functions shorter in terms of the number of characters.
AllocEngine::ClientContext4Ptr ctx = ex.getContext();
// Hostname reservations take precedence over any other configuration,
// i.e. DDNS configuration.
if (ctx->host_ && !ctx->host_->getHostname().empty()) {
// In order to send a reserved hostname value we expect that at least
// one of the following is the case: the client has sent us a hostname
// option, or the client has sent Parameter Request List option with
// the hostname option code included.
// It is going to be less expensive to first check the presence of the
// hostname option.
bool should_send_hostname = static_cast<bool>(opt_hostname);
// Hostname option is not present, so we have to check PRL option.
if (!should_send_hostname) {
OptionUint8ArrayPtr
option_prl = boost::dynamic_pointer_cast<OptionUint8Array>
(ex.getQuery()->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
// PRL option exists, so check if the hostname option code is
// included in it.
if (option_prl) {
const std::vector<uint8_t>&
requested_opts = option_prl->getValues();
if (std::find(requested_opts.begin(), requested_opts.end(),
DHO_HOST_NAME) != requested_opts.end()) {
// Client has requested hostname via Parameter Request
// List option.
should_send_hostname = true;
}
}
}
// If the hostname or PRL option indicates that the server should
// send back a hostname option, send this option with a reserved
// name for this client.
if (should_send_hostname) {
const std::string& hostname = d2_mgr.qualifyName(ctx->host_->getHostname(),
false);
LOG_DEBUG(ddns4_logger, DBG_DHCP4_DETAIL_DATA,
DHCP4_RESERVED_HOSTNAME_ASSIGNED)
.arg(ex.getQuery()->getLabel())
.arg(hostname);
OptionStringPtr opt_hostname_resp(new OptionString(Option::V4,
DHO_HOST_NAME,
hostname));
ex.getResponse()->addOption(opt_hostname_resp);
// We're done here.
return;
}
}
// There is no reservation for this client or the client hasn't requested
// hostname option. There is still a possibility that we'll have to send
// hostname option to this client if the client has included hostname option
// but there is no reservation, or the configuration of the server requires
// that we send the option regardless.
D2ClientConfig::ReplaceClientNameMode replace_name_mode =
d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
// Obtain the Hostname option from the client's message.
OptionStringPtr opt_hostname = boost::dynamic_pointer_cast<OptionString>
(ex.getQuery()->getOption(DHO_HOST_NAME));
// If we don't have a hostname then either we'll supply it or do nothing.
if (!opt_hostname) {
// If we're configured to supply it then add it to the response.
......@@ -1459,14 +1520,10 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
// By checking the number of labels present in the hostname we may infer
// whether client has sent the fully qualified or unqualified hostname.
// If there is a hostname reservation for this client, use it.
if (ex.getContext()->host_ && !ex.getContext()->host_->getHostname().empty()) {
opt_hostname_resp->setValue(d2_mgr.qualifyName(ex.getContext()->host_->getHostname(),
false));
} else if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
|| label_count < 2) {
if ((replace_name_mode == D2ClientConfig::RCM_ALWAYS ||
replace_name_mode == D2ClientConfig::RCM_WHEN_PRESENT)
|| label_count < 2) {
// Set to root domain to signal later on that we should replace it.
// DHO_HOST_NAME is a string option which cannot be empty.
/// @todo We may want to reconsider whether it is appropriate for the
......
......@@ -108,6 +108,60 @@ const char* CONFIGS[] = {
"\"enable-updates\": true,"
"\"qualifying-suffix\": \"fake-suffix.isc.org.\""
"}"
"}",
// Configuration which disables DNS updates but contains a reservation
// for a hostname. Reserved hostname should be assigned to a client if
// the client includes it in the Parameter Request List option.
"{ \"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\": \"reserved.example.org\""
" }"
" ]"
" }],"
"\"dhcp-ddns\": {"
"\"enable-updates\": false,"
"\"qualifying-suffix\": \"\""
"}"
"}",
// Configuration which disables DNS updates but contains a reservation
// for a hostname and the qualifying-suffix which should be appended to
// the reserved hostname in the Hostname option returned to a client.
"{ \"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\": \"foo-bar\""
" }"
" ]"
" }],"
"\"dhcp-ddns\": {"
"\"enable-updates\": false,"
"\"qualifying-suffix\": \"example.isc.org\""
"}"
"}"
};
......@@ -1324,6 +1378,87 @@ TEST_F(NameDhcpv4SrvTest, hostnameReservation) {
}
}
// This test verifies that the server sends the Hostname option to the client
// with hostname reservation and which included hostname option code in the
// Parameter Request List.
TEST_F(NameDhcpv4SrvTest, hostnameReservationPRL) {
Dhcp4Client client(Dhcp4Client::SELECTING);
// Use HW address that matches the reservation entry in the configuration.
client.setHWAddress("aa:bb:cc:dd:ee:ff");
// Configure DHCP server.
configure(CONFIGS[4], *client.getServer());
// Make sure that DDNS is enabled.
ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
// Request Hostname option.
ASSERT_NO_THROW(client.requestOption(DHO_HOST_NAME));
// Send the DHCPDISCOVER
ASSERT_NO_THROW(client.doDiscover());
// Make sure that the server responded.
Pkt4Ptr resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
// Obtain the Hostname option sent in the response and make sure that the server
// has used the hostname reserved for this client.
OptionStringPtr hostname;
hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
ASSERT_TRUE(hostname);
EXPECT_EQ("reserved.example.org", hostname->getValue());
// Now send the DHCPREQUEST with including the Hostname option.
ASSERT_NO_THROW(client.doRequest());
resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Once again check that the Hostname is as expected.
hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
ASSERT_TRUE(hostname);
EXPECT_EQ("reserved.example.org", hostname->getValue());
}
// This test verifies that the server sends the Hostname option to the client
// with partial hostname reservation and with the global qualifying-suffix set.
TEST_F(NameDhcpv4SrvTest, hostnameReservationNoDNSQualifyingSuffix) {
Dhcp4Client client(Dhcp4Client::SELECTING);
// Use HW address that matches the reservation entry in the configuration.
client.setHWAddress("aa:bb:cc:dd:ee:ff");
// Configure DHCP server.
configure(CONFIGS[5], *client.getServer());
// Make sure that DDNS is enabled.
ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
// Include the Hostname option.
ASSERT_NO_THROW(client.includeHostname("client-name"));
// Send the DHCPDISCOVER
ASSERT_NO_THROW(client.doDiscover());
// Make sure that the server responded.
Pkt4Ptr resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
// Obtain the Hostname option sent in the response and make sure that the server
// has used the hostname reserved for this client.
OptionStringPtr hostname;
hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
ASSERT_TRUE(hostname);
EXPECT_EQ("foo-bar.example.isc.org", hostname->getValue());
// Now send the DHCPREQUEST with including the Hostname option.
ASSERT_NO_THROW(client.doRequest());
resp = client.getContext().response_;
ASSERT_TRUE(resp);
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Once again check that the Hostname is as expected.
hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
ASSERT_TRUE(hostname);
EXPECT_EQ("foo-bar.example.isc.org", hostname->getValue());
}
// Test verifies that the server properly generates a FQDN when the client
// FQDN name is blank, whether or not DDNS updates are enabled. It also
// verifies that the lease is only in the database following a DHCPREQUEST and
......
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