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
 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

+ 71 - 14
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<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

+ 135 - 0
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<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