Browse Source

[3705] RSOO support added, unit-tests implemented.

Tomek Mrugalski 10 years ago
parent
commit
20f8fca7e1
3 changed files with 251 additions and 0 deletions
  1. 58 0
      src/bin/dhcp6/dhcp6_srv.cc
  2. 10 0
      src/bin/dhcp6/dhcp6_srv.h
  3. 183 0
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

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

@@ -444,6 +444,19 @@ bool Dhcpv6Srv::run() {
         }
 
         if (rsp) {
+
+            // Process relay-supplied options. It is important to call this very
+            // late in the process, because we now have all the options the
+            // server wanted to send already set. This is important, because
+            // RFC6422, section 6 states:
+            //
+            //   The server SHOULD discard any options that appear in the RSOO
+            //   for which it already has one or more candidates.
+            //
+            // So we ignore any RSOO options if there's an option with the same
+            // code already present.
+            processRSOO(query, rsp);
+
             rsp->setRemoteAddr(query->getRemoteAddr());
             rsp->setLocalAddr(query->getLocalAddr());
 
@@ -2718,5 +2731,50 @@ Daemon::getVersion(bool extended) {
     return (tmp.str());
 }
 
+void Dhcpv6Srv::processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp) {
+
+    if (query->relay_info_.empty()) {
+        // RSOO is inserted by relay agents, nothing to do here if it's
+        // a direct message.
+        return;
+    }
+
+    // Get the global options info. We'll use it to check whether an
+    // option is RSOO-enabled or not.
+    ConstCfgOptionPtr global_opts = CfgMgr::instance().getCurrentCfg()->
+        getCfgOption();
+
+    // Let's get over all relays (encapsulation levels). We need to do
+    // it in the same order as the client packet traversed the relays.
+    for (int i = query->relay_info_.size(); i > 0 ; --i) {
+        OptionPtr rsoo_container = query->getRelayOption(D6O_RSOO, i - 1);
+        if (!rsoo_container) {
+            // No relay-supplied options by this relay? Ok, carry on.
+            continue;
+        }
+
+        // There are RSOO options. Let's get through them one by one
+        // and if it's RSOO-enabled and there's no such option provided yet,
+        // copy it to the server's response
+        const OptionCollection& rsoo = rsoo_container->getOptions();
+        for (OptionCollection::const_iterator opt = rsoo.begin(); opt != rsoo.end();
+             ++opt) {
+            if (!global_opts->isRSOOEnabled(opt->second->getType())) {
+                // We didn't copy this option, because it's not RSOO-enabled.
+                continue;
+            }
+
+            if (rsp->getOption(opt->second->getType())) {
+                // There is such an option in the server's response already,
+                // we'll skip relay's option
+                continue;
+            }
+
+            // All checks went ok, let's add this option.
+            rsp->addOption(opt->second);
+        }
+    }
+}
+
 };
 };

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

@@ -606,6 +606,16 @@ protected:
     /// @return HWaddr pointer (or NULL if configured methods fail)
     static HWAddrPtr getMAC(const Pkt6Ptr& pkt);
 
+    /// @brief Processes Relay-supplied options, if present
+    ///
+    /// This method implements RFC6422. It checks if there are any RSOO options
+    /// inserted by the relay agents in the query message. If there are, they
+    /// are copied over to the response if the met the following criteria:
+    /// - the option is marked as RSOO-enabled (see relay-supplied-options
+    ///   configuration parameter)
+    /// - there is no such option provided by the server)
+    void processRSOO(const Pkt6Ptr& query, const Pkt6Ptr& rsp);
+
     /// @brief this is a prefix added to the contend of vendor-class option
     ///
     /// If incoming packet has a vendor class option, its content is

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

@@ -40,6 +40,7 @@
 #include <hooks/server_hooks.h>
 
 #include <dhcp6/tests/dhcp6_test_utils.h>
+#include <dhcp6/tests/dhcp6_client.h>
 #include <dhcp/tests/pkt_captures.h>
 #include <config/ccsession.h>
 #include <boost/pointer_cast.hpp>
@@ -2094,6 +2095,188 @@ TEST_F(Dhcpv6SrvTest, relayOverrideAndClientClass) {
     EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol));
 }
 
+/// @brief Creates RSOO option with suboptions
+///
+/// Creates Relay-Supplied Options option that includes nested options. The
+/// codes of those nested options are specified in codes parameter. Content of
+/// the options is controlled with payload parameter. When it is zero, option
+/// code will be used (e.g. option 100 will contain repeating bytes of value 100).
+/// When non-zero is used, payload will used. Each suboption is always set to
+/// arbitrary chosen value of 10.
+///
+/// @param codes a vector of option codes to be created
+/// @param payload specified payload (0 = fill payload with repeating option code)
+/// @return RSOO with nested options
+OptionPtr createRSOO(const std::vector<uint16_t> codes, uint8_t payload = 0) {
+    OptionDefinitionPtr def = LibDHCP::getOptionDef(Option::V6, D6O_RSOO);
+    if (!def) {
+        isc_throw(BadValue, "Can't find RSOO definition");
+    }
+    OptionPtr rsoo_container(new OptionCustom(*def, Option::V6));
+
+    for (int i = 0; i < codes.size(); ++i) {
+        OptionBuffer buf(10, payload ? payload : codes[i]); // let's make the option 10 bytes long
+        rsoo_container->addOption(OptionPtr(new Option(Option::V6, codes[i], buf)));
+    }
+
+    return (rsoo_container);
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly,
+// i.e. it includes in its response the options that are inserted by the relay.
+// The server must do this only for options that are RSOO-enabled.
+TEST_F(Dhcpv6SrvTest, rsoo) {
+
+    Dhcp6Client client;
+
+    string config =
+        "{"
+        "    \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+        "    \"preferred-lifetime\": 3000,"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"subnet6\": [ { "
+        "        \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+        "        \"subnet\": \"2001:db8::/48\" "
+        "     } ],"
+        "    \"valid-lifetime\": 4000"
+        "}";
+
+    EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+    // Now pretend the packet came via one relay.
+    Pkt6::RelayInfo relay;
+    relay.msg_type_ = DHCPV6_RELAY_FORW;
+    relay.hop_count_ = 1;
+    relay.linkaddr_ = IOAddress("2001:db8::1");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo1;
+    rsoo1.push_back(109);
+    rsoo1.push_back(110);
+    rsoo1.push_back(111);
+
+    // The relay will request echoing back 3 options: 109, 110, 111.
+    // The configuration allows echoing back only 110.
+    OptionPtr opt = createRSOO(rsoo1);
+    relay.options_.insert(make_pair(opt->getType(), opt));
+    client.relay_info_.push_back(relay);
+
+    client.doSARR();
+
+    // Option 110 should be copied to the client
+    EXPECT_NE(client.config_.options_.find(110), client.config_.options_.end());
+
+    // Options 109 and 111 should not be copied (they are not RSOO-enabled)
+    EXPECT_EQ(client.config_.options_.find(109), client.config_.options_.end());
+    EXPECT_EQ(client.config_.options_.find(111), client.config_.options_.end());
+}
+
+// Test that the server processes RSOO (Relay Supplied Options option) correctly
+// when there are more relays. In particular, the following case is tested:
+// if relay1 inserts option A and B, relay2 inserts option B and C, the response
+// should include options A, B and C. The server must use instance of option B
+// that comes from the first relay, not the second one.
+TEST_F(Dhcpv6SrvTest, rsoo2relays) {
+
+    Dhcp6Client client;
+
+    string config =
+        "{"
+        "    \"relay-supplied-options\": [ \"110\", \"120\", \"130\" ],"
+        "    \"preferred-lifetime\": 3000,"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"subnet6\": [ { "
+        "        \"pools\": [ { \"pool\": \"2001:db8::/64\" } ],"
+        "        \"subnet\": \"2001:db8::/48\" "
+        "     } ],"
+        "    \"valid-lifetime\": 4000"
+        "}";
+
+    EXPECT_NO_THROW(configure(config, *client.getServer()));
+
+    // Now pretend the packet came via two relays.
+
+    // This situation reflects the following case:
+    // client----relay1----relay2----server
+
+    // Fabricate the first relay.
+    Pkt6::RelayInfo relay1;
+    relay1.msg_type_ = DHCPV6_RELAY_FORW;
+    relay1.hop_count_ = 1;
+    relay1.linkaddr_ = IOAddress("2001:db8::1");
+    relay1.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo1;
+    rsoo1.push_back(110); // The relay1 will send 2 options: 110, 120
+    rsoo1.push_back(120);
+    OptionPtr opt = createRSOO(rsoo1, 1); // use 0x1 as payload
+    relay1.options_.insert(make_pair(opt->getType(), opt));
+
+    // Now the second relay.
+    Pkt6::RelayInfo relay2;
+    relay2.msg_type_ = DHCPV6_RELAY_FORW;
+    relay2.hop_count_ = 1;
+    relay2.linkaddr_ = IOAddress("2001:db8::1");
+    relay2.peeraddr_ = IOAddress("fe80::1");
+    vector<uint16_t> rsoo2;
+    rsoo2.push_back(120); // The relay2 will send 2 options: 120, 130
+    rsoo2.push_back(130);
+    opt = createRSOO(rsoo2, 2); // use 0x2 as payload
+    relay2.options_.insert(make_pair(opt->getType(), opt));
+
+    // The relays ecapsulate packet in this order: relay1, relay2, but the server
+    // decapsulates the packet in reverse order.
+    client.relay_info_.push_back(relay2);
+    client.relay_info_.push_back(relay1);
+
+    // There's a conflict here. Both relays want the server to echo back option
+    // 120. According to RFC6422, section 6:
+    //
+    // When such a conflict exists, the DHCP server MUST choose no more than
+    // one of these options to forward to the client.  The DHCP server MUST
+    // NOT forward more than one of these options to the client.
+    //
+    // By default, the DHCP server MUST choose the innermost value -- the
+    // value supplied by the relay agent closest to the DHCP client -- to
+    // forward to the DHCP client.
+
+    // Let the client do his thing.
+    client.doSARR();
+
+    int count110 = 0; // Let's count how many times option 110 was echoed back
+    int count120 = 0; // Let's count how many times option 120 was echoed back
+    int count130 = 0; // Let's count how many times option 130 was echoed back
+    OptionPtr opt120;
+    for (OptionCollection::const_iterator it = client.config_.options_.begin();
+         it != client.config_.options_.end(); ++it) {
+        switch (it->second->getType()) {
+        case 110:
+            count110++;
+            break;
+        case 120:
+            count120++;
+            opt120 = it->second;
+            break;
+        case 130:
+            count130++;
+            break;
+        default:
+            break;
+        }
+    }
+
+    // We expect to have exactly one instance of each option code.
+    EXPECT_EQ(1, count110);
+    EXPECT_EQ(1, count120);
+    EXPECT_EQ(1, count130);
+
+    // Now, let's check if the proper instance of option 120 was sent. It should
+    // match the content of what the first relay had sent.
+    ASSERT_TRUE(opt120);
+    vector<uint8_t> expected(10, 1);
+    EXPECT_EQ(expected, opt120->getData());
+}
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.