Browse Source

[4765] DHCPv6 server now uses client classes specified in HR db.

Marcin Siodelski 8 years ago
parent
commit
5db1edfb8e

+ 24 - 1
src/bin/dhcp6/dhcp6_srv.cc

@@ -67,6 +67,8 @@
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 #include <boost/tokenizer.hpp>
 #include <boost/tokenizer.hpp>
 #include <boost/algorithm/string/erase.hpp>
 #include <boost/algorithm/string/erase.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/split.hpp>
 
 
 #include <stdlib.h>
 #include <stdlib.h>
 #include <time.h>
 #include <time.h>
@@ -2273,6 +2275,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(solicit, ctx);
     initContext(solicit, ctx);
+    setReservedClientClasses(solicit, ctx);
 
 
     Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
     Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
 
 
@@ -2318,6 +2321,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(request, ctx);
     initContext(request, ctx);
+    setReservedClientClasses(request, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
 
@@ -2344,6 +2348,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(renew, ctx);
     initContext(renew, ctx);
+    setReservedClientClasses(renew, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
 
 
@@ -2370,6 +2375,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(rebind, ctx);
     initContext(rebind, ctx);
+    setReservedClientClasses(rebind, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
 
 
@@ -2396,6 +2402,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(confirm, ctx);
     initContext(confirm, ctx);
+    setReservedClientClasses(confirm, ctx);
 
 
     // Get IA_NAs from the Confirm. If there are none, the message is
     // Get IA_NAs from the Confirm. If there are none, the message is
     // invalid and must be discarded. There is nothing more to do.
     // invalid and must be discarded. There is nothing more to do.
@@ -2488,6 +2495,7 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(release, ctx);
     initContext(release, ctx);
+    setReservedClientClasses(release, ctx);
 
 
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
 
 
@@ -2516,6 +2524,7 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(decline, ctx);
     initContext(decline, ctx);
+    setReservedClientClasses(decline, ctx);
 
 
     // Copy client options (client-id, also relay information if present)
     // Copy client options (client-id, also relay information if present)
     copyClientOptions(decline, reply);
     copyClientOptions(decline, reply);
@@ -2796,6 +2805,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
     // Let's create a simplified client context here.
     // Let's create a simplified client context here.
     AllocEngine::ClientContext6 ctx;
     AllocEngine::ClientContext6 ctx;
     initContext(inf_request, ctx);
     initContext(inf_request, ctx);
+    setReservedClientClasses(inf_request, ctx);
 
 
     // Create a Reply packet, with the same trans-id as the client's.
     // Create a Reply packet, with the same trans-id as the client's.
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
@@ -2907,11 +2917,24 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
                 .arg("get exception?");
                 .arg("get exception?");
         }
         }
     }
     }
+}
+
+void
+Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt,
+                                    const AllocEngine::ClientContext6& ctx) {
+    if (ctx.host_ && pkt) {
+        BOOST_FOREACH(const std::string& client_class,
+                      ctx.host_->getClientClasses6()) {
+            pkt->addClass(client_class);
+        }
+    }
 
 
+    const ClientClasses& classes = pkt->getClasses();
     if (!classes.empty()) {
     if (!classes.empty()) {
+        std::string joined_classes = boost::algorithm::join(classes, ", ");
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
             .arg(pkt->getLabel())
             .arg(pkt->getLabel())
-            .arg(classes);
+            .arg(joined_classes);
     }
     }
 }
 }
 
 

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

@@ -629,6 +629,13 @@ protected:
     /// @param pkt packet to be classified
     /// @param pkt packet to be classified
     void classifyPacket(const Pkt6Ptr& pkt);
     void classifyPacket(const Pkt6Ptr& pkt);
 
 
+    /// @brief Assigns classes retrieved from host reservation database.
+    ///
+    /// @param pkt Pointer to the packet to which classes will be assigned.
+    /// @param ctx Reference to the client context.
+    void setReservedClientClasses(const Pkt6Ptr& pkt,
+                                  const AllocEngine::ClientContext6& ctx);
+
     /// @brief Attempts to get a MAC/hardware address using configred sources
     /// @brief Attempts to get a MAC/hardware address using configred sources
     ///
     ///
     /// Tries to extract MAC/hardware address information from the packet
     /// Tries to extract MAC/hardware address information from the packet

+ 191 - 47
src/bin/dhcp6/tests/classify_unittests.cc

@@ -14,11 +14,13 @@
 #include <dhcp/opaque_data_tuple.h>
 #include <dhcp/opaque_data_tuple.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor_class.h>
 #include <dhcp/option_vendor_class.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp6/tests/dhcp6_client.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <boost/pointer_cast.hpp>
 #include <string>
 #include <string>
 
 
 using namespace isc;
 using namespace isc;
@@ -31,21 +33,19 @@ namespace {
 /// @brief Set of JSON configurations used by the classification unit tests.
 /// @brief Set of JSON configurations used by the classification unit tests.
 ///
 ///
 /// - Configuration 0:
 /// - Configuration 0:
-///   - one subnet 3000::/32 used on eth0 interface
-///   - prefixes of length 64, delegated from the pool: 2001:db8:3::/48
-///   - the delegated prefix was intentionally selected to not match the
-///     subnet prefix, to test that the delegated prefix doesn't need to
-///     match the subnet prefix
-///
-/// - Configuration 1:
-///   - two subnets 2001:db8:1::/48 and 2001:db8:2::/48
-///   - first subnet assigned to interface eth0, another one assigned to eth1
-///   - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10,
-///     where X is 1 or 2
-///   - enables Rapid Commit for the first subnet and disables for the second
-///     one
-///   - DNS updates enabled
-///
+///   - Specifies 3 classes: 'router', 'reserved-class1' and 'reserved-class2'.
+///   - 'router' class is assigned when the client sends option 1234 (string)
+///      equal to 'foo'.
+///   - The other two classes are reserved for the client having
+///     DUID '01:02:03:04'
+///   - Class 'router' includes option 'ipv6-forwarding'.
+///   - Class 'reserved-class1' includes option DNS servers.
+///   - Class 'reserved-class2' includes option NIS servers.
+///   - All three options are sent when client has reservations for the
+///     'reserved-class1', 'reserved-class2' and sends option 1234 with
+///     the 'foo' value.
+///   - There is one subnet specified 2001:db8:1::/48 with pool of
+///     IPv6 addresses.
 const char* CONFIGS[] = {
 const char* CONFIGS[] = {
     // Configuration 0
     // Configuration 0
     "{ \"interfaces-config\": {"
     "{ \"interfaces-config\": {"
@@ -54,46 +54,64 @@ const char* CONFIGS[] = {
         "\"preferred-lifetime\": 3000,"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pd-pools\": ["
-        "        { \"prefix\": \"2001:db8:3::\", "
-        "          \"prefix-len\": 48, "
-        "          \"delegated-len\": 64"
-        "        } ],"
-        "    \"subnet\": \"3000::/32\", "
-        "    \"interface-id\": \"\","
-        "    \"interface\": \"eth0\""
-        " } ],"
-        "\"valid-lifetime\": 4000 }",
-
-// Configuration 1
-    "{ \"interfaces-config\": {"
-        "  \"interfaces\": [ \"*\" ]"
+        "\"option-def\": [ "
+        "{"
+        "    \"name\": \"host-name\","
+        "    \"code\": 1234,"
+        "    \"type\": \"string\""
         "},"
         "},"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ],"
+        "{"
+        "    \"name\": \"ipv6-forwarding\","
+        "    \"code\": 2345,"
+        "    \"type\": \"boolean\""
+        "} ],"
+        "\"client-classes\": ["
+        "{"
+        "   \"name\": \"router\","
+        "   \"test\": \"option[host-name].text == 'foo'\","
+        "    \"option-data\": ["
+        "    {"
+        "        \"name\": \"ipv6-forwarding\", "
+        "        \"data\": \"true\""
+        "    } ]"
+        "},"
+        "{"
+        "   \"name\": \"reserved-class1\","
+        "   \"option-data\": ["
+        "   {"
+        "       \"name\": \"dns-servers\","
+        "       \"data\": \"2001:db8:1::50\""
+        "   }"
+        "   ]"
+        "},"
+        "{"
+        "   \"name\": \"reserved-class2\","
+        "   \"option-data\": ["
+        "   {"
+        "       \"name\": \"nis-servers\","
+        "       \"data\": \"2001:db8:1::100\""
+        "   }"
+        "   ]"
+        "}"
+        "],"
+        "\"subnet6\": [ "
+        "{   \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
         "    \"subnet\": \"2001:db8:1::/48\", "
         "    \"subnet\": \"2001:db8:1::/48\", "
-        "    \"interface\": \"eth0\","
-        "    \"rapid-commit\": True"
-        " },"
-        " {"
-        "    \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ],"
-        "    \"subnet\": \"2001:db8:2::/48\", "
         "    \"interface\": \"eth1\","
         "    \"interface\": \"eth1\","
-        "    \"rapid-commit\": False"
+        "    \"reservations\": ["
+        "    {"
+        "        \"duid\": \"01:02:03:04\","
+        "        \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]"
+        "    } ]"
         " } ],"
         " } ],"
-        "\"valid-lifetime\": 4000,"
-        " \"dhcp-ddns\" : {"
-        "     \"enable-updates\" : True, "
-        "     \"qualifying-suffix\" : \"example.com\" }"
-    "}"
+        "\"valid-lifetime\": 4000 }"
 };
 };
 
 
 /// @brief Test fixture class for testing client classification by the
 /// @brief Test fixture class for testing client classification by the
 /// DHCPv6 server.
 /// DHCPv6 server.
+///
+/// @todo There are numerous tests not using Dhcp6Client class. They should be
+/// migrated to use it one day.
 class ClassifyTest : public Dhcpv6SrvTest {
 class ClassifyTest : public Dhcpv6SrvTest {
 public:
 public:
     /// @brief Constructor.
     /// @brief Constructor.
@@ -104,6 +122,60 @@ public:
           iface_mgr_test_config_(true) {
           iface_mgr_test_config_(true) {
     }
     }
 
 
+    /// @brief Verify values of options returned by the server when the server
+    /// uses configuration with index 0.
+    ///
+    /// @param config Reference to DHCP client's configuration received.
+    /// @param ip_forwarding Expected value of IP forwarding option. This option
+    /// is expected to always be present.
+    /// @param dns_servers String holding an address carried within DNS
+    /// servers option. If this value is empty, the option is expected to not
+    /// be included in the response.
+    /// @param nis_servers String holding an address carried within NIS
+    /// servers option. If this value is empty, the option is expected to not
+    /// be included in the response.
+    void verifyConfig0Options(const Dhcp6Client::Configuration& config,
+                              const uint8_t ip_forwarding = 1,
+                              const std::string& dns_servers = "",
+                              const std::string& nis_servers = "") {
+        // IP forwarding option should always exist.
+        OptionPtr ip_forwarding_opt = config.findOption(2345);
+        ASSERT_TRUE(ip_forwarding_opt);
+        // The option comprises 2 bytes of option code, 2 bytes of option length,
+        // and a single 1 byte value. This makes it 5 bytes of a total length.
+        ASSERT_EQ(5, ip_forwarding_opt->len());
+        ASSERT_EQ(static_cast<int>(ip_forwarding),
+                  static_cast<int>(ip_forwarding_opt->getUint8()));
+
+        // DNS servers.
+        Option6AddrLstPtr dns_servers_opt = boost::dynamic_pointer_cast<
+            Option6AddrLst>(config.findOption(D6O_NAME_SERVERS));
+        if (!dns_servers.empty()) {
+            ASSERT_TRUE(dns_servers_opt);
+            Option6AddrLst::AddressContainer addresses = dns_servers_opt->getAddresses();
+            // For simplicity, we expect only a single address.
+            ASSERT_EQ(1, addresses.size());
+            EXPECT_EQ(dns_servers, addresses[0].toText());
+
+        } else {
+            EXPECT_FALSE(dns_servers_opt);
+        }
+
+        // NIS servers.
+        Option6AddrLstPtr nis_servers_opt = boost::dynamic_pointer_cast<
+            Option6AddrLst>(config.findOption(D6O_NIS_SERVERS));
+        if (!nis_servers.empty()) {
+            ASSERT_TRUE(nis_servers_opt);
+            Option6AddrLst::AddressContainer addresses = nis_servers_opt->getAddresses();
+            // For simplicity, we expect only a single address.
+            ASSERT_EQ(1, addresses.size());
+            EXPECT_EQ(nis_servers, addresses[0].toText());
+
+        } else {
+            EXPECT_FALSE(nis_servers_opt);
+        }
+    }
+
     /// @brief Interface Manager's fake configuration control.
     /// @brief Interface Manager's fake configuration control.
     IfaceMgrTestConfig iface_mgr_test_config_;
     IfaceMgrTestConfig iface_mgr_test_config_;
 };
 };
@@ -601,5 +673,77 @@ TEST_F(ClassifyTest, relayOverrideAndClientClass) {
     EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
     EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
 }
 }
 
 
+// This test checks that it is possible to specify static reservations for
+// client classes.
+TEST_F(ClassifyTest, clientClassesInHostReservations) {
+    Dhcp6Client client;
+    // Initially use a DUID for which there are no reservations. As a result,
+    // the client should be assigned a single class "router".
+    client.setDUID("01:02:03:05");
+    client.setInterface("eth1");
+    client.requestAddress();
+    // Request all options we may potentially get. Otherwise, the server will
+    // not return them, even when the client is assigned to the classes for
+    // which these options should be sent.
+    client.requestOption(2345);
+    client.requestOption(D6O_NAME_SERVERS);
+    client.requestOption(D6O_NIS_SERVERS);
+
+    ASSERT_NO_THROW(configure(CONFIGS[0], *client.getServer()));
+
+    // Adding this option to the client's message will cause the client to
+    // belong to the 'router' class.
+    OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+    client.addExtraOption(hostname);
+
+    // Send a message to the server.
+    ASSERT_NO_THROW(client.doSolicit(true));
+
+    // IP forwarding should be present, but DNS and NIS servers should not.
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_));
+
+    // Modify the DUID of our client to the one for which class reservations
+    // have been made.
+    client.setDUID("01:02:03:04");
+    ASSERT_NO_THROW(client.doSolicit(true));
+
+    // This time, the client should obtain options from all three classes.
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+
+    // This should also work for Request case.
+    ASSERT_NO_THROW(client.doSARR());
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+
+    // Renew case.
+    ASSERT_NO_THROW(client.doRenew());
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+
+    // Rebind case.
+    ASSERT_NO_THROW(client.doRebind());
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+
+    // Confirm case. This must be before Information-request because the
+    // client must have an address to confirm from one of the transactions
+    // involving address assignment, i.e. Request, Renew or Rebind.
+    ASSERT_NO_THROW(client.doConfirm());
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+
+    // Information-request case.
+    ASSERT_NO_THROW(client.doInfRequest());
+    ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1,
+                                                 "2001:db8:1::50",
+                                                 "2001:db8:1::100"));
+}
+
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 1 - 1
src/bin/dhcp6/tests/dhcp6_client.cc

@@ -561,7 +561,7 @@ Dhcp6Client::doConfirm() {
     // Set the global status code to default: success and not received.
     // Set the global status code to default: success and not received.
     config_.resetGlobalStatusCode();
     config_.resetGlobalStatusCode();
     if (context_.response_) {
     if (context_.response_) {
-        config_.resetGlobalStatusCode();
+        config_.options_.clear();
         applyRcvdConfiguration(context_.response_);
         applyRcvdConfiguration(context_.response_);
     }
     }
 }
 }

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_client.h

@@ -110,6 +110,7 @@ public:
             leases_.clear();
             leases_.clear();
             status_codes_.clear();
             status_codes_.clear();
             resetGlobalStatusCode();
             resetGlobalStatusCode();
+            options_.clear();
         }
         }
 
 
         /// @brief Clears global status code.
         /// @brief Clears global status code.