Browse Source

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

Marcin Siodelski 8 years ago
parent
commit
022c43ecdb
3 changed files with 212 additions and 14 deletions
  1. 6 0
      src/bin/dhcp4/dhcp4_messages.mes
  2. 71 14
      src/bin/dhcp4/dhcp4_srv.cc
  3. 135 0
      src/bin/dhcp4/tests/fqdn_unittest.cc

+ 6 - 0
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
 transaction identification information. The second argument specifies
 the leased address.
 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
 % 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
 A debug message including the detailed data about the packet being sent
 to the client. The first argument contains the client and the transaction
 to the client. The first argument contains the client and the transaction

+ 71 - 14
src/bin/dhcp4/dhcp4_srv.cc

@@ -1400,18 +1400,79 @@ Dhcpv4Srv::processHostnameOption(Dhcpv4Exchange& ex) {
     // Fetch D2 configuration.
     // Fetch D2 configuration.
     D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
     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 =
     D2ClientConfig::ReplaceClientNameMode replace_name_mode =
             d2_mgr.getD2ClientConfig()->getReplaceClientNameMode();
             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 we don't have a hostname then either we'll supply it or do nothing.
     if (!opt_hostname) {
     if (!opt_hostname) {
         // If we're configured to supply it then add it to the response.
         // 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
     // By checking the number of labels present in the hostname we may infer
     // whether client has sent the fully qualified or unqualified hostname.
     // 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.
         // Set to root domain to signal later on that we should replace it.
         // DHO_HOST_NAME is a string option which cannot be empty.
         // DHO_HOST_NAME is a string option which cannot be empty.
         /// @todo We may want to reconsider whether it is appropriate for the
         /// @todo We may want to reconsider whether it is appropriate for the

+ 135 - 0
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -108,6 +108,60 @@ const char* CONFIGS[] = {
             "\"enable-updates\": true,"
             "\"enable-updates\": true,"
             "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
             "\"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
 // 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
 // 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
 // verifies that the lease is only in the database following a DHCPREQUEST and