From: Marcin Siodelski Date: Mon, 12 Sep 2016 14:47:04 +0000 (+0200) Subject: [5005] Send reserved hostaname even if DNS updates disabled. X-Git-Tag: trac5006_base~18^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=022c43ecdbd7326268b00359debb411912e54a21;p=thirdparty%2Fkea.git [5005] Send reserved hostaname even if DNS updates disabled. --- diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index 9066e4bbf6..d2e1cc17cb 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -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 diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 33a2e38058..291d80de41 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -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 + (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(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 + (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& + 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 - (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 diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc index 3d02b65418..afb469cd46 100644 --- a/src/bin/dhcp4/tests/fqdn_unittest.cc +++ b/src/bin/dhcp4/tests/fqdn_unittest.cc @@ -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(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(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(resp->getType())); + + // Once again check that the Hostname is as expected. + hostname = boost::dynamic_pointer_cast(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(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(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(resp->getType())); + + // Once again check that the Hostname is as expected. + hostname = boost::dynamic_pointer_cast(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