Commit 639db0b5 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3194] Changes after review:

 - config parsers updated
 - new unit-tests for vendor options written
 - libdhcp++ cleaned up
 - fixes in option_vendor.cc
 - comments added and cleaned up
parent d7460a8c
......@@ -94,13 +94,13 @@ protected:
} else if (option_space == "dhcp6") {
isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
<< " for DHCPv6 server");
}
// Check if this is a vendor-option. If it is, get vendor-specific
// definition.
uint32_t vendor_id = SubnetConfigParser::optionSpaceToVendorId(option_space);
if (vendor_id) {
def = LibDHCP::getVendorOptionDef(Option::V4, vendor_id, option_code);
} 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);
......
......@@ -671,10 +671,8 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
// Let's try to get ORO within that vendor-option
/// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
/// may have different policies.
OptionPtr oro = vendor_req->getOption(DOCSIS3_V4_ORO);
/// @todo: see OPT_UINT8_TYPE definition in OptionDefinition::optionFactory().
/// I think it should be OptionUint8Array, not OptionGeneric
OptionUint8ArrayPtr oro =
boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
// Option ORO not found. Don't do anything then.
if (!oro) {
......@@ -685,9 +683,9 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
// Get the list of options that client requested.
bool added = false;
const OptionBuffer& requested_opts = oro->getData();
const std::vector<uint8_t>& requested_opts = oro->getData();
for (OptionBuffer::const_iterator code = requested_opts.begin();
for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
code != requested_opts.end(); ++code) {
Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, *code);
if (desc.option) {
......
......@@ -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>
......@@ -1794,6 +1795,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.
......
......@@ -40,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>
......@@ -1178,6 +1179,10 @@ 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) {
......@@ -1197,10 +1202,10 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
"\"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_ + "\" "
" }, {"
" \"pool\": [ \"192.0.3.0/25\" ],"
" \"subnet\": \"192.0.3.0/24\" "
" } ],"
"\"valid-lifetime\": 4000 }";
......@@ -2712,6 +2717,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
......@@ -109,16 +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);
}
}
// 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);
}
};
......
......@@ -705,8 +705,6 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer
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.
......@@ -718,6 +716,8 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer
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.
......
......@@ -2157,10 +2157,13 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
// Try to get the non-existing option from the non-existing
// option space and expect that option is not returned.
Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 38);
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) {
......
......@@ -2022,8 +2022,6 @@ TEST_F(Dhcpv6SrvTest, docsisTraffic) {
// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
TEST_F(Dhcpv6SrvTest, docsisVendorOptionsParse) {
NakedDhcpv6Srv srv(0);
// Let's get a traffic capture from DOCSIS3.0 modem
Pkt6Ptr sol = captureDocsisRelayedSolicit();
EXPECT_NO_THROW(sol->unpack());
......@@ -2035,10 +2033,10 @@ TEST_F(Dhcpv6SrvTest, docsisVendorOptionsParse) {
boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
ASSERT_TRUE(vendor);
EXPECT_TRUE(vendor->getOption(1));
EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_ORO));
EXPECT_TRUE(vendor->getOption(36));
EXPECT_TRUE(vendor->getOption(35));
EXPECT_TRUE(vendor->getOption(2));
EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_DEVICE_TYPE));
EXPECT_TRUE(vendor->getOption(3));
EXPECT_TRUE(vendor->getOption(4));
EXPECT_TRUE(vendor->getOption(5));
......@@ -2046,7 +2044,7 @@ TEST_F(Dhcpv6SrvTest, docsisVendorOptionsParse) {
EXPECT_TRUE(vendor->getOption(7));
EXPECT_TRUE(vendor->getOption(8));
EXPECT_TRUE(vendor->getOption(9));
EXPECT_TRUE(vendor->getOption(10));
EXPECT_TRUE(vendor->getOption(DOCSIS3_V6_VENDOR_NAME));
EXPECT_TRUE(vendor->getOption(15));
EXPECT_FALSE(vendor->getOption(20));
......@@ -2061,9 +2059,9 @@ TEST_F(Dhcpv6SrvTest, docsisVendorORO) {
// Let's get a traffic capture from DOCSIS3.0 modem
Pkt6Ptr sol = captureDocsisRelayedSolicit();
EXPECT_NO_THROW(sol->unpack());
ASSERT_NO_THROW(sol->unpack());
// Check if the packet contain
// Check if the packet contains vendor options option
OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
ASSERT_TRUE(opt);
......
......@@ -35,6 +35,7 @@ const OptionDefParams DOCSIS3_V4_DEFS[] = {
/// 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
......@@ -62,4 +63,4 @@ const int DOCSIS3_V6_DEFS_SIZE = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefPara
}; // anonymous namespace
#endif // STD_OPTION_DEFS_H
#endif // DOCSIS3_OPTION_DEFS_H
......@@ -604,57 +604,7 @@ 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));
}
for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
}
// 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);
}
initOptionSpace(v4option_defs_, OPTION_DEF_PARAMS4, OPTION_DEF_PARAMS_SIZE4);
}
void
......
......@@ -55,7 +55,13 @@ 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);
......@@ -154,16 +160,46 @@ 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)