Parcourir la source

[3035] Process Hostname option sent by a client.

Marcin Siodelski il y a 11 ans
Parent
commit
ee888857ee
2 fichiers modifiés avec 97 ajouts et 3 suppressions
  1. 43 2
      src/bin/dhcp4/dhcp4_srv.cc
  2. 54 1
      src/bin/dhcp4/tests/fqdn_unittest.cc

+ 43 - 2
src/bin/dhcp4/dhcp4_srv.cc

@@ -809,7 +809,7 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
     // one.
     if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) {
         std::ostringstream name;
-        if (fqdn->getDomainName().empty()) {
+        if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
             name << FQDN_GENERATED_PARTIAL_NAME;
         } else {
             name << fqdn->getDomainName();
@@ -843,12 +843,43 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
 void
 Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
                                  Pkt4Ptr& answer) {
+    // Do nothing if the DNS updates are disabled.
     if (!FQDN_ENABLE_UPDATE) {
         return;
     }
 
     std::string hostname = opt_hostname->readString();
-    answer->addOption(OptionCustomPtr(new OptionCustom(*opt_hostname)));
+    unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
+    // Copy construct the hostname provided by the client. It is entirely
+    // possible that we will use the hostname option provided by the client
+    // to perform the DNS update and we will send the same option to him to
+    // indicate that we accepted this hostname.
+    OptionCustomPtr opt_hostname_resp(new OptionCustom(*opt_hostname));
+
+    // The hostname option may be unqualified or fully qualified. The lab_count
+    // holds the number of labels for the name. The number of 1 means that
+    // there is only root label "." (even for unqualified names, as the
+    // getLabelCount function treats each name as a fully qualified one).
+    // 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 only one label, it is a root. We will have to generate
+    // the whole domain name for the client.
+    if (FQDN_REPLACE_CLIENT_NAME || (label_count < 2)) {
+        std::ostringstream resp_hostname;
+        resp_hostname << FQDN_GENERATED_PARTIAL_NAME << "."
+                      << FQDN_PARTIAL_SUFFIX << ".";
+        opt_hostname_resp->writeString(resp_hostname.str());
+    // If there are two labels, it means that the client has specified
+    // the unqualified name. We have to concatenate the unqalified name
+    // with the domain name.
+    } else if (label_count == 2) {
+        std::ostringstream resp_hostname;
+        resp_hostname << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
+        opt_hostname_resp->writeString(resp_hostname.str());
+    }
+
+    answer->addOption(opt_hostname_resp);
 }
 
 void
@@ -997,6 +1028,16 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         hostname = fqdn->getDomainName();
         fqdn_fwd = fqdn->getFlag(Option4ClientFqdn::FLAG_S);
         fqdn_rev = !fqdn->getFlag(Option4ClientFqdn::FLAG_N);
+    } else {
+        OptionCustomPtr opt_hostname = boost::dynamic_pointer_cast<
+            OptionCustom>(answer->getOption(DHO_HOST_NAME));
+        if (opt_hostname) {
+            hostname = opt_hostname->readString();
+            // @todo It could be configurable what sort of updates the server
+            // is doing when Hostname option was sent.
+            fqdn_fwd = true;
+            fqdn_rev = true;
+        }
     }
 
     // Use allocation engine to pick a lease for this client. Allocation engine

+ 54 - 1
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -363,6 +363,13 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardPartialNameFqdn) {
 }
 
 // Test that server generates the fully qualified domain name for the client
+// if client supplies the unqualified name in the Hostname option.
+TEST_F(NameDhcpv4SrvTest, serverUpdateUnqualifiedHostname) {
+    Pkt4Ptr query = generatePktWithHostname(DHCPREQUEST, "myhost");
+    testProcessHostname(query, "myhost.example.com.");
+}
+
+// Test that server generates the fully qualified domain name for the client
 // if clietn supplies empty domain name.
 TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
     Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
@@ -522,7 +529,7 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
 // a different domain-name. Server should use existing lease for the second
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
-TEST_F(NameDhcpv4SrvTest, processTwoRequests) {
+TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                        Option4ClientFqdn::FLAG_E,
                                        "myhost.example.com.",
@@ -570,6 +577,52 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequests) {
                             0, subnet_->getValid());
 }
 
+// Test that client may send two requests, each carrying Hostname option with
+// a different name. Server should use existing lease for the second request
+// but modify the DNS entries for the lease according to the contents of the
+// Hostname sent in the second request.
+TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+    Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
+
+    Pkt4Ptr reply;
+    ASSERT_NO_THROW(reply = srv_->processRequest(req1));
+
+    checkResponse(reply, DHCPACK, 1234);
+
+    // Verify that there is one NameChangeRequest generated.
+    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            reply->getYiaddr().toText(), "myhost.example.com.",
+                            "00010132E91AA355CFBB753C0F0497A5A940436"
+                            "965B68B6D438D98E680BF10B09F3BCF",
+                            0, subnet_->getValid());
+
+    // Create another Request message but with a different Hostname. Server
+    // should generate two NameChangeRequests: one to remove existing entry,
+    // another one to add new entry with updated domain-name.
+    Pkt4Ptr req2 = generatePktWithHostname(DHCPREQUEST, "otherhost");
+
+    ASSERT_NO_THROW(reply = srv_->processRequest(req2));
+
+    checkResponse(reply, DHCPACK, 1234);
+
+    // There should be two NameChangeRequests. Verify that they are valid.
+    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            reply->getYiaddr().toText(),
+                            "myhost.example.com.",
+                            "00010132E91AA355CFBB753C0F0497A5A940436"
+                            "965B68B6D438D98E680BF10B09F3BCF",
+                            0, subnet_->getValid());
+
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                            reply->getYiaddr().toText(),
+                            "otherhost.example.com.",
+                            "000101A5AEEA7498BD5AD9D3BF600E49FF39A7E3"
+                            "AFDCE8C3D0E53F35CC584DD63C89CA",
+                            0, subnet_->getValid());
+}
+
 // Test that when the Release message is sent for the previously acquired
 // lease, then server genenerates a NameChangeRequest to remove the entries
 // corresponding to the lease being released.