Browse Source

[master] Merge branch 'trac3194_1'

Marcin Siodelski 11 years ago
parent
commit
db31405ea7

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

@@ -94,6 +94,13 @@ protected:
         } else if (option_space == "dhcp6") {
             isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
                      << " for DHCPv6 server");
+        } else {
+            // 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);

+ 68 - 5
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>
@@ -635,20 +638,78 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
     // to be returned to the client.
     for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
          opt != requested_opts.end(); ++opt) {
-        Subnet::OptionDescriptor desc =
-            subnet->getOptionDescriptor("dhcp4", *opt);
-        if (desc.option) {
-            msg->addOption(desc.option);
+        if (!msg->getOption(*opt)) {
+            Subnet::OptionDescriptor desc =
+                subnet->getOptionDescriptor("dhcp4", *opt);
+            if (desc.option && !msg->getOption(*opt)) {
+                msg->addOption(desc.option);
+            }
+        }
+    }
+}
+
+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.
+    OptionUint8ArrayPtr oro =
+        boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
+
+    // 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 std::vector<uint8_t>& requested_opts = oro->getValues();
+
+    for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
+         code != requested_opts.end(); ++code) {
+        if  (!vendor_rsp->getOption(*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).
     static const uint16_t required_options[] = {
-        DHO_SUBNET_MASK,
         DHO_ROUTERS,
         DHO_DOMAIN_NAME_SERVERS,
         DHO_DOMAIN_NAME };
@@ -863,6 +924,7 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
     // Adding any other options makes sense only when we got the lease.
     if (offer->getYiaddr() != IOAddress("0.0.0.0")) {
         appendRequestedOptions(discover, offer);
+        appendRequestedVendorOptions(discover, offer);
         // There are a few basic options that we always want to
         // include in the response. If client did not request
         // them we append them for him.
@@ -892,6 +954,7 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
     // Adding any other options makes sense only when we got the lease.
     if (ack->getYiaddr() != IOAddress("0.0.0.0")) {
         appendRequestedOptions(request, ack);
+        appendRequestedVendorOptions(request, ack);
         // There are a few basic options that we always want to
         // include in the response. If client did not request
         // them we append them for him.

+ 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

+ 115 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -23,6 +23,7 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks_manager.h>
@@ -1939,6 +1940,120 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(3));
 }
 
+// This test checks if vendor options can be specified in the config file
+// (in hex format), and later retrieved from configured subnet
+TEST_F(Dhcp4ParserTest, 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\"," // VENDOR_ID_CABLE_LABS = 4491
+        "    \"code\": 100," // just a random code
+        "    \"data\": \"AB CDEF0105\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"option-two\","
+        "    \"space\": \"vendor-1234\","
+        "    \"code\": 100,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": False"
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1-192.0.2.10\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now available for the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // Try to get the option from the vendor space 4491
+    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 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, 100);
+    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(Dhcp4ParserTest, 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\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Options should be now available for the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // Try to get the option from the vendor space 4491
+    Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(VENDOR_ID_CABLE_LABS, 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, 100);
+    ASSERT_FALSE(desc2.option);
+}
+
+
+
 // Tests of the hooks libraries configuration.  All tests have the pre-
 // condition (checked in the test fixture's SetUp() method) that no hooks
 // libraries are loaded at the start of the tests.

+ 270 - 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>
@@ -38,6 +40,7 @@
 #include <gtest/gtest.h>
 #include <hooks/server_hooks.h>
 #include <hooks/hooks_manager.h>
+#include <config/ccsession.h>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -1138,11 +1141,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 +1179,91 @@ TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
     EXPECT_TRUE(rai_response->equal(rai_query));
 }
 
+/// @todo move vendor options tests to a separate file.
+/// @todo Add more extensive vendor options tests, including multiple
+///       vendor options
+
+// 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\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " } ],"
+        "\"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
@@ -2763,6 +2847,187 @@ TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
     //EXPECT_EQ(leases.size(), 1);
 }
 
+// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
+TEST_F(Dhcpv4SrvTest, docsisVendorOptionsParse) {
+
+    // Let's get a traffic capture from DOCSIS3.0 modem
+    Pkt4Ptr dis = captureRelayedDiscover();
+    ASSERT_NO_THROW(dis->unpack());
+
+    // Check if the packet contain
+    OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_TRUE(opt);
+
+    boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+
+    // This particular capture that we have included options 1 and 5
+    EXPECT_TRUE(vendor->getOption(1));
+    EXPECT_TRUE(vendor->getOption(5));
+
+    // It did not include options any other options
+    EXPECT_FALSE(vendor->getOption(2));
+    EXPECT_FALSE(vendor->getOption(3));
+    EXPECT_FALSE(vendor->getOption(17));
+}
+
+// Checks if server is able to parse incoming docsis option and extract suboption 1 (docsis ORO)
+TEST_F(Dhcpv4SrvTest, docsisVendorORO) {
+
+    // Let's get a traffic capture from DOCSIS3.0 modem
+    Pkt4Ptr dis = captureRelayedDiscover();
+    EXPECT_NO_THROW(dis->unpack());
+
+    // Check if the packet contains vendor specific information option
+    OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_TRUE(opt);
+
+    boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+    ASSERT_TRUE(vendor);
+
+    opt = vendor->getOption(DOCSIS3_V4_ORO);
+    ASSERT_TRUE(opt);
+
+    OptionUint8ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint8Array>(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(Dhcpv4SrvTest, vendorOptionsORO) {
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"all\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-data\": [ {"
+        "          \"name\": \"tftp-servers\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": 2,"
+        "          \"data\": \"192.0.2.1, 192.0.2.2\","
+        "          \"csv-format\": True"
+        "        }],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/25\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
+    ASSERT_TRUE(x);
+    comment_ = isc::config::parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
+    // Set the giaddr to non-zero address as if it was relayed.
+    dis->setGiaddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr offer = srv.processDiscover(dis);
+
+    // check if we get response at all
+    ASSERT_TRUE(offer);
+
+    // We did not include any vendor opts in DISCOVER, so there should be none
+    // in OFFER.
+    ASSERT_FALSE(offer->getOption(DHO_VIVSO_SUBOPTIONS));
+
+    // 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<OptionUint8Array> vendor_oro(new OptionUint8Array(Option::V4,
+                                                                        DOCSIS3_V4_ORO));
+    vendor_oro->addValue(DOCSIS3_V4_TFTP_SERVERS); // Request option 33
+    OptionPtr vendor(new OptionVendor(Option::V4, 4491));
+    vendor->addOption(vendor_oro);
+    dis->addOption(vendor);
+
+    // Need to process SOLICIT again after requesting new option.
+    offer = srv.processDiscover(dis);
+    ASSERT_TRUE(offer);
+
+    // Check if thre is vendor option response
+    OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+    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 docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+    ASSERT_TRUE(docsis2);
+
+    Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+    ASSERT_TRUE(tftp_srvs);
+
+    Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
+    ASSERT_EQ(2, addrs.size());
+    EXPECT_EQ("192.0.2.1", addrs[0].toText());
+    EXPECT_EQ("192.0.2.2", addrs[1].toText());
+}
+
+// Test checks whether it is possible to use option definitions defined in
+// src/lib/dhcp/docsis3_option_defs.h.
+TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
+    ConstElementPtr x;
+    string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-data\": [ {"
+        "          \"name\": \"tftp-servers\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": ";
+    string config_postfix = ","
+        "          \"data\": \"192.0.2.1\","
+        "          \"csv-format\": True"
+        "        }],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.50\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"renew-timer\": 1000, "
+        "    \"rebind-timer\": 1000, "
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface\": \"\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // There is docsis3 (vendor-id=4491) vendor option 2, which is a
+    // tftp-server. Its format is list of IPv4 addresses.
+    string config_valid = config_prefix + "2" + 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);
+
+    NakedDhcpv4Srv srv(0);
+
+    // This should fail (missing option definition)
+    EXPECT_NO_THROW(x = configureDhcp4Server(srv, json_bogus));
+    ASSERT_TRUE(x);
+    comment_ = isc::config::parseAnswer(rcode_, x);
+    ASSERT_EQ(1, rcode_);
+
+    // This should work (option definition present)
+    EXPECT_NO_THROW(x = configureDhcp4Server(srv, json_valid));
+    ASSERT_TRUE(x);
+    comment_ = isc::config::parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+}
+
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 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

+ 8 - 1
src/bin/dhcp6/config_parser.cc

@@ -109,9 +109,16 @@ protected:
         } else if (option_space == "dhcp4") {
             isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
                      << " for DHCPv4 server");
+        } else {
+            // 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;
+        return (def);
     }
 };
 

+ 55 - 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;
+    }
+
+    // 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;
+    }
+
+    uint32_t vendor_id = vendor_req->getVendorId();
+
+    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);

+ 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

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

@@ -2049,6 +2049,121 @@ 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, 100);
+    ASSERT_FALSE(desc2.option);
+}
+
+/// @todo add tests similar to vendorOptionsCsv and vendorOptionsHex, but for
+///       vendor options defined in a subnet.
+
 // The goal of this test is to verify that the standard option can
 // be configured to encapsulate multiple other options.
 TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {

+ 204 - 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,208 @@ 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) {
+
+    // 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(DOCSIS3_V6_ORO));
+    EXPECT_TRUE(vendor->getOption(36));
+    EXPECT_TRUE(vendor->getOption(35));
+    EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_DEVICE_TYPE));
+    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(DOCSIS3_V6_VENDOR_NAME));
+    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();
+    ASSERT_NO_THROW(sol->unpack());
+
+    // Check if the packet contains vendor options option
+    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);
+
+    // 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 }";
 
-    /// @todo Check that the ADVERTISE is ok, that it includes all options,
-    /// that is relayed properly, etc.
+    // 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

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

@@ -0,0 +1,66 @@
+// 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);
+
+/// @todo define remaining docsis3 v6 codes
+#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 // DOCSIS3_OPTION_DEFS_H

+ 309 - 68
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(const 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(const 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) {
@@ -172,6 +244,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
@@ -296,6 +385,191 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
     return (offset);
 }
 
+size_t LibDHCP::unpackVendorOptions6(const 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(const 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) {
@@ -346,68 +620,35 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
 
 void
 LibDHCP::initStdOptionDefs4() {
-    v4option_defs_.clear();
-
-    // Now let's add all option definitions.
-    for (int i = 0; i < OPTION_DEF_PARAMS_SIZE4; ++i) {
-        std::string encapsulates(OPTION_DEF_PARAMS4[i].encapsulates);
-        if (!encapsulates.empty() && OPTION_DEF_PARAMS4[i].array) {
-            isc_throw(isc::BadValue, "invalid standard option definition: "
-                      << "option with code '" << OPTION_DEF_PARAMS4[i].code
-                      << "' may not encapsulate option space '"
-                      << encapsulates << "' because the definition"
-                      << " indicates that this option comprises an array"
-                      << " of values");
-        }
-
-        // Depending whether the option encapsulates an option space or not
-        // we pick different constructor to create an instance of the option
-        // definition.
-        OptionDefinitionPtr definition;
-        if (encapsulates.empty()) {
-            // Option does not encapsulate any option space.
-            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
-                                                  OPTION_DEF_PARAMS4[i].code,
-                                                  OPTION_DEF_PARAMS4[i].type,
-                                                  OPTION_DEF_PARAMS4[i].array));
-
-        } else {
-            // Option does encapsulate an option space.
-            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
-                                                  OPTION_DEF_PARAMS4[i].code,
-                                                  OPTION_DEF_PARAMS4[i].type,
-                                                  OPTION_DEF_PARAMS4[i].encapsulates));
-
-        }
+    initOptionSpace(v4option_defs_, OPTION_DEF_PARAMS4, OPTION_DEF_PARAMS_SIZE4);
+}
 
-        for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
-            definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
-        }
+void
+LibDHCP::initStdOptionDefs6() {
+    initOptionSpace(v6option_defs_, OPTION_DEF_PARAMS6, OPTION_DEF_PARAMS_SIZE6);
+}
 
-        // Sanity check if the option is valid.
-        try {
-            definition->validate();
-        } catch (const 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.
-            v4option_defs_.clear();
-            throw;
-        }
-        v4option_defs_.push_back(definition);
-    }
+void
+LibDHCP::initVendorOptsDocsis4() {
+    initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE);
 }
 
 void
-LibDHCP::initStdOptionDefs6() {
-    v6option_defs_.clear();
+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 < OPTION_DEF_PARAMS_SIZE6; ++i) {
-        std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
-        if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
+    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"
@@ -420,33 +661,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);
     }
 }

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

@@ -55,6 +55,17 @@ public:
     static OptionDefinitionPtr getOptionDef(const Option::Universe u,
                                             const uint16_t code);
 
+    /// @brief Returns vendor option definition for a given vendor-id and code
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param vendor_id enterprise-id for a given vendor
+    /// @param code option code
+    /// @return reference to an option definition being requested
+    /// or NULL pointer if option definition has not been found.
+    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)
@@ -155,6 +166,49 @@ public:
                                       uint16_t type,
                                       Option::Factory * factory);
 
+    /// @brief Returns v4 option definitions for a given vendor
+    ///
+    /// @param vendor_id enterprise-id of a given vendor
+    /// @return a container for a given vendor (or NULL if not option
+    ///         definitions are defined)
+    static const OptionDefContainer*
+    getVendorOption4Defs(const uint32_t vendor_id);
+
+    /// @brief Returns v6 option definitions for a given vendor
+    ///
+    /// @param vendor_id enterprise-id of a given vendor
+    /// @return a container for a given vendor (or NULL if not option
+    ///         definitions are defined)
+    static const OptionDefContainer*
+    getVendorOption6Defs(const uint32_t vendor_id);
+
+    /// @brief Parses provided buffer as DHCPv6 vendor options and creates
+    ///        Option objects.
+    ///
+    /// Parses provided buffer and stores created Option objects
+    /// in options container.
+    ///
+    /// @param vendor_id enterprise-id of the vendor
+    /// @param buf Buffer to be parsed.
+    /// @param options Reference to option container. Options will be
+    ///        put here.
+    static size_t unpackVendorOptions6(const uint32_t vendor_id,
+                                       const OptionBuffer& buf,
+                                       isc::dhcp::OptionCollection& options);
+
+    /// @brief Parses provided buffer as DHCPv4 vendor options and creates
+    ///        Option objects.
+    ///
+    /// Parses provided buffer and stores created Option objects
+    /// in options container.
+    ///
+    /// @param vendor_id enterprise-id of the vendor
+    /// @param buf Buffer to be parsed.
+    /// @param options Reference to option container. Options will be
+    ///        put here.
+    static size_t unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
+                                       isc::dhcp::OptionCollection& options);
+
 private:
 
     /// Initialize standard DHCPv4 option definitions.
@@ -176,6 +230,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_;
 
@@ -187,6 +245,12 @@ private:
 
     /// Container with DHCPv6 option definitions.
     static OptionDefContainer v6option_defs_;
+
+    /// Container for v4 vendor option definitions
+    static VendorOptionDefContainers vendor4_defs_;
+
+    /// Container for v6 vendor option definitions
+    static VendorOptionDefContainers vendor6_defs_;
 };
 
 }

+ 70 - 29
src/lib/dhcp/option_definition.cc

@@ -26,6 +26,7 @@
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <boost/algorithm/string/classification.hpp>
@@ -115,7 +116,19 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                                 OptionBufferConstIter begin,
                                 OptionBufferConstIter end,
                                 UnpackOptionsCallback callback) const {
+
     try {
+        // Some of the options are represented by the specialized classes derived
+        // from Option class (e.g. IA_NA, IAADDR). Although, they can be also
+        // represented by the generic classes, we want the object of the specialized
+        // type to be returned. Therefore, we first check that if we are dealing
+        // with such an option. If the instance is returned we just exit at this
+        // point. If not, we will search for a generic option type to return.
+        OptionPtr option = factorySpecialFormatOption(u, begin, end, callback);
+        if (option) {
+            return (option);
+        }
+
         switch(type_) {
         case OPT_EMPTY_TYPE:
             if (getEncapsulatedSpace().empty()) {
@@ -186,37 +199,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
             return (OptionPtr(new OptionString(u, type, begin, end)));
 
         default:
-            if (u == Option::V6) {
-                if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
-                    haveIA6Format()) {
-                    // Return Option6IA instance for IA_PD and IA_NA option
-                    // types only. We don't want to return Option6IA for other
-                    // options that comprise 3 UINT32 data fields because
-                    // Option6IA accessors' and modifiers' names are derived
-                    // from the IA_NA and IA_PD options' field names: IAID,
-                    // T1, T2. Using functions such as getIAID, getT1 etc. for
-                    // options other than IA_NA and IA_PD would be bad practice
-                    // and cause confusion.
-                    return (factoryIA6(type, begin, end));
-
-                } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
-                    // Rerurn Option6IAAddr option instance for the IAADDR
-                    // option only for the same reasons as described in
-                    // for IA_NA and IA_PD above.
-                    return (factoryIAAddr6(type, begin, end));
-                } else if (code_ == D6O_CLIENT_FQDN && haveClientFqdnFormat()) {
-                    // FQDN option requires special processing. Thus, there is
-                    // a specialized class to handle it.
-                    return (OptionPtr(new Option6ClientFqdn(begin, end)));
-                }
-            } else {
-                if ((code_ == DHO_FQDN) && haveFqdn4Format()) {
-                    return (OptionPtr(new Option4ClientFqdn(begin, end)));
-                }
-            }
+            // Do nothing. We will return generic option a few lines down.
+            ;
         }
         return (OptionPtr(new OptionCustom(*this, u, begin, end)));
-
     } catch (const Exception& ex) {
         isc_throw(InvalidOptionValue, ex.what());
     }
@@ -392,6 +378,16 @@ OptionDefinition::haveClientFqdnFormat() const {
             (record_fields_[1] == OPT_FQDN_TYPE));
 }
 
+bool
+OptionDefinition::haveVendor4Format() const {
+    return (true);
+}
+
+bool
+OptionDefinition::haveVendor6Format() const {
+    return  (getType() == OPT_UINT32_TYPE && !getEncapsulatedSpace().empty());
+}
+
 template<typename T>
 T
 OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
@@ -574,6 +570,51 @@ OptionDefinition::factoryIAAddr6(uint16_t type,
     return (option);
 }
 
+OptionPtr
+OptionDefinition::factorySpecialFormatOption(Option::Universe u,
+                                             OptionBufferConstIter begin,
+                                             OptionBufferConstIter end,
+                                             UnpackOptionsCallback) const {
+    if (u == Option::V6) {
+        if ((getCode() == D6O_IA_NA || getCode() == D6O_IA_PD) &&
+            haveIA6Format()) {
+            // Return Option6IA instance for IA_PD and IA_NA option
+            // types only. We don't want to return Option6IA for other
+            // options that comprise 3 UINT32 data fields because
+            // Option6IA accessors' and modifiers' names are derived
+            // from the IA_NA and IA_PD options' field names: IAID,
+            // T1, T2. Using functions such as getIAID, getT1 etc. for
+            // options other than IA_NA and IA_PD would be bad practice
+            // and cause confusion.
+            return (factoryIA6(getCode(), begin, end));
+
+        } else if (getCode() == D6O_IAADDR && haveIAAddr6Format()) {
+            // Rerurn Option6IAAddr option instance for the IAADDR
+            // option only for the same reasons as described in
+            // for IA_NA and IA_PD above.
+            return (factoryIAAddr6(getCode(), begin, end));
+        } else if (getCode() == D6O_CLIENT_FQDN && haveClientFqdnFormat()) {
+            // FQDN option requires special processing. Thus, there is
+            // a specialized class to handle it.
+            return (OptionPtr(new Option6ClientFqdn(begin, end)));
+
+        } else if (getCode() == D6O_VENDOR_OPTS && haveVendor6Format()) {
+            // Vendor-Specific Information.
+            return (OptionPtr(new OptionVendor(Option::V6, begin, end)));
+
+        }
+    } else {
+        if ((getCode() == DHO_FQDN) && haveFqdn4Format()) {
+            return (OptionPtr(new Option4ClientFqdn(begin, end)));
+
+        } else if (getCode() == DHO_VIVSO_SUBOPTIONS && haveVendor4Format()) {
+            // Vendor-Specific Information.
+            return (OptionPtr(new OptionVendor(Option::V4, begin, end)));
+
+        }
+    }
+    return (OptionPtr());
+}
 
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 51 - 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 {
@@ -296,6 +297,28 @@ public:
     /// %Option.
     bool haveFqdn4Format() const;
 
+    /// @brief Check if the option has format of Vendor-Identifying Vendor
+    /// Specific Options.
+    ///
+    /// @return Always true.
+    /// @todo The Vendor-Identifying Vendor-Specific Option has a complex format
+    /// which we do not support here. Therefore it is not really possible to
+    /// check that the current definition is valid. We may need to add support
+    /// for such option format or simply do not check the format for certain
+    /// options, e.g. vendor options, IA_NA, IAADDR and always return objects
+    /// of the certain type.
+    bool haveVendor4Format() const;
+
+    /// @brief Check if option has a format of the Vendor-Specific Information
+    /// %Option.
+    ///
+    /// The Vendor-Specific Information %Option comprises 32-bit enterprise id
+    /// and the suboptions.
+    ///
+    /// @return true if option definition conforms to the format of the
+    /// Vendor-Specific Information %Option.
+    bool haveVendor6Format() const;
+
     /// @brief Option factory.
     ///
     /// This function creates an instance of DHCP option using
@@ -492,6 +515,31 @@ public:
 
 private:
 
+    /// @brief Creates an instance of an option having special format.
+    ///
+    /// The option with special formats are encapsulated by the dedicated
+    /// classes derived from @c Option class. In particular these are:
+    /// - IA_NA
+    /// - IAADDR
+    /// - FQDN
+    /// - VIVSO.
+    ///
+    /// @param u A universe (V4 or V6).
+    /// @param begin beginning of the option buffer.
+    /// @param end end of the option buffer.
+    /// @param callback An instance of the function which parses packet options.
+    /// If this is set to non NULL value this function will be used instead of
+    /// @c isc::dhcp::LibDHCP::unpackOptions6 and
+    /// isc::dhcp::LibDHCP::unpackOptions4.
+    ///
+    /// @return An instance of the option having special format or NULL if
+    /// such an option can't be created because an option with the given
+    /// option code hasn't got the special format.
+    OptionPtr factorySpecialFormatOption(Option::Universe u,
+                                         OptionBufferConstIter begin,
+                                         OptionBufferConstIter end,
+                                         UnpackOptionsCallback callback) const;
+
     /// @brief Check if specified option format is a record with 3 fields
     /// where first one is custom, and two others are uint32.
     ///
@@ -601,6 +649,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

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

@@ -0,0 +1,85 @@
+// 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, const 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);
+
+    // Store vendor-id
+    buf.writeUint32(vendor_id_);
+
+    // The format is slightly different for v4
+    if (universe_ == Option::V4) {
+        // Calculate and store data-len as follows:
+        // data-len = total option length - header length
+        //            - enterprise id field length - data-len field size
+        buf.writeUint8(len() - getHeaderLen() -
+                       sizeof(uint32_t) - sizeof(uint8_t));
+    }
+
+    packOptions(buf);
+}
+
+void OptionVendor::unpack(OptionBufferConstIter begin,
+                          OptionBufferConstIter end) {
+    if (distance(begin, end) < sizeof(uint32_t)) {
+        isc_throw(OutOfRange, "Truncated vendor-specific information option"
+                  << ", length=" << distance(begin, end));
+    }
+
+    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
+
+    // Data-len field exists in DHCPv4 vendor options only
+    if (universe_ == Option::V4) {
+        length += sizeof(uint8_t);  // data-len
+    }
+
+    // length of all suboptions
+    for (OptionCollection::iterator it = options_.begin();
+         it != options_.end();
+         ++it) {
+        length += (*it).second->len();
+    }
+    return (length);
+}

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

@@ -0,0 +1,101 @@
+// 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 {
+
+/// @brief This class represents vendor-specific information option.
+///
+/// As specified in RFC3925, the option formatting is slightly different
+/// for DHCPv4 than DHCPv6. The DHCPv4 Option includes additional field
+/// holding vendor data length.
+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, const 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.
+    /// @todo Extend constructor to set encapsulated option space name.
+    OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+                 OptionBufferConstIter end);
+
+    /// @brief 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)
+    virtual 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.
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Sets enterprise identifier
+    ///
+    /// @param value vendor identifier
+    void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
+
+    /// @brief Returns enterprise identifier
+    ///
+    /// @return enterprise identifier
+    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
+};
+
+/// Pointer to a vendor option
+typedef boost::shared_ptr<OptionVendor> OptionVendorPtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_VENDOR_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 - 0
src/lib/dhcp/tests/Makefile.am

@@ -45,6 +45,7 @@ libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += option_string_unittest.cc
+libdhcp___unittests_SOURCES += option_vendor_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc

+ 3 - 2
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -27,6 +27,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
 #include <util/buffer.h>
 
 #include <gtest/gtest.h>
@@ -937,7 +938,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(Option));
 
     LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, begin, end,
-                                    typeid(Option));
+                                    typeid(OptionVendor));
 }
 
 // Test that definitions of standard options have been initialized
@@ -1014,7 +1015,7 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(OptionCustom));
 
     LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
-                                    typeid(OptionInt<uint32_t>),
+                                    typeid(OptionVendor),
                                     "vendor-opts-space");
 
     LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,

+ 240 - 0
src/lib/dhcp/tests/option_vendor_unittest.cc

@@ -0,0 +1,240 @@
+// 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 <config.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_int_array.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+
+class OptionVendorTest : public ::testing::Test {
+public:
+    OptionVendorTest() {
+    }
+
+    OptionBuffer createV4VendorOptions() {
+
+        // Copied from wireshark, file docsis-*-CG3000DCR-Registration-Filtered.cap
+        // packet #1
+        /* V-I Vendor-specific Information (125)
+           Length: 127
+           Enterprise ID: Cable Television Laboratories, Inc. (4491)
+           Suboption 1: Option Request
+           Suboption 5: Modem capabilties */
+        string from_wireshark = "7d7f0000118b7a01010205750101010201030301010401"
+            "0105010106010107010f0801100901030a01010b01180c01010d0200400e020010"
+            "0f010110040000000211010014010015013f1601011701011801041901041a0104"
+            "1b01201c01021d01081e01201f0110200110210102220101230100240100250101"
+            "260200ff270101";
+
+        OptionBuffer bin;
+        // Decode the hex string and store it in bin (which happens
+        // to be OptionBuffer format)
+        isc::util::encode::decodeHex(from_wireshark, bin);
+
+        return (bin);
+    }
+
+    OptionBuffer createV6VendorOption() {
+
+        // Copied from wireshark, docsis-CG3000DCR-Registration-v6CMM-Filtered.cap
+        // packet #1 (v6 vendor option with lots of cable modem specific data)
+        string from_wireshark = "001100ff0000118b0001000a0020002100220025002600"
+            "02000345434d0003000b45434d3a45524f555445520004000d3242523232395534"
+            "303034344300050004312e30340006000856312e33332e303300070007322e332e"
+            "3052320008000630303039354200090009434733303030444352000a00074e6574"
+            "6765617200230077057501010102010303010104010105010106010107010f0801"
+            "100901030a01010b01180c01010d0200400e0200100f0101100400000002110100"
+            "14010015013f1601011701011801041901041a01041b01201c01021d01081e0120"
+            "1f0110200110210102220101230100240100250101260200ff2701010024000620"
+            "e52ab81514";
+        /* Vendor-specific Information
+                Option: Vendor-specific Information (17)
+                Length: 255
+                Value: 0000118b0001000a00200021002200250026000200034543...
+                Enterprise ID: Cable Television Laboratories, Inc. (4491)
+                Suboption 1: Option Request =  32 33 34 37 38
+                Suboption 2: Device Type = "ECM"
+                Suboption 3: Embedded Components = "ECM:EROUTER"
+                Suboption 4: Serial Number = "2BR229U40044C"
+                Suboption 5: Hardware Version = "1.04"
+                Suboption 6: Software Version = "V1.33.03"
+                Suboption 7: Boot ROM Version = "2.3.0R2"
+                Suboption 8: Organization Unique Identifier = "00095B"
+                Suboption 9: Model Number = "CG3000DCR"
+                Suboption 10: Vendor Name = "Netgear"
+                Suboption 35: TLV5 = 057501010102010303010104010105010106010107010f08...
+                Suboption 36: Device Identifier = 20e52ab81514 */
+
+        OptionBuffer bin;
+        // Decode the hex string and store it in bin (which happens
+        // to be OptionBuffer format)
+        isc::util::encode::decodeHex(from_wireshark, bin);
+
+        return (bin);
+    }
+};
+
+// Basic test for v4 vendor option functionality
+TEST_F(OptionVendorTest, v4Basic) {
+
+    uint32_t vendor_id = 1234;
+
+    scoped_ptr<Option> opt;
+    EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V4, vendor_id)));
+
+    EXPECT_EQ(Option::V4, opt->getUniverse());
+    EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, opt->getType());
+
+    // Minimal length is 7: 1(type) + 1(length) + 4(vendor-id) + datalen(1)
+    EXPECT_EQ(7, opt->len());
+
+    // Check destructor
+    EXPECT_NO_THROW(opt.reset());
+}
+
+// Basic test for v6 vendor option functionality
+TEST_F(OptionVendorTest, v6Basic) {
+
+    uint32_t vendor_id = 1234;
+
+    scoped_ptr<Option> opt;
+    EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V6, vendor_id)));
+
+    EXPECT_EQ(Option::V6, opt->getUniverse());
+    EXPECT_EQ(D6O_VENDOR_OPTS, opt->getType());
+
+    // Minimal length is 8: 2(type) + 2(length) + 4(vendor-id)
+    EXPECT_EQ(8, opt->len());
+
+    // Check destructor
+    EXPECT_NO_THROW(opt.reset());
+}
+
+// Tests whether we can parse v4 vendor options properly
+TEST_F(OptionVendorTest, v4Parse) {
+    OptionBuffer binary = createV4VendorOptions();
+
+    // Let's create vendor option based on incoming buffer
+    OptionVendorPtr vendor;
+    ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2,
+                                                  binary.end())));
+
+    // We know that there are supposed to be 2 options inside
+    EXPECT_TRUE(vendor->getOption(DOCSIS3_V4_ORO));
+    EXPECT_TRUE(vendor->getOption(5));
+}
+
+// Tests whether we can parse and then pack a v4 option.
+TEST_F(OptionVendorTest, packUnpack4) {
+    OptionBuffer binary = createV4VendorOptions();
+
+    OptionVendorPtr vendor;
+
+    // Create vendor option (ignore the first 2 bytes, these are option code
+    // and option length
+    ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2,
+                                                  binary.end())));
+
+    OutputBuffer output(0);
+
+    EXPECT_NO_THROW(vendor->pack(output));
+
+    ASSERT_EQ(binary.size(), output.getLength());
+
+    // We're lucky, because the packet capture we have happens to have options
+    // with monotonically increasing values (1 and 5), so our pack() method
+    // will pack them in exactly the same order as in the original.
+    EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength()));
+}
+
+// Tests whether we can parse v6 vendor options properly
+TEST_F(OptionVendorTest, v6Parse) {
+    OptionBuffer binary = createV6VendorOption();
+
+    OptionVendorPtr vendor;
+    // Create vendor option (ignore the first 4 bytes. These are option code
+    // (2 bytes) and option length (2 bytes).
+    ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4,
+                                                  binary.end())));
+
+    OptionPtr opt;
+    opt = vendor->getOption(DOCSIS3_V6_ORO);
+    ASSERT_TRUE(opt);
+    OptionUint16ArrayPtr oro =
+        boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+
+    // Check that all remaining expected options are there
+    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(35));
+    EXPECT_TRUE(vendor->getOption(36));
+
+    // Check that there are no other options there
+    for (uint16_t i = 11; i < 35; ++i) {
+        EXPECT_FALSE(vendor->getOption(i));
+    }
+
+    for (uint16_t i = 37; i < 65535; ++i) {
+        EXPECT_FALSE(vendor->getOption(i));
+    }
+}
+
+// Tests whether we can parse and then pack a v6 option.
+TEST_F(OptionVendorTest, packUnpack6) {
+    OptionBuffer binary = createV6VendorOption();
+
+    OptionVendorPtr vendor;
+
+    // Create vendor option (ignore the first 4 bytes. These are option code
+    // (2 bytes) and option length (2 bytes).
+    ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4,
+                                                  binary.end())));
+
+    OutputBuffer output(0);
+
+    EXPECT_NO_THROW(vendor->pack(output));
+
+    ASSERT_EQ(binary.size(), output.getLength());
+    EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength()));
+}
+
+}

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

@@ -366,7 +366,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

@@ -430,6 +430,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;
@@ -450,7 +452,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
@@ -460,7 +465,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;
@@ -1048,8 +1052,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);
+
+            // Check if the option space defines a vendor-option
+            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);
+            }
         }
     }
 
@@ -1078,12 +1090,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;

+ 11 - 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,15 @@ 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-X", where
+    /// X can be any value between 1 and MAX_UINT32.
+    /// 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
     ///

+ 10 - 9
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:
 
@@ -41,8 +42,8 @@ public:
     /// @brief Adds a new item to the option_space.
     ///
     /// @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) {
+    /// @param option_space name or vendor-id of the 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;
@@ -54,10 +55,10 @@ public:
     /// space an empty container is created and returned. However
     /// this container is not added to the list of option spaces.
     ///
-    /// @param option_space name of the option space.
+    /// @param option_space name or vendor-id 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) {
@@ -90,8 +91,8 @@ public:
 
 private:
 
-    /// A map holding container (option space name is the key).
-    typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+    /// A map holding container (option space name or vendor-id is the key).
+    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);

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

@@ -180,9 +180,22 @@ public:
     void addOption(const OptionPtr& option, bool persistent,
                    const std::string& option_space);
 
+
+    /// @brief Adds new vendor option instance to the collection.
+    ///
+    /// @param option option instance.
+    /// @param persistent if true, send an option regardless if client
+    /// requested it or not.
+    /// @param vendor_id enterprise id of the vendor space to add an option to.
+    void addVendorOption(const OptionPtr& option, bool persistent,
+                         uint32_t vendor_id);
+
     /// @brief Delete all options configured for the subnet.
     void delOptions();
 
+    /// @brief Deletes all vendor options configured for the subnet.
+    void delVendorOptions();
+
     /// @brief checks if the specified address is in pools
     ///
     /// Note the difference between inSubnet() and inPool(). For a given
@@ -221,6 +234,14 @@ public:
     OptionContainerPtr
     getOptionDescriptors(const std::string& option_space) const;
 
+    /// @brief Return a collection of vendor option descriptors.
+    ///
+    /// @param vendor_id enterprise id of the option space.
+    ///
+    /// @return pointer to collection of options configured for a subnet.
+    OptionContainerPtr
+    getVendorOptionDescriptors(uint32_t vendor_id) const;
+
     /// @brief Return single option descriptor.
     ///
     /// @param option_space name of the option space.
@@ -232,6 +253,16 @@ public:
     getOptionDescriptor(const std::string& option_space,
                         const uint16_t option_code);
 
+    /// @brief Return single vendor option descriptor.
+    ///
+    /// @param vendor_id enterprise id of the option space.
+    /// @param option_code code of the option to be returned.
+    ///
+    /// @return option descriptor found for the specified option space
+    /// and 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 +471,17 @@ private:
 
     /// A collection of option spaces grouping option descriptors.
     typedef OptionSpaceContainer<OptionContainer,
-                                 OptionDescriptor> OptionSpaceCollection;
+        OptionDescriptor, std::string> OptionSpaceCollection;
+
+    /// A collection of vendor space option descriptors.
+    typedef OptionSpaceContainer<OptionContainer,
+        OptionDescriptor, uint32_t> VendorOptionSpaceCollection;
+
+    /// Regular options are kept here
     OptionSpaceCollection option_spaces_;
 
+    /// Vendor options are kept here
+    VendorOptionSpaceCollection vendor_option_spaces_;
 };
 
 /// @brief A generic pointer to either Subnet4 or Subnet6 object

+ 10 - 0
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -250,6 +250,16 @@ TEST_F(DhcpParserTest, interfaceListParserTest) {
     EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
 }
 
+// Checks whether option space can be detected as vendor-id
+TEST_F(DhcpParserTest, vendorOptionSpace) {
+    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId(""));
+    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId("dhcp4"));
+    EXPECT_EQ(0, SubnetConfigParser::optionSpaceToVendorId("vendor-"));
+    EXPECT_EQ(1, SubnetConfigParser::optionSpaceToVendorId("vendor-1"));
+    EXPECT_EQ(4491, SubnetConfigParser::optionSpaceToVendorId("vendor-4491"));
+    EXPECT_EQ(12345678, SubnetConfigParser::optionSpaceToVendorId("vendor-12345678"));
+}
+
 /// @brief Test Implementation of abstract OptionDataParser class. Allows
 /// testing basic option parsing.
 class UtestOptionDataParser : public OptionDataParser {

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

@@ -615,6 +615,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));