Browse Source

[4106] Allow for transmitting vendor option in the DHCPv4o6 message.

Marcin Siodelski 9 years ago
parent
commit
36d3119433
2 changed files with 156 additions and 50 deletions
  1. 52 27
      src/lib/dhcpsrv/dhcp4o6_ipc.cc
  2. 104 23
      src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc

+ 52 - 27
src/lib/dhcpsrv/dhcp4o6_ipc.cc

@@ -21,7 +21,7 @@
 #include <dhcp/option_string.h>
 #include <dhcp/option_vendor.h>
 #include <dhcpsrv/dhcp4o6_ipc.h>
-
+#include <boost/pointer_cast.hpp>
 #include <errno.h>
 #include <netinet/in.h>
 #include <string>
@@ -138,27 +138,35 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     // Get interface name and remote address
     pkt->unpack();
 
-    OptionVendorPtr vendor =
-        boost::dynamic_pointer_cast<OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS));
-
-    // Vendor option must exist.
-    if (!vendor) {
-        isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS
-                  << " not present in the DHCP4o6 message sent between the "
-                  " servers");
+    // Vendor option is initially NULL. If we find the instance of the vendor
+    // option with the ISC enterprise id this pointer will point to it.
+    OptionVendorPtr option_vendor;
+
+    // Get all vendor option and look for the one with the ISC enterprise id.
+    OptionCollection vendor_options = pkt->getOptions(D6O_VENDOR_OPTS);
+    for (OptionCollection::const_iterator opt = vendor_options.begin();
+         opt != vendor_options.end(); ++opt) {
+        option_vendor = boost::dynamic_pointer_cast<OptionVendor>(opt->second);
+        if (option_vendor) {
+            if (option_vendor->getVendorId() == ENTERPRISE_ID_ISC) {
+                break;
+            }
+
+        } else {
+            option_vendor.reset();
+        }
     }
-
-    // The vendor option must require appropriate enterprise-id.
-    if (vendor->getVendorId() != ENTERPRISE_ID_ISC) {
+         
+    // Vendor option must exist.
+    if (!option_vendor) {
         isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS
-                  << " in the DHCP4o6 message contains invalid enterprise-id '"
-                  << vendor->getVendorId() << "'. Expected enterprise-id '"
-                  << ENTERPRISE_ID_ISC << "'");
+                  << " with ISC enterprise id is not present in the DHCP4o6"
+                  " message sent between the servers");
     }
 
     // The option carrying interface name is required.
     OptionStringPtr ifname = boost::dynamic_pointer_cast<
-        OptionString>(vendor->getOption(ISC_V6_4O6_INTERFACE));
+        OptionString>(option_vendor->getOption(ISC_V6_4O6_INTERFACE));
     if (!ifname) {
         isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS
                   << " doesn't contain the " << ISC_V6_4O6_INTERFACE
@@ -175,8 +183,8 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     }
 
     // Get the option holding source IPv6 address.
-    OptionCustomPtr srcs =
-        boost::dynamic_pointer_cast<OptionCustom>(vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+    OptionCustomPtr srcs = boost::dynamic_pointer_cast<
+        OptionCustom>(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
     if (!srcs) {
         isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS
                   << " doesn't contain the " << ISC_V6_4O6_SRC_ADDRESS
@@ -184,11 +192,21 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
                   " between Kea servers");
     }
 
-    // Update the packet and return it
-    static_cast<void>(pkt->delOption(D6O_VENDOR_OPTS));
+    // Update the packet.
     pkt->setRemoteAddr(srcs->readAddress());
     pkt->setIface(iface->getName());
     pkt->setIndex(iface->getIndex());
+
+    // Remove options that have been added by the IPC sender.
+    static_cast<void>(option_vendor->delOption(ISC_V6_4O6_INTERFACE));
+    static_cast<void>(option_vendor->delOption(ISC_V6_4O6_SRC_ADDRESS));
+
+    // If there are no more options, the IPC sender has probably created the
+    // vendor option, in which case we should remove it here.
+    if (option_vendor->getOptions().empty()) {
+        static_cast<void>(pkt->delOption(D6O_VENDOR_OPTS));
+    }
+
     return (pkt);
 }
 
@@ -206,17 +224,24 @@ void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) {
                   " IPC socket is closed");
     }
 
-    // Get a vendor option
-    OptionVendorPtr vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+    // Check if vendor option exists.
+    OptionVendorPtr option_vendor = dynamic_pointer_cast<
+        OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS));
+
+    // If vendor option doesn't exist or its enterprise id is not ISC's
+    // enterprise id, let's create it.
+    if (!option_vendor || (option_vendor->getVendorId() != ENTERPRISE_ID_ISC)) {
+        option_vendor.reset(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+        pkt->addOption(option_vendor);
+
+    }
 
     // Push interface name and source address in it
-    vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
                                                        ISC_V6_4O6_INTERFACE,
                                                        pkt->getIface())));
-    vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                                           pkt->getRemoteAddr())));
-    pkt->addOption(vendor);
-
+    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                                                  pkt->getRemoteAddr())));
     // Get packet content
     OutputBuffer& buf = pkt->getBuffer();
     buf.clear();

+ 104 - 23
src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc

@@ -171,19 +171,43 @@ protected:
 
     /// @brief Creates an instance of the DHCPv4o6 message with vendor option.
     ///
-    /// The vendor option appended to the message has ISC entprise id and
-    /// comprises option @c ISC_V6_4O6_INTERFACE and @c ISC_V6_4O6_SRC_ADDRESS.
+    /// @param msg_type Message type.
+    /// @param postfix Postfix to be appended to the remote address. See the
+    /// documentation of @c createDHCPv4o6Message for details.
+    ///
+    /// @return Pointer to the created message.
+    static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type,
+                                                     const uint16_t postfix,
+                                                     const uint32_t enterprise_id);
+
+    /// @brief Creates an instance of the DHCPv4o6 message with ISC
+    /// vendor option.
+    ///
     /// This is useful to test scenarios when the IPC is forwarding messages
-    /// that contain options also inserted by IPC. The duplicate options are
-    /// allowed and IPC should deal with this with no error.
+    /// that contain vendor option with ISC enterprise ID.
     ///
     /// @param msg_type Message type.
     /// @param postfix Postfix to be appended to the remote address. See the
     /// documentation of @c createDHCPv4o6Message for details.
     ///
     /// @return Pointer to the created message.
-    static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type,
-                                                     const uint16_t postfix);
+    static Pkt6Ptr createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type,
+                                                        const uint16_t postfix);
+
+    /// @brief Creates an instance of the DHCPv4o6 message with vendor
+    /// option holding enterprise id of 32000.
+    ///
+    /// This is useful to test scenarios when the IPC is forwarding messages
+    /// that contain some vendor option and when IPC also appends the ISC
+    /// vendor option to carry some DHCPv4o6 specific information.
+    ///
+    /// @param msg_type Message type.
+    /// @param postfix Postfix to be appended to the remote address. See the
+    /// documentation of @c createDHCPv4o6Message for details.
+    ///
+    /// @return Pointer to the created message.
+    static Pkt6Ptr createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type,
+                                                        const uint16_t postfix);
 
     /// @brief Creates an instance of the DHCPv4-query Message option.
     ///
@@ -254,28 +278,33 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6Message(const uint16_t msg_type,
 
 Pkt6Ptr
 Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type,
-                                                      const uint16_t postfix) {
+                                                      const uint16_t postfix,
+                                                      const uint32_t enterprise_id) {
     Pkt6Ptr pkt = createDHCPv4o6Message(msg_type, postfix);
 
     // Create vendor option with ISC enterprise id.
-    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
-
-    // Add interface. Such interface doesn't have to exist in the system because
-    // IPC should not use this option when it is received. It should rather use
-    // the option that the sender side is appending to the message.
-    option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
-                                                              ISC_V6_4O6_INTERFACE,
-                                                              "non-existing")));
-    // Add some remote address.
-    option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
-                                                                  IOAddress("3000::10"))));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, enterprise_id));
+
+    // Add some option to the vendor option.
+    option_vendor->addOption(OptionPtr(new Option(Option::V6, 100)));
+
     // Add vendor option to the message.
     pkt->addOption(option_vendor);
 
     return (pkt);
 }
 
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type,
+                                                         const uint16_t postfix) {
+    return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, ENTERPRISE_ID_ISC));
+}
 
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type,
+                                                         const uint16_t postfix) {
+    return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, 32000));
+}
 
 OptionPtr
 Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(const int src) {
@@ -311,11 +340,18 @@ Dhcp4o6IpcBaseTest::testSendReceive(const uint16_t iterations_num,
     uint16_t msg_type = (src == 6 ? DHCPV6_DHCPV4_QUERY :
                          DHCPV6_DHCPV4_RESPONSE);
 
+    std::vector<bool> has_vendor_option;
+
     // Send the number of messages configured for the test.
     for (uint16_t i = 1; i <= iterations_num; ++i) {
         // Create the DHCPv4o6 message.
         Pkt6Ptr pkt = create_msg_fun(msg_type, i);
 
+        // Remember if the vendor option exists in the source packet. The
+        // received packet should also contain this option if it exists
+        // in the source packet.
+        has_vendor_option.push_back(static_cast<bool>(pkt->getOption(D6O_VENDOR_OPTS)));
+
         // Actaully send the message through the IPC.
         ASSERT_NO_THROW(ipc_src.send(pkt))
             << "Failed to send the message over the IPC for iteration " << i;
@@ -344,6 +380,24 @@ Dhcp4o6IpcBaseTest::testSendReceive(const uint16_t iterations_num,
 
         // Check that encapsulated DHCPv4 message has been received.
         EXPECT_TRUE(pkt_received->getOption(D6O_DHCPV4_MSG));
+
+        if (has_vendor_option[i - 1]) {
+            // Make sure that the vendor option wasn't deleted when the packet was
+            // received.
+            OptionPtr option_vendor = pkt_received->getOption(D6O_VENDOR_OPTS);
+            ASSERT_TRUE(option_vendor)
+                << "vendor option deleted in the received DHCPv4o6 packet for"
+                " iteration " << i;
+
+            // ISC_V6_4O6_INTERFACE shouldn't be present.
+            EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_INTERFACE));
+
+            // ISC_V6_4O6_SRC_ADDRESS shouldn't be present.
+            EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+
+        } else {
+            EXPECT_FALSE(pkt_received->getOption(D6O_VENDOR_OPTS));
+        }
     }
 }
 
@@ -374,22 +428,49 @@ Dhcp4o6IpcBaseTest::testReceiveError(const Pkt6Ptr& pkt) {
 
 
 // This test verifies that the IPC can transmit messages between the
-// DHCPv6 and DHCPv4 server.
+// DHCPv4 and DHCPv6 server.
 TEST_F(Dhcp4o6IpcBaseTest, send4To6) {
     testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V4, ENDPOINT_TYPE_V6,
                     &createDHCPv4o6Message);
 }
 
-// This test verifies taht the IPC can transmit messages between the
-// DHCPv4 and DHCPv6 server.
+// This test verifies that the IPC can transmit messages between the
+// DHCPv6 and DHCPv4 server.
 TEST_F(Dhcp4o6IpcBaseTest, send6To4) {
     testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V6, ENDPOINT_TYPE_V4,
                     &createDHCPv4o6Message);
 }
 
-TEST_F(Dhcp4o6IpcBaseTest, send6To4WithVendorOption) {
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithISCVendorOption) {
     testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V6, ENDPOINT_TYPE_V4,
-                    &createDHCPv4o6MsgWithVendorOption);
+                    &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithISCVendorOption) {
+    testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V4, ENDPOINT_TYPE_V6,
+                    &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv6
+// and DHCPv4 server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithAnyVendorOption) {
+    testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V6, ENDPOINT_TYPE_V4,
+                    &createDHCPv4o6MsgWithAnyVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv4
+// and DHCPv6 server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithAnyVendorOption) {
+    testSendReceive(TEST_ITERATIONS, ENDPOINT_TYPE_V4, ENDPOINT_TYPE_V6,
+                    &createDHCPv4o6MsgWithAnyVendorOption);
 }
 
 // This test checks that the values of the socket descriptor are correct