Browse Source

[3194] Implemented basic vendor options support.

Marcin Siodelski 11 years ago
parent
commit
0764a74e46

+ 7 - 0
src/bin/dhcp4/config_parser.cc

@@ -96,6 +96,13 @@ protected:
                      << " for DHCPv6 server");
         }
 
+        // Check if this is a vendor-option. If it is, get vendor-specific
+        // definition.
+        uint32_t vendor_id = SubnetConfigParser::optionSpaceToVendorId(option_space);
+        if (vendor_id) {
+            def = LibDHCP::getVendorOptionDef(Option::V4, vendor_id, option_code);
+        }
+
         return (def);
     }
 };

+ 80 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -20,7 +20,9 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/pkt4.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcpsrv/addr_utilities.h>
@@ -36,6 +38,7 @@
 
 #include <boost/algorithm/string/erase.hpp>
 #include <boost/bind.hpp>
+#include <boost/foreach.hpp>
 
 #include <iomanip>
 #include <fstream>
@@ -644,6 +647,62 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
 }
 
 void
+Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+    // Get the configured subnet suitable for the incoming packet.
+    Subnet4Ptr subnet = selectSubnet(question);
+    // Leave if there is no subnet matching the incoming packet.
+    // There is no need to log the error message here because
+    // it will be logged in the assignLease() when it fails to
+    // pick the suitable subnet. We don't want to duplicate
+    // error messages in such case.
+    if (!subnet) {
+        return;
+    }
+
+    // Try to get the vendor option
+    boost::shared_ptr<OptionVendor> vendor_req =
+        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(DHO_VIVSO_SUBOPTIONS));
+    if (!vendor_req) {
+        return;
+    }
+
+    uint32_t vendor_id = vendor_req->getVendorId();
+
+    // Let's try to get ORO within that vendor-option
+    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
+    /// may have different policies.
+    OptionPtr oro = vendor_req->getOption(DOCSIS3_V4_ORO);
+
+    /// @todo: see OPT_UINT8_TYPE definition in OptionDefinition::optionFactory().
+    /// I think it should be OptionUint8Array, not OptionGeneric
+
+    // Option ORO not found. Don't do anything then.
+    if (!oro) {
+        return;
+    }
+
+    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V4, vendor_id));
+
+    // Get the list of options that client requested.
+    bool added = false;
+    const OptionBuffer& requested_opts = oro->getData();
+
+    for (OptionBuffer::const_iterator code = requested_opts.begin();
+         code != requested_opts.end(); ++code) {
+        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, *code);
+        if (desc.option) {
+            vendor_rsp->addOption(desc.option);
+            added = true;
+        }
+    }
+
+    if (added) {
+        answer->addOption(vendor_rsp);
+    }
+}
+
+
+void
 Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
     // Identify options that we always want to send to the
     // client (if they are configured).
@@ -858,6 +917,7 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
     copyDefaultFields(discover, offer);
     appendDefaultOptions(offer, DHCPOFFER);
     appendRequestedOptions(discover, offer);
+    appendRequestedVendorOptions(discover, offer);
 
     assignLease(discover, offer);
 
@@ -881,6 +941,7 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
     copyDefaultFields(request, ack);
     appendDefaultOptions(ack, DHCPACK);
     appendRequestedOptions(request, ack);
+    appendRequestedVendorOptions(request, ack);
 
     // Note that we treat REQUEST message uniformly, regardless if this is a
     // first request (requesting for new address), renewing existing address
@@ -1226,6 +1287,25 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
                       << "-byte long buffer.");
         }
 
+        /// @todo: Not sure if this is needed. Perhaps it would be better to extend
+        /// DHO_VIVSO_SUBOPTIONS definitions in std_option_defs.h to cover
+        /// OptionVendor class?
+        if (opt_type == DHO_VIVSO_SUBOPTIONS) {
+            if (offset + 4 > buf.size()) {
+                // Truncated vendor-option. There is expected at least 4 bytes
+                // long enterprise-id field
+                return (offset);
+            }
+
+            // Parse this as vendor option
+            OptionPtr vendor_opt(new OptionVendor(Option::V4, buf.begin() + offset,
+                                                  buf.begin() + offset + opt_len));
+            options.insert(std::make_pair(opt_type, vendor_opt));
+
+            offset += opt_len;
+            continue;
+        }
+
         // Get all definitions with the particular option code. Note that option code
         // is non-unique within this container however at this point we expect
         // to get one option definition with the particular code. If more are

+ 12 - 0
src/bin/dhcp4/dhcp4_srv.h

@@ -227,6 +227,18 @@ protected:
     /// @param msg outgoing message (options will be added here)
     void appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
 
+    /// @brief Appends requested vendor options as requested by client.
+    ///
+    /// This method is similar to \ref appendRequestedOptions(), but uses
+    /// vendor options. The major difference is that vendor-options use
+    /// its own option spaces (there may be more than one distinct set of vendor
+    /// options, each with unique vendor-id). Vendor options are requested
+    /// using separate options within their respective vendor-option spaces.
+    ///
+    /// @param question DISCOVER or REQUEST message from a client.
+    /// @param msg outgoing message (options will be added here)
+    void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
     /// @brief Assigns a lease and appends corresponding options
     ///
     /// This method chooses the most appropriate lease for reqesting

+ 84 - 5
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -25,8 +25,10 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter_inet.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/config_parser.h>
@@ -1138,11 +1140,7 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
     EXPECT_EQ(srvid_text, text);
 }
 
-// Checks if callouts installed on pkt4_receive are indeed called and the
-// all necessary parameters are passed.
-//
-// Note that the test name does not follow test naming convention,
-// but the proper hook name is "buffer4_receive".
+// Checks if received relay agent info option is echoed back to the client
 TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
 
     NakedDhcpv4Srv srv(0);
@@ -1180,6 +1178,87 @@ TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
     EXPECT_TRUE(rai_response->equal(rai_query));
 }
 
+// Checks if vendor options are parsed correctly and requested vendor options
+// are echoed back.
+TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
+
+    NakedDhcpv4Srv srv(0);
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-data\": [ {"
+        "          \"name\": \"tftp-servers\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": 2,"
+        "          \"data\": \"10.253.175.16\","
+        "          \"csv-format\": True"
+        "        }],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"10.254.226.0/25\" ],"
+        "    \"subnet\": \"10.254.226.0/24\", "
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " }, {"
+        "    \"pool\": [ \"192.0.3.0/25\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+    // added option 82 (relay agent info) with 3 suboptions. The server
+    // is supposed to echo it back in its response.
+    Pkt4Ptr dis;
+    ASSERT_NO_THROW(dis = captureRelayedDiscover());
+
+    // Simulate that we have received that traffic
+    srv.fakeReceive(dis);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered buffer4_receive callback.
+    srv.run();
+
+    // Check that the server did send a reposonse
+    ASSERT_EQ(1, srv.fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt4Ptr offer = srv.fake_sent_.front();
+    ASSERT_TRUE(offer);
+
+    // Get Relay Agent Info from query...
+    OptionPtr vendor_opt_response = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_TRUE(vendor_opt_response);
+
+    // Check if it's of a correct type
+    boost::shared_ptr<OptionVendor> vendor_opt =
+        boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
+    ASSERT_TRUE(vendor_opt);
+
+    // Get Relay Agent Info from response...
+    OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS);
+    ASSERT_TRUE(tftp_servers_generic);
+
+    Option4AddrLstPtr tftp_servers =
+        boost::dynamic_pointer_cast<Option4AddrLst>(tftp_servers_generic);
+
+    ASSERT_TRUE(tftp_servers);
+
+    Option4AddrLst::AddressContainer addrs = tftp_servers->getAddresses();
+    ASSERT_EQ(1, addrs.size());
+    EXPECT_EQ("10.253.175.16", addrs[0].toText());
+}
+
+
 /// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc:
 /// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
 /// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not

+ 2 - 0
src/bin/dhcp4/tests/wireshark.cc

@@ -100,6 +100,8 @@ Bootstrap Protocol
     Option: (55) Parameter Request List
     Option: (60) Vendor class identifier
     Option: (125) V-I Vendor-specific Information
+      - suboption 1 (Option Request): requesting option 2
+      - suboption 5 (Modem Caps): 117 bytes
     Option: (43) Vendor-Specific Information (CableLabs)
     Option: (61) Client identifier
     Option: (57) Maximum DHCP Message Size

+ 7 - 0
src/bin/dhcp6/config_parser.cc

@@ -111,6 +111,13 @@ protected:
                      << " for DHCPv4 server");
         }
 
+        // Check if this is a vendor-option. If it is, get vendor-specific
+        // definition.
+        uint32_t vendor_id = SubnetConfigParser::optionSpaceToVendorId(option_space);
+        if (vendor_id) {
+            def = LibDHCP::getVendorOptionDef(Option::V6, vendor_id, option_code);
+        }
+
         return def;
     }
 };

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

@@ -17,6 +17,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/duid.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
@@ -26,6 +27,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaprefix.h>
 #include <dhcp/option_custom.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp6/dhcp6_log.h>
@@ -683,6 +685,57 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     }
 }
 
+void
+Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+    // Get the configured subnet suitable for the incoming packet.
+    Subnet6Ptr subnet = selectSubnet(question);
+    // Leave if there is no subnet matching the incoming packet.
+    // There is no need to log the error message here because
+    // it will be logged in the assignLease() when it fails to
+    // pick the suitable subnet. We don't want to duplicate
+    // error messages in such case.
+    if (!subnet) {
+        return;
+    }
+
+    // Try to get the vendor option
+    boost::shared_ptr<OptionVendor> vendor_req =
+        boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
+    if (!vendor_req) {
+        return;
+    }
+
+    uint32_t vendor_id = vendor_req->getVendorId();
+
+    // Let's try to get ORO within that vendor-option
+    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
+    /// may have different policies.
+    boost::shared_ptr<OptionUint16Array> oro =
+        boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO));
+
+    // Option ORO not found. Don't do anything then.
+    if (!oro) {
+        return;
+    }
+
+    boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
+
+    // Get the list of options that client requested.
+    bool added = false;
+    const std::vector<uint16_t>& requested_opts = oro->getValues();
+    BOOST_FOREACH(uint16_t opt, requested_opts) {
+        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt);
+        if (desc.option) {
+            vendor_rsp->addOption(desc.option);
+            added = true;
+        }
+    }
+
+    if (added) {
+        answer->addOption(vendor_rsp);
+    }
+}
+
 OptionPtr
 Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
     // @todo This function uses OptionCustom class to manage contents
@@ -2114,6 +2167,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     copyDefaultOptions(solicit, advertise);
     appendDefaultOptions(solicit, advertise);
     appendRequestedOptions(solicit, advertise);
+    appendRequestedVendorOptions(solicit, advertise);
 
     Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
     assignLeases(solicit, advertise, fqdn);
@@ -2135,6 +2189,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     copyDefaultOptions(request, reply);
     appendDefaultOptions(request, reply);
     appendRequestedOptions(request, reply);
+    appendRequestedVendorOptions(request, reply);
 
     Option6ClientFqdnPtr fqdn = processClientFqdn(request);
     assignLeases(request, reply, fqdn);
@@ -2301,6 +2356,22 @@ Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
             continue;
         }
 
+        if (opt_type == D6O_VENDOR_OPTS) {
+            if (offset + 4 > length) {
+                // Truncated vendor-option. There is expected at least 4 bytes
+                // long enterprise-id field
+                return (offset);
+            }
+
+            // Parse this as vendor option
+            OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+                                                  buf.begin() + offset + opt_len));
+            options.insert(std::make_pair(opt_type, vendor_opt));
+
+            offset += opt_len;
+            continue;
+        }
+
         // Get all definitions with the particular option code. Note that option
         // code is non-unique within this container however at this point we
         // expect to get one option definition with the particular code. If more

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

@@ -336,6 +336,15 @@ protected:
     /// @param answer server's message (options will be added here)
     void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
+    /// @brief Appends requested vendor options to server's answer.
+    ///
+    /// This is mostly useful for Cable Labs options for now, but the method
+    /// is easily extensible to other vendors.
+    ///
+    /// @param question client's message
+    /// @param answer server's message (vendor options will be added here)
+    void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+
     /// @brief Assigns leases.
     ///
     /// It supports addresses (IA_NA) only. It does NOT support temporary

+ 112 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -2049,6 +2049,118 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     EXPECT_EQ(1516, optionIA->getT2());
 }
 
+// This test checks if vendor options can be specified in the config file
+// (in hex format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
+
+    // This configuration string is to configure two options
+    // sharing the code 1 and belonging to the different vendor spaces.
+    // (different vendor-id values).
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"option-one\","
+        "    \"space\": \"vendor-4491\","
+        "    \"code\": 100,"
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"option-two\","
+        "    \"space\": \"vendor-1234\","
+        "    \"code\": 100,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": False"
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now available for the subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+
+    // Try to get the option from the vendor space 4491
+    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
+    ASSERT_TRUE(desc1.option);
+    EXPECT_EQ(100, desc1.option->getType());
+    // Try to get the option from the vendor space 1234
+    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100);
+    ASSERT_TRUE(desc2.option);
+    EXPECT_EQ(100, desc1.option->getType());
+
+    // Try to get the non-existing option from the non-existing
+    // option space and  expect that option is not returned.
+    Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 38);
+    ASSERT_FALSE(desc3.option);
+}
+
+// This test checks if vendor options can be specified in the config file,
+// (in csv format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
+
+    // This configuration string is to configure two options
+    // sharing the code 1 and belonging to the different vendor spaces.
+    // (different vendor-id values).
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-4491\","
+        "    \"code\": 100,"
+        "    \"data\": \"this is a string vendor-opt\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 100,"
+        "    \"type\": \"string\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-4491\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now available for the subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+
+    // Try to get the option from the vendor space 4491
+    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
+    ASSERT_TRUE(desc1.option);
+    EXPECT_EQ(100, desc1.option->getType());
+
+    // Try to get the non-existing option from the non-existing
+    // option space and  expect that option is not returned.
+    Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 38);
+    ASSERT_FALSE(desc2.option);
+}
+
 // The goal of this test is to verify that the standard option can
 // be configured to encapsulate multiple other options.
 TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {

+ 206 - 2
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -25,10 +25,13 @@
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -2014,9 +2017,210 @@ TEST_F(Dhcpv6SrvTest, docsisTraffic) {
     ASSERT_FALSE(srv.fake_sent_.empty());
     Pkt6Ptr adv = srv.fake_sent_.front();
     ASSERT_TRUE(adv);
+}
+
+// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
+TEST_F(Dhcpv6SrvTest, docsisVendorOptionsParse) {
+
+    NakedDhcpv6Srv srv(0);
+
+    // Let's get a traffic capture from DOCSIS3.0 modem
+    Pkt6Ptr sol = captureDocsisRelayedSolicit();
+    EXPECT_NO_THROW(sol->unpack());
+
+    // Check if the packet contain
+    OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(opt);
+
+    boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+
+    EXPECT_TRUE(vendor->getOption(1));
+    EXPECT_TRUE(vendor->getOption(36));
+    EXPECT_TRUE(vendor->getOption(35));
+    EXPECT_TRUE(vendor->getOption(2));
+    EXPECT_TRUE(vendor->getOption(3));
+    EXPECT_TRUE(vendor->getOption(4));
+    EXPECT_TRUE(vendor->getOption(5));
+    EXPECT_TRUE(vendor->getOption(6));
+    EXPECT_TRUE(vendor->getOption(7));
+    EXPECT_TRUE(vendor->getOption(8));
+    EXPECT_TRUE(vendor->getOption(9));
+    EXPECT_TRUE(vendor->getOption(10));
+    EXPECT_TRUE(vendor->getOption(15));
+
+    EXPECT_FALSE(vendor->getOption(20));
+    EXPECT_FALSE(vendor->getOption(11));
+    EXPECT_FALSE(vendor->getOption(17));
+}
+
+// Checks if server is able to parse incoming docsis option and extract suboption 1 (docsis ORO)
+TEST_F(Dhcpv6SrvTest, docsisVendorORO) {
+
+    NakedDhcpv6Srv srv(0);
+
+    // Let's get a traffic capture from DOCSIS3.0 modem
+    Pkt6Ptr sol = captureDocsisRelayedSolicit();
+    EXPECT_NO_THROW(sol->unpack());
+
+    // Check if the packet contain
+    OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(opt);
+
+    boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+
+    opt = vendor->getOption(DOCSIS3_V6_ORO);
+    ASSERT_TRUE(opt);
+
+    OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+    EXPECT_TRUE(oro);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-def\": [ {"
+        "        \"name\": \"config-file\","
+        "        \"code\": 33,"
+        "        \"type\": \"string\","
+        "        \"array\": False,"
+        "        \"record-types\": \"\","
+        "        \"space\": \"vendor-4491\","
+        "        \"encapsulate\": \"\""
+        "     } ],"
+        "    \"option-data\": [ {"
+        "          \"name\": \"config-file\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": 33,"
+        "          \"data\": \"normal_erouter_v6.cm\","
+        "          \"csv-format\": True"
+        "        }],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"renew-timer\": 1000, "
+        "    \"rebind-timer\": 1000, "
+        "    \"preferred-lifetime\": 3000,"
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    NakedDhcpv6Srv srv(0);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+
+    ASSERT_EQ(0, rcode_);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv.processSolicit(sol);
+
+    // check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // We did not include any vendor opts in SOLCIT, so there should be none
+    // in ADVERTISE.
+    ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
+
+    // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
+    // That suboption has code 1 and is a docsis ORO option.
+    boost::shared_ptr<OptionUint16Array> vendor_oro(new OptionUint16Array(Option::V6,
+                                                                          DOCSIS3_V6_ORO));
+    vendor_oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33
+    OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+    vendor->addOption(vendor_oro);
+    sol->addOption(vendor);
 
-    /// @todo Check that the ADVERTISE is ok, that it includes all options,
-    /// that is relayed properly, etc.
+    // Need to process SOLICIT again after requesting new option.
+    adv = srv.processSolicit(sol);
+    ASSERT_TRUE(adv);
+
+    // Check if thre is vendor option response
+    OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(tmp);
+
+    // The response should be OptionVendor object
+    boost::shared_ptr<OptionVendor> vendor_resp =
+        boost::dynamic_pointer_cast<OptionVendor>(tmp);
+    ASSERT_TRUE(vendor_resp);
+
+    OptionPtr docsis33 = vendor_resp->getOption(33);
+    ASSERT_TRUE(docsis33);
+
+    OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+    ASSERT_TRUE(config_file);
+    EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
+
+// Test checks whether it is possible to use option definitions defined in
+// src/lib/dhcp/docsis3_option_defs.h.
+TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
+    ConstElementPtr x;
+    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-data\": [ {"
+        "          \"name\": \"config-file\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": ";
+    string config_postfix = ","
+        "          \"data\": \"normal_erouter_v6.cm\","
+        "          \"csv-format\": True"
+        "        }],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"renew-timer\": 1000, "
+        "    \"rebind-timer\": 1000, "
+        "    \"preferred-lifetime\": 3000,"
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // There is docsis3 (vendor-id=4491) vendor option 33, which is a
+    // config-file. Its format is a single string.
+    string config_valid = config_prefix + "33" + config_postfix;
+
+    // There is no option 99 defined in vendor-id=4491. As there is no
+    // definition, the config should fail.
+    string config_bogus = config_prefix + "99" + config_postfix;
+
+    ElementPtr json_bogus = Element::fromJSON(config_bogus);
+    ElementPtr json_valid = Element::fromJSON(config_valid);
+
+    NakedDhcpv6Srv srv(0);
+
+    // This should fail (missing option definition)
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_bogus));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(1, rcode_);
+
+    // This should work (option definition present)
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_valid));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
 }
 
 // This test verifies that the following option structure can be parsed:

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -29,6 +29,7 @@ libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
 libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
+libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
 libb10_dhcp___la_SOURCES += option_int.h
 libb10_dhcp___la_SOURCES += option_int_array.h
 libb10_dhcp___la_SOURCES += option.cc option.h

+ 65 - 0
src/lib/dhcp/docsis3_option_defs.h

@@ -0,0 +1,65 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DOCSIS3_OPTION_DEFS_H
+#define DOCSIS3_OPTION_DEFS_H
+
+#include <dhcp/std_option_defs.h>
+#include <dhcp/option_data_types.h>
+
+
+namespace {
+
+#define VENDOR_ID_CABLE_LABS 4491
+
+#define DOCSIS3_V4_ORO 1
+#define DOCSIS3_V4_TFTP_SERVERS 2
+
+/// @brief Definitions of standard DHCPv4 options.
+const OptionDefParams DOCSIS3_V4_DEFS[] = {
+    { "oro", DOCSIS3_V4_ORO, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+    { "tftp-servers", DOCSIS3_V4_TFTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V4_DEFS_SIZE  = sizeof(DOCSIS3_V4_DEFS) / sizeof(OptionDefParams);
+
+#define DOCSIS3_V6_ORO 1
+#define DOCSIS3_V6_DEVICE_TYPE 2
+#define DOCSIS3_V6_VENDOR_NAME 10
+#define DOCSIS3_V6_TFTP_SERVERS 32
+#define DOCSIS3_V6_CONFIG_FILE 33
+#define DOCSIS3_V6_SYSLOG_SERVERS 34
+#define DOCSIS3_V6_TIME_SERVERS 37
+#define DOCSIS3_V6_TIME_OFFSET 38
+
+/// @brief Definitions of standard DHCPv6 options.
+const OptionDefParams DOCSIS3_V6_DEFS[] = {
+    { "oro",            DOCSIS3_V6_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+    { "device-type",    DOCSIS3_V6_DEVICE_TYPE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "vendor-type",    DOCSIS3_V6_VENDOR_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "tftp-servers",   DOCSIS3_V6_TFTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "time-servers",   DOCSIS3_V6_TIME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "config-file",    DOCSIS3_V6_CONFIG_FILE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "syslog-servers", DOCSIS3_V6_SYSLOG_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "time-offset",    DOCSIS3_V6_TIME_OFFSET, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" }
+    // @todo add definitions for all remaning options.
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V6_DEFS_SIZE  = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H

+ 309 - 18
src/lib/dhcp/libdhcp++.cc

@@ -18,13 +18,16 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
+#include <dhcp/option_vendor.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/std_option_defs.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
+#include <dhcp/option_definition.h>
 
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
@@ -45,17 +48,29 @@ OptionDefContainer LibDHCP::v4option_defs_;
 // Static container with DHCPv6 option definitions.
 OptionDefContainer LibDHCP::v6option_defs_;
 
+VendorOptionDefContainers LibDHCP::vendor4_defs_;
+
+VendorOptionDefContainers LibDHCP::vendor6_defs_;
+
+// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
+// definitions there
+void initOptionSpace(OptionDefContainer& defs,
+                     const OptionDefParams* params,
+                     size_t params_size);
+
 const OptionDefContainer&
 LibDHCP::getOptionDefs(const Option::Universe u) {
     switch (u) {
     case Option::V4:
         if (v4option_defs_.empty()) {
             initStdOptionDefs4();
+            initVendorOptsDocsis4();
         }
         return (v4option_defs_);
     case Option::V6:
         if (v6option_defs_.empty()) {
             initStdOptionDefs6();
+            initVendorOptsDocsis6();
         }
         return (v6option_defs_);
     default:
@@ -63,6 +78,38 @@ LibDHCP::getOptionDefs(const Option::Universe u) {
     }
 }
 
+const OptionDefContainer*
+LibDHCP::getVendorOption4Defs(uint32_t vendor_id) {
+
+    if (vendor_id == VENDOR_ID_CABLE_LABS &&
+        vendor4_defs_.find(VENDOR_ID_CABLE_LABS) == vendor4_defs_.end()) {
+        initVendorOptsDocsis4();
+    }
+
+    VendorOptionDefContainers::const_iterator def = vendor4_defs_.find(vendor_id);
+    if (def == vendor4_defs_.end()) {
+        // No such vendor-id space
+        return (NULL);
+    }
+    return (&(def->second));
+}
+
+const OptionDefContainer*
+LibDHCP::getVendorOption6Defs(uint32_t vendor_id) {
+
+    if (vendor_id == VENDOR_ID_CABLE_LABS &&
+        vendor6_defs_.find(VENDOR_ID_CABLE_LABS) == vendor6_defs_.end()) {
+        initVendorOptsDocsis6();
+    }
+
+    VendorOptionDefContainers::const_iterator def = vendor6_defs_.find(vendor_id);
+    if (def == vendor6_defs_.end()) {
+        // No such vendor-id space
+        return (NULL);
+    }
+    return (&(def->second));
+}
+
 OptionDefinitionPtr
 LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
     const OptionDefContainer& defs = getOptionDefs(u);
@@ -74,6 +121,31 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
     return (OptionDefinitionPtr());
 }
 
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+                            const uint16_t code) {
+    const OptionDefContainer* defs = NULL;
+    if (u == Option::V4) {
+        defs = getVendorOption4Defs(vendor_id);
+    } else if (u == Option::V6) {
+        defs = getVendorOption6Defs(vendor_id);
+    }
+
+    if (!defs) {
+        // Weird universe or unknown vendor_id. We don't care. No definitions
+        // one way or another
+        // What is it anyway?
+        return (OptionDefinitionPtr());
+    }
+
+    const OptionDefContainerTypeIndex& idx = defs->get<1>();
+    const OptionDefContainerTypeRange& range = idx.equal_range(code);
+    if (range.first != range.second) {
+        return (*range.first);
+    }
+    return (OptionDefinitionPtr());
+}
+
 bool
 LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
     if (u == Option::V6) {
@@ -164,6 +236,23 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             continue;
         }
 
+        if (opt_type == D6O_VENDOR_OPTS) {
+            if (offset + 4 > length) {
+                // Truncated vendor-option. There is expected at least 4 bytes
+                // long enterprise-id field
+                return (offset);
+            }
+
+            // Parse this as vendor option
+            OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+                                                  buf.begin() + offset + opt_len));
+            options.insert(std::make_pair(opt_type, vendor_opt));
+
+            offset += opt_len;
+            continue;
+        }
+
+
         // Get all definitions with the particular option code. Note that option
         // code is non-unique within this container however at this point we
         // expect to get one option definition with the particular code. If more
@@ -280,6 +369,191 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
     return (offset);
 }
 
+size_t LibDHCP::unpackVendorOptions6(uint32_t vendor_id,
+                                     const OptionBuffer& buf,
+                                     isc::dhcp::OptionCollection& options) {
+    size_t offset = 0;
+    size_t length = buf.size();
+
+    // Get the list of option definitions for this particular vendor-id
+    const OptionDefContainer* option_defs = LibDHCP::getVendorOption6Defs(vendor_id);
+
+    // Get the search index #1. It allows to search for option definitions
+    // using option code. If there's no such vendor-id space, we're out of luck
+    // anyway.
+    const OptionDefContainerTypeIndex* idx = NULL;
+    if (option_defs) {
+        idx = &(option_defs->get<1>());
+    }
+
+    // The buffer being read comprises a set of options, each starting with
+    // a two-byte type code and a two-byte length field.
+    while (offset + 4 <= length) {
+        uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+        offset += 2;
+
+        uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+        offset += 2;
+
+        if (offset + opt_len > length) {
+            // @todo: consider throwing exception here.
+            return (offset);
+        }
+
+        OptionPtr opt;
+        opt.reset();
+
+        // If there is a definition for such a vendor option...
+        if (idx) {
+            // Get all definitions with the particular option code. Note that option
+            // code is non-unique within this container however at this point we
+            // expect to get one option definition with the particular code. If more
+            // are returned we report an error.
+            const OptionDefContainerTypeRange& range = idx->equal_range(opt_type);
+            // Get the number of returned option definitions for the option code.
+            size_t num_defs = distance(range.first, range.second);
+
+            if (num_defs > 1) {
+                // Multiple options of the same code are not supported right now!
+                isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+                          " for option type " << opt_type << " returned. Currently it is not"
+                          " supported to initialize multiple option definitions"
+                          " for the same option code. This will be supported once"
+                          " support for option spaces is implemented");
+            } else if (num_defs == 1) {
+                // The option definition has been found. Use it to create
+                // the option instance from the provided buffer chunk.
+                const OptionDefinitionPtr& def = *(range.first);
+                assert(def);
+                opt = def->optionFactory(Option::V6, opt_type,
+                                         buf.begin() + offset,
+                                         buf.begin() + offset + opt_len);
+            }
+        }
+
+        // This can happen in one of 2 cases:
+        // 1. we do not have definitions for that vendor-space
+        // 2. we do have definitions, but that particular option was not defined
+        if (!opt) {
+            opt = OptionPtr(new Option(Option::V6, opt_type,
+                                       buf.begin() + offset,
+                                       buf.begin() + offset + opt_len));
+        }
+
+        // add option to options
+        if (opt) {
+            options.insert(std::make_pair(opt_type, opt));
+        }
+        offset += opt_len;
+    }
+
+    return (offset);
+}
+
+size_t LibDHCP::unpackVendorOptions4(uint32_t vendor_id, const OptionBuffer& buf,
+                                     isc::dhcp::OptionCollection& options) {
+    size_t offset = 0;
+
+    // Get the list of stdandard option definitions.
+    const OptionDefContainer* option_defs = LibDHCP::getVendorOption4Defs(vendor_id);
+    // Get the search index #1. It allows to search for option definitions
+    // using option code.
+    const OptionDefContainerTypeIndex* idx = NULL;
+    if (option_defs) {
+        idx = &(option_defs->get<1>());
+    }
+
+    // The buffer being read comprises a set of options, each starting with
+    // a one-byte type code and a one-byte length field.
+    while (offset + 1 <= buf.size()) {
+
+        // Note that Vendor-Specific info option (RFC3925) has a different option
+        // format than Vendor-Spec info for DHCPv6. (there's additional layer of
+        // data-length
+        uint8_t data_len = buf[offset++];
+
+        if (offset + data_len > buf.size()) {
+            // Truncated data-option
+            return (offset);
+        }
+
+        uint8_t offset_end = offset + data_len;
+
+        // beginning of data-chunk parser
+        while (offset + 1 <= offset_end) {
+            uint8_t opt_type = buf[offset++];
+
+            // DHO_END is a special, one octet long option
+            if (opt_type == DHO_END)
+                return (offset); // just return. Don't need to add DHO_END option
+
+            // DHO_PAD is just a padding after DHO_END. Let's continue parsing
+            // in case we receive a message without DHO_END.
+            if (opt_type == DHO_PAD)
+                continue;
+
+            if (offset + 1 >= buf.size()) {
+                // opt_type must be cast to integer so as it is not treated as
+                // unsigned char value (a number is presented in error message).
+                isc_throw(OutOfRange, "Attempt to parse truncated option "
+                          << static_cast<int>(opt_type));
+            }
+
+            uint8_t opt_len =  buf[offset++];
+            if (offset + opt_len > buf.size()) {
+                isc_throw(OutOfRange, "Option parse failed. Tried to parse "
+                          << offset + opt_len << " bytes from " << buf.size()
+                          << "-byte long buffer.");
+            }
+
+            OptionPtr opt;
+            opt.reset();
+
+            if (idx) {
+                // Get all definitions with the particular option code. Note that option code
+                // is non-unique within this container however at this point we expect
+                // to get one option definition with the particular code. If more are
+                // returned we report an error.
+                const OptionDefContainerTypeRange& range = idx->equal_range(opt_type);
+            // Get the number of returned option definitions for the option code.
+                size_t num_defs = distance(range.first, range.second);
+
+                if (num_defs > 1) {
+                    // Multiple options of the same code are not supported right now!
+                    isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+                              " for option type " << static_cast<int>(opt_type)
+                              << " returned. Currently it is not supported to initialize"
+                              << " multiple option definitions for the same option code."
+                              << " This will be supported once support for option spaces"
+                              << " is implemented");
+                } else if (num_defs == 1) {
+                    // The option definition has been found. Use it to create
+                    // the option instance from the provided buffer chunk.
+                    const OptionDefinitionPtr& def = *(range.first);
+                    assert(def);
+                    opt = def->optionFactory(Option::V4, opt_type,
+                                             buf.begin() + offset,
+                                             buf.begin() + offset + opt_len);
+                }
+            }
+
+            if (!opt) {
+                opt = OptionPtr(new Option(Option::V4, opt_type,
+                                           buf.begin() + offset,
+                                           buf.begin() + offset + opt_len));
+            }
+
+            options.insert(std::make_pair(opt_type, opt));
+            offset += opt_len;
+
+        } // end of data-chunk
+
+    }
+    return (offset);
+}
+
+
+
 void
 LibDHCP::packOptions(isc::util::OutputBuffer& buf,
                      const OptionCollection& options) {
@@ -385,13 +659,30 @@ LibDHCP::initStdOptionDefs4() {
 
 void
 LibDHCP::initStdOptionDefs6() {
-    v6option_defs_.clear();
+    initOptionSpace(v6option_defs_, OPTION_DEF_PARAMS6, OPTION_DEF_PARAMS_SIZE6);
+}
 
-    for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
-        std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
-        if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
+void
+LibDHCP::initVendorOptsDocsis4() {
+    initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE);
+}
+
+void
+LibDHCP::initVendorOptsDocsis6() {
+    vendor6_defs_[VENDOR_ID_CABLE_LABS] = OptionDefContainer();
+    initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE);
+}
+
+void initOptionSpace(OptionDefContainer& defs,
+                     const OptionDefParams* params,
+                     size_t params_size) {
+    defs.clear();
+
+    for (int i = 0; i < params_size; ++i) {
+        std::string encapsulates(params[i].encapsulates);
+        if (!encapsulates.empty() && params[i].array) {
             isc_throw(isc::BadValue, "invalid standard option definition: "
-                      << "option with code '" << OPTION_DEF_PARAMS6[i].code
+                      << "option with code '" << params[i].code
                       << "' may not encapsulate option space '"
                       << encapsulates << "' because the definition"
                       << " indicates that this option comprises an array"
@@ -404,33 +695,33 @@ LibDHCP::initStdOptionDefs6() {
         OptionDefinitionPtr definition;
         if (encapsulates.empty()) {
             // Option does not encapsulate any option space.
-            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
-                                                  OPTION_DEF_PARAMS6[i].code,
-                                                  OPTION_DEF_PARAMS6[i].type,
-                                                  OPTION_DEF_PARAMS6[i].array));
+            definition.reset(new OptionDefinition(params[i].name,
+                                                  params[i].code,
+                                                  params[i].type,
+                                                  params[i].array));
         } else {
             // Option does encapsulate an option space.
-            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
-                                                  OPTION_DEF_PARAMS6[i].code,
-                                                  OPTION_DEF_PARAMS6[i].type,
-                                                  OPTION_DEF_PARAMS6[i].encapsulates));
+            definition.reset(new OptionDefinition(params[i].name,
+                                                  params[i].code,
+                                                  params[i].type,
+                                                  params[i].encapsulates));
 
         }
 
-        for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
-            definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
+        for (int rec = 0; rec < params[i].records_size; ++rec) {
+            definition->addRecordField(params[i].records[rec]);
         }
 
         try {
             definition->validate();
-        } catch (const Exception& ex) {
+        } catch (const isc::Exception& ex) {
             // This is unlikely event that validation fails and may
             // be only caused by programming error. To guarantee the
             // data consistency we clear all option definitions that
             // have been added so far and pass the exception forward.
-            v6option_defs_.clear();
+            defs.clear();
             throw;
         }
-        v6option_defs_.push_back(definition);
+        defs.push_back(definition);
     }
 }

+ 26 - 0
src/lib/dhcp/libdhcp++.h

@@ -55,6 +55,11 @@ public:
     static OptionDefinitionPtr getOptionDef(const Option::Universe u,
                                             const uint16_t code);
 
+
+    static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u,
+                                                  const uint32_t vendor_id,
+                                                  const uint16_t code);
+
     /// @brief Check if the specified option is a standard option.
     ///
     /// @param u universe (V4 or V6)
@@ -149,6 +154,19 @@ public:
                                       uint16_t type,
                                       Option::Factory * factory);
 
+    static const OptionDefContainer*
+    getVendorOption4Defs(uint32_t vendor_id);
+
+    static const OptionDefContainer*
+    getVendorOption6Defs(uint32_t vendor_id);
+
+    static size_t unpackVendorOptions6(uint32_t vendor_id,
+                                       const OptionBuffer& buf,
+                                       isc::dhcp::OptionCollection& options);
+
+    static size_t unpackVendorOptions4(uint32_t vendor_id, const OptionBuffer& buf,
+                                       isc::dhcp::OptionCollection& options);
+
 private:
 
     /// Initialize standard DHCPv4 option definitions.
@@ -170,6 +188,10 @@ private:
     /// is incorrect. This is a programming error.
     static void initStdOptionDefs6();
 
+    static void initVendorOptsDocsis4();
+
+    static void initVendorOptsDocsis6();
+
     /// pointers to factories that produce DHCPv6 options
     static FactoryMap v4factories_;
 
@@ -181,6 +203,10 @@ private:
 
     /// Container with DHCPv6 option definitions.
     static OptionDefContainer v6option_defs_;
+
+    static VendorOptionDefContainers vendor4_defs_;
+
+    static VendorOptionDefContainers vendor6_defs_;
 };
 
 }

+ 4 - 0
src/lib/dhcp/option_definition.h

@@ -23,6 +23,7 @@
 #include <boost/multi_index/sequenced_index.hpp>
 #include <boost/multi_index_container.hpp>
 #include <boost/shared_ptr.hpp>
+#include <map>
 
 namespace isc {
 namespace dhcp {
@@ -601,6 +602,9 @@ typedef boost::multi_index_container<
 /// Pointer to an option definition container.
 typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
 
+/// Container that holds various vendor option containers
+typedef std::map<uint32_t, OptionDefContainer> VendorOptionDefContainers;
+
 /// Type of the index #1 - option type.
 typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
 /// Pair of iterators to represent the range of options definitions

+ 68 - 0
src/lib/dhcp/option_vendor.cc

@@ -0,0 +1,68 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+
+using namespace isc::dhcp;
+
+OptionVendor::OptionVendor(Option::Universe u, uint32_t vendor_id)
+    :Option(u, u==Option::V4?DHO_VIVSO_SUBOPTIONS:D6O_VENDOR_OPTS), vendor_id_(vendor_id) {
+}
+
+OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+                           OptionBufferConstIter end)
+    :Option(u, u==Option::V4?DHO_VIVSO_SUBOPTIONS:D6O_VENDOR_OPTS), vendor_id_(0) {
+    unpack(begin, end);
+}
+
+
+void OptionVendor::pack(isc::util::OutputBuffer& buf) {
+    packHeader(buf);
+
+    buf.writeUint32(vendor_id_);
+
+    packOptions(buf);
+}
+
+void OptionVendor::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+    if (distance(begin, end) < sizeof(uint32_t)) {
+        isc_throw(OutOfRange, "Truncated vendor-specific information option");
+    }
+
+    vendor_id_ = isc::util::readUint32(&(*begin));
+
+    OptionBuffer vendor_buffer(begin +4, end);
+    
+    if (universe_ == Option::V6) {
+        LibDHCP::unpackVendorOptions6(vendor_id_, vendor_buffer, options_);
+    } else {
+        LibDHCP::unpackVendorOptions4(vendor_id_, vendor_buffer, options_);
+    }
+}
+
+uint16_t OptionVendor::len() {
+    uint16_t length = getHeaderLen();
+
+    length += sizeof(uint32_t); // Vendor-id field
+
+    // length of all suboptions
+    for (OptionCollection::iterator it = options_.begin();
+         it != options_.end();
+         ++it) {
+        length += (*it).second->len();
+    }
+    return (length);
+}

+ 104 - 0
src/lib/dhcp/option_vendor.h

@@ -0,0 +1,104 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_VENDOR_H
+#define OPTION_VENDOR_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// This class represents vendor-specific information option.
+class OptionVendor: public Option {
+
+public:
+    /// @brief Constructor.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id vendor enterprise-id (unique 32 bit integer)
+    OptionVendor(Option::Universe u, uint32_t vendor_id);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates option from a buffer. This constructor
+    /// may throw exception if \ref unpack function throws during buffer
+    /// parsing.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param begin iterator to first byte of option data.
+    /// @param end iterator to end of option data (first byte after option end).
+    ///
+    /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+    /// @throw isc::dhcp::InvalidDataType if data field type provided
+    /// as template parameter is not a supported integer type.
+    /// @todo Extend constructor to set encapsulated option space name.
+    OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+                 OptionBufferConstIter end);
+
+    /// Writes option in wire-format to buf, returns pointer to first unused
+    /// byte after stored option.
+    ///
+    /// @param [out] buf buffer (option will be stored here)
+    ///
+    /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+    /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+    /// because it is checked in a constructor.
+    void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses received buffer
+    ///
+    /// Parses received buffer and returns offset to the first unused byte after
+    /// parsed option.
+    ///
+    /// @param begin iterator to first byte of option data
+    /// @param end iterator to end of option data (first byte after option end)
+    ///
+    /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+    /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+    /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+    /// because it is checked in a constructor.
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Set option value.
+    ///
+    /// @param value new option value.
+    void setVendorId(uint32_t vendor_id) { vendor_id_ = vendor_id; }
+
+    /// @brief Return option value.
+    ///
+    /// @return option value.
+    uint32_t getVendorId() const { return vendor_id_; }
+
+    /// @brief returns complete length of option
+    ///
+    /// Returns length of this option, including option header and suboptions
+    ///
+    /// @return length of this option
+    virtual uint16_t len();
+
+private:
+
+    uint32_t vendor_id_;  ///< Enterprise-id 
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_H

+ 2 - 0
src/lib/dhcp/std_option_defs.h

@@ -16,6 +16,8 @@
 #define STD_OPTION_DEFS_H
 
 #include <dhcp/option_data_types.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
 
 namespace {
 

+ 1 - 1
src/lib/dhcpsrv/cfgmgr.h

@@ -355,7 +355,7 @@ private:
     /// A collection of option definitions that can be accessed
     /// using option space name they belong to.
     OptionSpaceContainer<OptionDefContainer,
-                         OptionDefinitionPtr> option_def_spaces_;
+        OptionDefinitionPtr, std::string> option_def_spaces_;
 
     /// @brief Container for defined DHCPv6 option spaces.
     OptionSpaceCollection spaces6_;

+ 64 - 5
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -429,6 +429,8 @@ OptionDataParser::createOption() {
                 << "')");
     }
 
+    const bool csv_format = boolean_values_->getParam("csv-format");
+
     // Find the Option Definition for the option by its option code.
     // findOptionDefinition will throw if not found, no need to test.
     OptionDefinitionPtr def;
@@ -449,7 +451,10 @@ OptionDataParser::createOption() {
         if (std::distance(range.first, range.second) > 0) {
             def = *range.first;
         }
-        if (!def) {
+
+        // It's ok if we don't have option format if the option is
+        // specified as hex
+        if (!def && csv_format) {
             isc_throw(DhcpConfigError, "definition for the option '"
                       << option_space << "." << option_name
                       << "' having code '" <<  option_code
@@ -459,7 +464,6 @@ OptionDataParser::createOption() {
 
     // Get option data from the configuration database ('data' field).
     const std::string option_data = string_values_->getParam("data");
-    const bool csv_format = boolean_values_->getParam("csv-format");
 
     // Transform string of hexadecimal digits into binary format.
     std::vector<uint8_t> binary;
@@ -1047,8 +1051,16 @@ SubnetConfigParser::createSubnet() {
             }
             // Add sub-options (if any).
             appendSubOptions(option_space, desc.option);
-            // In any case, we add the option to the subnet.
-            subnet_->addOption(desc.option, false, option_space);
+
+            // thomson
+            uint32_t vendor_id = optionSpaceToVendorId(option_space);
+            if (vendor_id) {
+                // This is a vendor option
+                subnet_->addVendorOption(desc.option, false, vendor_id);
+            } else {
+                // This is a normal option
+                subnet_->addOption(desc.option, false, option_space);
+            }
         }
     }
 
@@ -1077,12 +1089,59 @@ SubnetConfigParser::createSubnet() {
             if (!existing_desc.option) {
                 // Add sub-options (if any).
                 appendSubOptions(option_space, desc.option);
-                subnet_->addOption(desc.option, false, option_space);
+
+                uint32_t vendor_id = optionSpaceToVendorId(option_space);
+                if (vendor_id) {
+                    // This is a vendor option
+                    subnet_->addVendorOption(desc.option, false, vendor_id);
+                } else {
+                    // This is a normal option
+                    subnet_->addOption(desc.option, false, option_space);
+                }
             }
         }
     }
 }
 
+uint32_t
+SubnetConfigParser::optionSpaceToVendorId(const std::string& option_space) {
+    if (option_space.size() < 8) {
+        // 8 is a minimal length of "vendor-X" format
+        return (0);
+    }
+    if (option_space.substr(0,7) != "vendor-") {
+        return (0);
+    }
+
+    // text after "vendor-", supposedly numbers only
+    string x = option_space.substr(7);
+
+    int64_t check;
+    try {
+        check = boost::lexical_cast<int64_t>(x);
+    } catch (const boost::bad_lexical_cast &) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
+        //           << ") as unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check > std::numeric_limits<uint32_t>::max()) {
+        /// @todo: Should we throw here?
+        //isc_throw(BadValue, "Value " << x << "is too large"
+        //          << " for unsigned 32-bit integer.");
+        return (0);
+    }
+    if (check < 0) {
+        /// @todo: Should we throw here?
+        // isc_throw(BadValue, "Value " << x << "is negative."
+        //       << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+        return (0);
+    }
+
+    // value is small enough to fit
+    return (static_cast<uint32_t>(check));
+}
+
 isc::dhcp::Triplet<uint32_t>
 SubnetConfigParser::getParam(const std::string& name) {
     uint32_t value = 0;

+ 10 - 2
src/lib/dhcpsrv/dhcp_parsers.h

@@ -34,14 +34,14 @@ namespace dhcp {
 
 /// @brief Storage for option definitions.
 typedef OptionSpaceContainer<OptionDefContainer,
-                             OptionDefinitionPtr> OptionDefStorage;
+    OptionDefinitionPtr, std::string> OptionDefStorage;
 /// @brief Shared pointer to option definitions storage.
 typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
 
 /// Collection of containers holding option spaces. Each container within
 /// a particular option space holds so-called option descriptors.
 typedef OptionSpaceContainer<Subnet::OptionContainer,
-                             Subnet::OptionDescriptor> OptionStorage;
+    Subnet::OptionDescriptor, std::string> OptionStorage;
 /// @brief Shared pointer to option storage.
 typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 
@@ -773,6 +773,14 @@ public:
     /// @brief Adds the created subnet to a server's configuration.
     virtual void commit() = 0;
 
+    /// @brief tries to convert option_space string to numeric vendor_id
+    ///
+    /// This will work if the option_space has format "vendor-1234".
+    /// This is used to detect whether a given option-space is a vendor
+    /// space or not. Returns 0 if the format is different.
+    /// @return numeric vendor-id (or 0 if the format does not match)
+    static uint32_t optionSpaceToVendorId(const std::string& option_space);
+
 protected:
     /// @brief creates parsers for entries in subnet definition
     ///

+ 7 - 6
src/lib/dhcpsrv/option_space_container.h

@@ -31,7 +31,8 @@ namespace dhcp {
 /// @tparam ContainerType of the container holding items within
 /// option space.
 /// @tparam ItemType type of the item being held by the container.
-template<typename ContainerType, typename ItemType>
+/// @tparam Selector a string (for option spaces) or uint32_t (for vendor options)
+template<typename ContainerType, typename ItemType, typename Selector>
 class OptionSpaceContainer {
 public:
 
@@ -42,7 +43,7 @@ public:
     ///
     /// @param item reference to the item being added.
     /// @param option_space name of the option space.
-    void addItem(const ItemType& item, const std::string& option_space) {
+    void addItem(const ItemType& item, const Selector& option_space) {
         ItemsContainerPtr items = getItems(option_space);
         items->push_back(item);
         option_space_map_[option_space] = items;
@@ -57,7 +58,7 @@ public:
     /// @param option_space name of the option space.
     ///
     /// @return pointer to the container holding items.
-    ItemsContainerPtr getItems(const std::string& option_space) const {
+    ItemsContainerPtr getItems(const Selector& option_space) const {
         const typename OptionSpaceMap::const_iterator& items =
             option_space_map_.find(option_space);
         if (items == option_space_map_.end()) {
@@ -73,8 +74,8 @@ public:
     /// @todo This function is likely to be removed once
     /// we create a structore of OptionSpaces defined
     /// through the configuration manager.
-    std::list<std::string> getOptionSpaceNames() {
-        std::list<std::string> names;
+    std::list<Selector> getOptionSpaceNames() {
+        std::list<Selector> names;
         for (typename OptionSpaceMap::const_iterator space =
                  option_space_map_.begin();
              space != option_space_map_.end(); ++space) {
@@ -91,7 +92,7 @@ public:
 private:
 
     /// A map holding container (option space name is the key).
-    typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+    typedef std::map<Selector, ItemsContainerPtr> OptionSpaceMap;
     OptionSpaceMap option_space_map_;
 };
 

+ 32 - 0
src/lib/dhcpsrv/subnet.cc

@@ -88,6 +88,38 @@ Subnet::getOptionDescriptor(const std::string& option_space,
     return (*range.first);
 }
 
+void Subnet::addVendorOption(const OptionPtr& option, bool persistent,
+                             uint32_t vendor_id){
+
+    validateOption(option);
+
+    vendor_option_spaces_.addItem(OptionDescriptor(option, persistent), vendor_id);
+}
+
+Subnet::OptionContainerPtr
+Subnet::getVendorOptionDescriptors(uint32_t vendor_id) const {
+    return (vendor_option_spaces_.getItems(vendor_id));
+}
+
+Subnet::OptionDescriptor
+Subnet::getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code) {
+    OptionContainerPtr options = getVendorOptionDescriptors(vendor_id);
+    if (!options || options->empty()) {
+        return (OptionDescriptor(false));
+    }
+    const OptionContainerTypeIndex& idx = options->get<1>();
+    const OptionContainerTypeRange& range = idx.equal_range(option_code);
+    if (std::distance(range.first, range.second) == 0) {
+        return (OptionDescriptor(false));
+    }
+
+    return (*range.first);
+}
+
+void Subnet::delVendorOptions() {
+    vendor_option_spaces_.clearItems();
+}
+
 isc::asiolink::IOAddress Subnet::getLastAllocated(Lease::Type type) const {
     // check if the type is valid (and throw if it isn't)
     checkType(type);

+ 17 - 1
src/lib/dhcpsrv/subnet.h

@@ -180,9 +180,14 @@ public:
     void addOption(const OptionPtr& option, bool persistent,
                    const std::string& option_space);
 
+    void addVendorOption(const OptionPtr& option, bool persistent,
+                         uint32_t vendor_id);
+
     /// @brief Delete all options configured for the subnet.
     void delOptions();
 
+    void delVendorOptions();
+
     /// @brief checks if the specified address is in pools
     ///
     /// Note the difference between inSubnet() and inPool(). For a given
@@ -221,6 +226,9 @@ public:
     OptionContainerPtr
     getOptionDescriptors(const std::string& option_space) const;
 
+    OptionContainerPtr
+    getVendorOptionDescriptors(uint32_t vendor_id) const;
+
     /// @brief Return single option descriptor.
     ///
     /// @param option_space name of the option space.
@@ -232,6 +240,9 @@ public:
     getOptionDescriptor(const std::string& option_space,
                         const uint16_t option_code);
 
+    OptionDescriptor
+    getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code);
+
     /// @brief returns the last address that was tried from this pool
     ///
     /// This method returns the last address that was attempted to be allocated
@@ -440,9 +451,14 @@ private:
 
     /// A collection of option spaces grouping option descriptors.
     typedef OptionSpaceContainer<OptionContainer,
-                                 OptionDescriptor> OptionSpaceCollection;
+        OptionDescriptor, std::string> OptionSpaceCollection;
+
+    typedef OptionSpaceContainer<OptionContainer,
+        OptionDescriptor, uint32_t> VendorOptionSpaceCollection;
+
     OptionSpaceCollection option_spaces_;
 
+    VendorOptionSpaceCollection vendor_option_spaces_;
 };
 
 /// @brief A generic pointer to either Subnet4 or Subnet6 object

+ 70 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -597,6 +597,76 @@ TEST(Subnet6Test, getOptionDescriptor) {
     }
 }
 
+
+TEST(Subnet6Test, addVendorOptions) {
+
+    uint32_t vendor_id1 = 12345678;
+    uint32_t vendor_id2 = 87654321;
+    uint32_t vendor_id_bogus = 1111111;
+
+    // Create as subnet to add options to it.
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+    // Differentiate options by their codes (100-109)
+    for (uint16_t code = 100; code < 110; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id1));
+    }
+
+    // Add 7 options to another option space. The option codes partially overlap
+    // with option codes that we have added to dhcp6 option space.
+    for (uint16_t code = 105; code < 112; ++code) {
+        OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+        ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id2));
+    }
+
+    // Get options from the Subnet and check if all 10 are there.
+    Subnet::OptionContainerPtr options = subnet->getVendorOptionDescriptors(vendor_id1);
+    ASSERT_TRUE(options);
+    ASSERT_EQ(10, options->size());
+
+    // Validate codes of options added to dhcp6 option space.
+    uint16_t expected_code = 100;
+    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option);
+        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ++expected_code;
+    }
+
+    options = subnet->getVendorOptionDescriptors(vendor_id2);
+    ASSERT_TRUE(options);
+    ASSERT_EQ(7, options->size());
+
+    // Validate codes of options added to isc option space.
+    expected_code = 105;
+    for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+         option_desc != options->end(); ++option_desc) {
+        ASSERT_TRUE(option_desc->option);
+        EXPECT_EQ(expected_code, option_desc->option->getType());
+        ++expected_code;
+    }
+
+    // Try to get options from a non-existing option space.
+    options = subnet->getVendorOptionDescriptors(vendor_id_bogus);
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    // Delete options from all spaces.
+    subnet->delVendorOptions();
+
+    // Make sure that all options have been removed.
+    options = subnet->getVendorOptionDescriptors(vendor_id1);
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+
+    options = subnet->getVendorOptionDescriptors(vendor_id2);
+    ASSERT_TRUE(options);
+    EXPECT_TRUE(options->empty());
+}
+
+
+
 // This test verifies that inRange() and inPool() methods work properly.
 TEST(Subnet6Test, inRangeinPool) {
     Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));