Browse Source

[3036] Added implementation for the FQDN option processing.

Marcin Siodelski 11 years ago
parent
commit
8c1e879aeb

+ 131 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -20,6 +20,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
@@ -56,6 +57,34 @@ using namespace std;
 namespace isc {
 namespace dhcp {
 
+namespace {
+
+// The following constants describe server's behavior with respect to the
+// DHCPv6 Client FQDN Option sent by a client. They will be removed
+// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
+
+// Should server always include the FQDN option in its response, regardless
+// if it has been requested in ORO (Disabled).
+const bool FQDN_ALWAYS_INCLUDE = false;
+// Enable AAAA RR update delegation to the client (Disabled).
+const bool FQDN_ALLOW_CLIENT_UPDATE = false;
+// Globally enable updates (Enabled).
+const bool FQDN_ENABLE_UPDATE = true;
+// The partial name generated for the client if empty name has been
+// supplied.
+const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
+// Do update, even if client requested no updates with N flag (Disabled).
+const bool FQDN_OVERRIDE_NO_UPDATE = false;
+// Server performs an update when client requested delegation (Enabled).
+const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
+// The fully qualified domain-name suffix if partial name provided by
+// a client.
+const char* FQDN_PARTIAL_SUFFIX = "example.com";
+// Should server replace the domain-name supplied by the client (Disabled).
+const bool FQDN_REPLACE_CLIENT_NAME = false;
+
+}
+
 /// @brief file name of a server-id file
 ///
 /// Server must store its duid in persistent storage that must not change
@@ -631,6 +660,99 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     }
 }
 
+void
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+    // Get Client FQDN Option from the client's message. If this option hasn't
+    // been included, do nothing.
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
+    if (!fqdn) {
+        return;
+    }
+
+    // Prepare the FQDN option which will be included in the response to
+    // the client.
+    Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
+    // RFC 4704, section 6. - all flags set to 0.
+    fqdn_resp->resetFlags();
+
+    // Conditions when N flag has to be set to indicate that server will not
+    // perform DNS updates:
+    // 1. Updates are globally disabled,
+    // 2. Client requested no update and server respects it,
+    // 3. Client requested that the AAAA update is delegated to the client but
+    //    server neither respects delegation of updates nor it is configured
+    //    to send update on its own when client requested delegation.
+    if (!FQDN_ENABLE_UPDATE ||
+        (fqdn->getFlag(Option6ClientFqdn::FLAG_N) && !FQDN_OVERRIDE_NO_UPDATE) ||
+        (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) && !FQDN_ALLOW_CLIENT_UPDATE &&
+         !FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);
+
+    // Conditions when S flag is set to indicate that server will perform
+    // DNS update on its own:
+    // 1. Client requested that server performs DNS update and DNS updates are
+    //    globally enabled
+    // 2. Client requested that server delegates AAAA update to the client but
+    //    server doesn't respect delegation and it is configured to perform
+    //    an update on its own when client requested delegation.
+    } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
+               (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
+                !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
+    }
+
+    // Server MUST set the O flag if it has overridden the client's setting
+    // of S flag.
+    if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
+        fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
+    }
+
+    // If client supplied partial or empty domain-name, server should
+    // generate one.
+    if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
+        std::ostringstream name;
+        if (fqdn->getDomainName().empty()) {
+            name << FQDN_GENERATED_PARTIAL_NAME;
+        } else {
+            name << fqdn->getDomainName();
+        }
+        name << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+    // Server may be configured to replace a name supplied by a client,
+    // even if client supplied fully qualified domain-name.
+    } else if (FQDN_REPLACE_CLIENT_NAME) {
+        std::ostringstream name;
+        name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+    }
+
+    // Server sends back the FQDN option to the client if client has requested
+    // it using Option Request Option. However, server may be configured to
+    // send the FQDN option in its response, regardless whether client requested
+    // it or not.
+    bool include_fqdn = FQDN_ALWAYS_INCLUDE;
+    if (!include_fqdn) {
+        OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
+            OptionUint16Array>(question->getOption(D6O_ORO));
+        if (oro) {
+            const std::vector<uint16_t>& values = oro->getValues();
+            for (int i = 0; i < values.size(); ++i) {
+                if (values[i] == D6O_CLIENT_FQDN) {
+                    include_fqdn = true;
+                }
+            }
+        }
+    }
+
+    if (include_fqdn) {
+        answer->addOption(fqdn_resp);
+    }
+}
+
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                        Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
@@ -1025,6 +1147,8 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
 
     assignLeases(solicit, advertise);
 
+    processClientFqdn(solicit, advertise);
+
     return (advertise);
 }
 
@@ -1041,6 +1165,8 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
 
     assignLeases(request, reply);
 
+    processClientFqdn(request, reply);
+
     return (reply);
 }
 
@@ -1055,6 +1181,8 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendDefaultOptions(renew, reply);
     appendRequestedOptions(renew, reply);
 
+    processClientFqdn(renew, reply);
+
     renewLeases(renew, reply);
 
     return reply;
@@ -1086,6 +1214,9 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
 
     releaseLeases(release, reply);
 
+    // @todo If client sent a release and we should remove outstanding
+    // DNS records.
+
     return reply;
 }
 

+ 21 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -264,6 +264,27 @@ protected:
     /// @param answer server's message (IA_NA options will be added here)
     void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
+    /// @brief Processes Client FQDN Option.
+    ///
+    /// This function retrieves DHCPv6 Client FQDN %Option (if any) from the
+    /// packet sent by a client and takes necessary actions upon this option.
+    /// Received option comprises flags field which controls what DNS updates
+    /// server should do. Server may override client's preference based on
+    /// the current configuration. Server indicates that it has overridden
+    /// the preference by storing DHCPv6 Client Fqdn %Option with the
+    /// appropriate flags in the response to a client. This option is also
+    /// used to communicate the client's domain-name which should be sent
+    /// to the DNS in the update. Again, server may act upon the received
+    /// domain-name, i.e. if the provided domain-name is partial it should
+    /// generate the fully qualified domain-name.
+    ///
+    /// All the logic required to form appropriate answer to the client is
+    /// held in this function.
+    ///
+    /// @param question Client's message.
+    /// @param answer Server's response to the client.
+    void processClientFqdn(const Pkt6Ptr& question, Pkt6Ptr& answer);
+
     /// @brief Attempts to renew received addresses
     ///
     /// It iterates through received IA_NA options and attempts to renew

+ 142 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -21,6 +21,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_int_array.h>
@@ -33,6 +34,7 @@
 #include <util/buffer.h>
 #include <util/range_utilities.h>
 
+#include <boost/pointer_cast.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <unistd.h>
@@ -52,6 +54,10 @@ using namespace std;
 // Maybe it should be isc::test?
 namespace {
 
+const uint8_t FQDN_FLAG_S = 0x1;
+const uint8_t FQDN_FLAG_O = 0x2;
+const uint8_t FQDN_FLAG_N = 0x4;
+
 class NakedDhcpv6Srv: public Dhcpv6Srv {
     // "naked" Interface Manager, exposes internal members
 public:
@@ -70,6 +76,7 @@ public:
     using Dhcpv6Srv::processRequest;
     using Dhcpv6Srv::processRenew;
     using Dhcpv6Srv::processRelease;
+    using Dhcpv6Srv::processClientFqdn;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
@@ -225,6 +232,8 @@ public:
         EXPECT_EQ(expected_transid, rsp->getTransid());
     }
 
+    // Generates client's packet holding an FQDN option.
+
     virtual ~NakedDhcpv6SrvTest() {
         // Let's clean up if there is such a file.
         unlink(DUID_FILE);
@@ -329,6 +338,95 @@ public:
     Pool6Ptr pool_;
 };
 
+class FqdnDhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+    FqdnDhcpv6SrvTest() {
+    }
+
+    virtual ~FqdnDhcpv6SrvTest() {
+    }
+
+    Pkt6Ptr generatePktWithFqdn(uint8_t msg_type,
+                                const uint8_t fqdn_flags,
+                                const std::string& fqdn_domain_name,
+                                const Option6ClientFqdn::DomainNameType
+                                fqdn_type,
+                                const bool include_oro,
+                                OptionPtr srvid = OptionPtr()) {
+        Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+        pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+        pkt->addOption(generateIA(234, 1500, 3000));
+        OptionPtr clientid = generateClientId();
+        pkt->addOption(clientid);
+        if (srvid && (msg_type != DHCPV6_SOLICIT)) {
+            pkt->addOption(srvid);
+        }
+
+        pkt->addOption(OptionPtr(new Option6ClientFqdn(fqdn_flags,
+                                                       fqdn_domain_name,
+                                                       fqdn_type)));
+
+        if (include_oro) {
+            OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6,
+                                                           D6O_ORO));
+            oro->addValue(D6O_CLIENT_FQDN);
+            pkt->addOption(oro);
+        }
+
+        return (pkt);
+    }
+
+    // Returns an instance of the option carrying FQDN.
+    Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
+        return (boost::dynamic_pointer_cast<Option6ClientFqdn>
+                (pkt->getOption(D6O_CLIENT_FQDN)));
+    }
+
+    void testFqdn(const uint16_t msg_type,
+                  const bool use_oro,
+                  const uint8_t in_flags,
+                  const std::string& in_domain_name,
+                  const Option6ClientFqdn::DomainNameType in_domain_type,
+                  const uint8_t exp_flags,
+                  const std::string& exp_domain_name) {
+        NakedDhcpv6Srv srv(0);
+        Pkt6Ptr question = generatePktWithFqdn(msg_type,
+                                               in_flags,
+                                               in_domain_name,
+                                               in_domain_type,
+                                               use_oro);
+        ASSERT_TRUE(getClientFqdnOption(question));
+
+        Pkt6Ptr answer;
+        if (msg_type == DHCPV6_SOLICIT) {
+            answer.reset(new Pkt6(DHCPV6_ADVERTISE, 1234));
+
+        } else {
+            answer.reset(new Pkt6(DHCPV6_REPLY, 1234));
+
+        }
+
+        ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
+
+        Option6ClientFqdnPtr answ_fqdn = getClientFqdnOption(answer);
+        ASSERT_TRUE(answ_fqdn);
+
+        const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0 ?
+            true : false;
+        const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0 ?
+            true : false;
+        const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0 ?
+            true : false;
+
+        EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N));
+        EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S));
+        EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
+
+        EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
+        EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+    }
+};
+
 // This test verifies that incoming SOLICIT can be handled properly when
 // there are no subnets configured.
 //
@@ -1756,6 +1854,50 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
     EXPECT_EQ(duid1_text, text);
 }
 
+// A set of tests verifying server's behaviour when it receives the DHCPv6
+// Client Fqdn Option.
+// @todo: Extend these tests once appropriate configuration parameters are
+// implemented (ticket #3034).
+
+// Test server's response when client requests that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
+    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost.example.com",
+             Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client provides partial domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
+    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost",
+             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client provides empty domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
+    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "",
+             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client requests no DNS update.
+TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
+    testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_N, "myhost.example.com",
+             Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
+             "myhost.example.com.");
+}
+
+// Test server's response when client requests that server delegates the AAAA
+// update to the client and this delegation is not allowed.
+TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
+    SCOPED_TRACE("Client AAAA Update is not allowed");
+    testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
+             Option6ClientFqdn::FULL, FQDN_FLAG_S | FQDN_FLAG_O,
+             "myhost.example.com.");
+}
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 

+ 9 - 1
src/lib/dhcp/option6_client_fqdn.cc

@@ -75,8 +75,11 @@ Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
 Option6ClientFqdnImpl::
 Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
     : flags_(source.flags_),
-      domain_name_(new isc::dns::Name(*source.domain_name_)),
+      domain_name_(),
       domain_name_type_(source.domain_name_type_) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+    }
 }
 
 Option6ClientFqdnImpl&
@@ -260,6 +263,11 @@ Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
     impl_->flags_ = new_flag;
 }
 
+void
+Option6ClientFqdn::resetFlags() {
+    impl_->flags_ = 0;
+}
+
 std::string
 Option6ClientFqdn::getDomainName() const {
     if (impl_->domain_name_) {

+ 3 - 0
src/lib/dhcp/option6_client_fqdn.h

@@ -170,6 +170,9 @@ public:
     /// set (true), or cleared (false).
     void setFlag(const Flag flag, const bool set);
 
+    /// @brief Sets the flag field value to 0.
+    void resetFlags();
+
     /// @brief Returns the domain-name in the text format.
     ///
     /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).

+ 25 - 0
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -392,6 +392,31 @@ TEST(Option6ClientFqdnTest, setFlag) {
                  InvalidFqdnOptionFlags);
 }
 
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option6ClientFqdnTest, resetFlags) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
+                                           Option6ClientFqdn::FLAG_O,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Check that flags we set in the constructor are set.
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+    option->resetFlags();
+
+    // After reset, all flags should be 0.
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+}
+
 // This test verifies that current domain-name can be replaced with a new
 // domain-name.
 TEST(Option6ClientFqdnTest, setDomainName) {