| 2 | tftp-servers | a list of IPv4 addresses of TFTP servers to be used by the cable modem |
+-------------+--------------+------------------------------------------------------------------------+
-In Kea each vendor is represented by its own vendor space. Since there
-are hundreds of vendors and sometimes they use different option
-definitions for different hardware, it is impossible for Kea to support
-them all natively. Fortunately, it's easy to define support for
-new vendor options. Let's take an example of the Genexis home gateway. This
-device requires sending the vivso 125 option with a sub-option 2 that
-contains a string with the TFTP server URL. To support such a device, three
-steps are needed: first, we need to define option definitions that will
-explain how the option is supposed to be formed. Second, we need to
-define option values. Third, we need to tell Kea when to send those
+In Kea each vendor is represented by its own vendor space. Since there are
+hundreds of vendors and sometimes they use different option definitions for
+different hardware, it is impossible for Kea to support them all natively.
+Fortunately, it's easy to define support for new vendor options. Let's take an
+example of the Genexis home gateway. This device requires sending the vivso 125
+option with a sub-option 2 that contains a string with the TFTP server URL. To
+support such a device, three steps are needed: first, we need to define option
+definitions that will explain how the option is supposed to be formed. Second,
+we need to define option values. Third, we need to tell Kea when to send those
specific options, which we can do via client classification.
An example snippet of a configuration could look similar to the
::
{
- // First, we need to define that the suboption 2 in vivso option for
+ // First, we need to define that the sub-option 2 in vivso option for
// vendor-id 25167 has a specific format (it's a plain string in this example).
// After this definition, we can specify values for option tftp.
"option-def": [
"test": "substring(option[60].hex,0,7) == 'HMC1000'",
// Once the device is recognized, we want to send two options:
- // the vivso option with vendor-id set to 25167, and a suboption 2.
+ // the vivso option with vendor-id set to 25167, and a sub-option 2.
"option-data": [
{
"name": "vivso-suboptions",
"data": "25167"
},
- // The suboption 2 value is defined as any other option. However,
- // we want to send this suboption 2, even when the client didn't
+ // The sub-option 2 value is defined as any other option. However,
+ // we want to send this sub-option 2, even when the client didn't
// explicitly request it (often there is no way to do that for
// vendor options). Therefore we use always-send to force Kea
// to always send this option when 25167 vendor space is involved.
} ]
}
-By default, Kea sends back
-only those options that are requested by a client, unless there are
-protocol rules that tell the DHCP server to always send an option. This
-approach works nicely in most cases and avoids problems with clients
-refusing responses with options they do not understand. However,
-the situation with vendor options is more complex, as they
-are not requested the same way as other options, are
-not well-documented in official RFCs, or vary by vendor.
-
-Some vendors (such
-as DOCSIS, identified by vendor option 4491) have a mechanism to
-request specific vendor options and Kea is able to honor those.
-Unfortunately, for many other vendors, such as Genexis (25167, discussed
-above), Kea does not have such a mechanism, so it cannot send any
-sub-options on its own. To solve this issue, we devised the concept of
-persistent options. Kea can be told to always send options, even if the
-client did not request them. This can be achieved by adding
-``"always-send": true`` to the option definition. Note that in this
-particular case an option is defined in vendor space 25167. With
-``always-send`` enabled, the option is sent every time there is a
-need to deal with vendor space 25167.
+By default, Kea sends back only those options that are requested by a client,
+unless there are protocol rules that tell the DHCP server to always send an
+option. This approach works nicely in most cases and avoids problems with
+clients refusing responses with options they do not understand. However, the
+situation with vendor options is more complex, as they are not requested the
+same way as other options, are not well-documented in official RFCs, or vary by
+vendor.
+
+Some vendors (such as DOCSIS, identified by vendor option 4491) have a mechanism
+to request specific vendor options and Kea is able to honor those (sub-option 1).
+Unfortunately, for many other vendors, such as Genexis (25167, discussed above),
+Kea does not have such a mechanism, so it cannot send any sub-options on its own.
+To solve this issue, we devised the concept of persistent options. Kea can be
+told to always send options, even if the client did not request them. This can
+be achieved by adding ``"always-send": true`` to the option definition. Note
+that in this particular case an option is defined in vendor space 25167. With
+``always-send`` enabled, the option is sent every time there is a need to deal
+with vendor space 25167.
+This is also how the Kea server can be configured to send multiple vendor
+enterprise numbers and multiple options, specific for each vendor.
Another possibility is to redefine the option; see :ref:`dhcp4-private-opts`.
.. note::
- Currently only one vendor is supported for the ``vivco-suboptions`` (code 124)
- and ``vivso-suboptions`` (code 125) options. Specifying
- multiple enterprise numbers within a single option instance or multiple
- options with different enterprise numbers is not supported.
+ Multiple vendor enterprise numbers are supported by ``vivso-suboptions``
+ (code 125) option. The option can contain multiple options for each vendor.
+
+ Kea will honor DOCSIS sub-option 1 (ORO) and will add only requested options
+ if this sub-option is present in the client request.
+
+ Currently only one vendor is supported for the ``vivco-suboptions``
+ (code 124) option. Specifying multiple enterprise numbers within a single
+ option instance or multiple options with different enterprise numbers is not
+ supported.
.. _dhcp4-option-spaces:
------------------------
The kea-dhcp4 server partially supports long options (RFC3396).
-Since Kea 2.1.6, the server accepts configuring long options and suboptions
-(longer than 255 bytes). The options and suboptions are stored internally
+Since Kea 2.1.6, the server accepts configuring long options and sub-options
+(longer than 255 bytes). The options and sub-options are stored internally
in their unwrapped form and they can be processed as usual using the parser
-language. On send, the server splits long options and suboptions into multiple
-options and suboptions, using the respective option code.
+language. On send, the server splits long options and sub-options into multiple
+options and sub-options, using the respective option code.
::
The server is also able to receive packets with split options (options using
the same option code) and to fuse the data chunks into one option. This is
-also supported for suboptions if each suboption data chunk also contains the
-suboption code and suboption length.
+also supported for sub-options if each sub-option data chunk also contains the
+sub-option code and sub-option length.
.. _dhcp4-stateless-configuration:
{ "ISC": { "relay-agent-info": { "sub-options": "0x0104AABBCCDD" } } }
-Or with remote and relay suboptions:
+Or with remote and relay sub-options:
::
-------------------------
With ``"ignore-rai-link-selection": true``, Relay Agent Information Link
-Selection suboption data will not be used for subnet selection. This will use
+Selection sub-option data will not be used for subnet selection. This will use
normal subnet selection logic instead of attempting to use the subnet specified
-by the suboption. This option is not RFC compliant and is set to ``false`` by
+by the sub-option. This option is not RFC compliant and is set to ``false`` by
default. Setting this option to ``true`` can help with subnet selection in
certain scenarios, for example, when your DHCP relays do not allow you to
-specify which suboptions are included in the Relay Agent Information option,
+specify which sub-options are included in the Relay Agent Information option,
and include incorrect Link Selection information.
.. code-block:: json
// Retrieve subnet.
Subnet4Ptr subnet = ex.getContext()->subnet_;
- if (!subnet) {
- // All methods using the CfgOptionList object return soon when
- // there is no subnet so do the same
- return;
- }
-
// Firstly, host specific options.
const ConstHostPtr& host = ex.getContext()->currentHost();
if (host && !host->getCfgOption4()->empty()) {
co_list.push_back(host->getCfgOption4());
}
- // Secondly, pool specific options.
- Pkt4Ptr resp = ex.getResponse();
- IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
- if (resp) {
- addr = resp->getYiaddr();
- }
- if (!addr.isV4Zero()) {
- PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
- if (pool && !pool->getCfgOption()->empty()) {
- co_list.push_back(pool->getCfgOption());
+ // Secondly, pool specific options. Pools are defined within a subnet, so
+ // if there is no subnet, there is nothing to do.
+ if (subnet) {
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool && !pool->getCfgOption()->empty()) {
+ co_list.push_back(pool->getCfgOption());
+ }
}
- }
- // Thirdly, subnet configured options.
- if (!subnet->getCfgOption()->empty()) {
- co_list.push_back(subnet->getCfgOption());
- }
+ // Thirdly, subnet configured options.
+ if (!subnet->getCfgOption()->empty()) {
+ co_list.push_back(subnet->getCfgOption());
+ }
- // Fourthly, shared network specific options.
- SharedNetwork4Ptr network;
- subnet->getSharedNetwork(network);
- if (network && !network->getCfgOption()->empty()) {
- co_list.push_back(network->getCfgOption());
+ // Fourthly, shared network specific options.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network && !network->getCfgOption()->empty()) {
+ co_list.push_back(network->getCfgOption());
+ }
}
// Each class in the incoming packet
void
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
- // Get the subnet relevant for the client. We will need it
- // to get the options associated with it.
- Subnet4Ptr subnet = ex.getContext()->subnet_;
- // If we can't find the subnet for the client there is no way
- // to get the options to be sent to a client. We don't log an
- // error because it will be logged by the assignLease method
- // anyway.
- if (!subnet) {
- return;
- }
-
// Unlikely short cut
const CfgOptionList& co_list = ex.getCfgOptionList();
if (co_list.empty()) {
Pkt4Ptr query = ex.getQuery();
Pkt4Ptr resp = ex.getResponse();
- std::vector<uint8_t> requested_opts;
+ set<uint8_t> requested_opts;
// try to get the 'Parameter Request List' option which holds the
// codes of requested options.
OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
- // Get the codes of requested options.
+
+ // Get the list of options that client requested.
if (option_prl) {
- requested_opts = option_prl->getValues();
+ for (uint16_t code : option_prl->getValues()) {
+ static_cast<void>(requested_opts.insert(code));
+ }
}
+
// Iterate on the configured option list to add persistent options
- for (CfgOptionList::const_iterator copts = co_list.begin();
- copts != co_list.end(); ++copts) {
- const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
+ for (auto const& copts : co_list) {
+ const OptionContainerPtr& opts = copts->getAll(DHCP4_OPTION_SPACE);
if (!opts) {
continue;
}
desc != range.second; ++desc) {
// Add the persistent option code to requested options
if (desc->option_) {
- uint8_t code = static_cast<uint8_t>(desc->option_->getType());
- requested_opts.push_back(code);
+ static_cast<void>(requested_opts.insert(desc->option_->getType()));
}
}
}
// For each requested option code get the instance of the option
// to be returned to the client.
- for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
- opt != requested_opts.end(); ++opt) {
- // Add nothing when it is already there
- if (!resp->getOption(*opt)) {
+ for (auto const& opt : requested_opts) {
+ // Add nothing when it is already there.
+ // Skip special cases: DHO_VIVSO_SUBOPTIONS
+ if (opt == DHO_VIVSO_SUBOPTIONS) {
+ continue;
+ }
+ if (!resp->getOption(opt)) {
// Iterate on the configured option list
- for (CfgOptionList::const_iterator copts = co_list.begin();
- copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
+ for (auto const& copts : co_list) {
+ OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, opt);
// Got it: add it and jump to the outer loop
if (desc.option_) {
- resp->addOption(desc.option_);
+ resp->Pkt::addOption(desc.option_);
break;
}
}
}
}
+
+ if (requested_opts.count(DHO_VIVSO_SUBOPTIONS) > 0) {
+ set<uint32_t> vendor_ids;
+ for (auto opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
+ OptionVendorPtr vendor_opts;
+ vendor_opts = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ if (vendor_opts) {
+ static_cast<void>(vendor_ids.insert(vendor_opts->getVendorId()));
+ }
+ }
+ // Iterate on the configured option list
+ for (auto const& copts : co_list) {
+ for (OptionDescriptor desc : copts->getList(DHCP4_OPTION_SPACE,
+ DHO_VIVSO_SUBOPTIONS)) {
+ if (!desc.option_) {
+ continue;
+ }
+ OptionVendorPtr vendor_opts =
+ boost::dynamic_pointer_cast<OptionVendor>(desc.option_);
+ if (!vendor_opts) {
+ continue;
+ }
+ // Is the vendor id already in the response?
+ uint32_t vendor_id = vendor_opts->getVendorId();
+ if (vendor_ids.count(vendor_id) > 0) {
+ continue;
+ }
+ // Got it: add it.
+ resp->Pkt::addOption(desc.option_);
+ static_cast<void>(vendor_ids.insert(vendor_id));
+ }
+ }
+ }
}
void
Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
// Get the configured subnet suitable for the incoming packet.
Subnet4Ptr subnet = ex.getContext()->subnet_;
+
+ const CfgOptionList& co_list = ex.getCfgOptionList();
// 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;
- }
-
- // Unlikely short cut
- const CfgOptionList& co_list = ex.getCfgOptionList();
- if (co_list.empty()) {
+ //
+ // Also, if there's no options to possibly assign, give up.
+ if (!subnet || co_list.empty()) {
return;
}
- uint32_t vendor_id = 0;
-
- // Try to get the vendor option from the client packet. This is how it's
- // supposed to be done. Client sends vivso, we look at the vendor-id and
- // then send back the vendor options specific to that client.
- boost::shared_ptr<OptionVendor> vendor_req = boost::dynamic_pointer_cast<
- OptionVendor>(ex.getQuery()->getOption(DHO_VIVSO_SUBOPTIONS));
- if (vendor_req) {
- vendor_id = vendor_req->getVendorId();
+ Pkt4Ptr query = ex.getQuery();
+ Pkt4Ptr resp = ex.getResponse();
+ set<uint32_t> vendor_ids;
+
+ // The server could have provided the option using client classification or
+ // hooks. If there're vendor info options in the response already, use them.
+ map<uint32_t, OptionVendorPtr> vendor_rsps;
+ for (auto opt : resp->getOptions(DHO_VIVSO_SUBOPTIONS)) {
+ OptionVendorPtr vendor_rsp;
+ vendor_rsp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ if (vendor_rsp) {
+ uint32_t vendor_id = vendor_rsp->getVendorId();
+ vendor_rsps[vendor_id] = vendor_rsp;
+ static_cast<void>(vendor_ids.insert(vendor_id));
+ }
}
- // Something is fishy. Client was supposed to send vivso, but didn't.
- // Let's try an alternative. It's possible that the server already
- // inserted vivso in the response message, (e.g. by using client
- // classification or perhaps a hook inserted it).
- boost::shared_ptr<OptionVendor> vendor_rsp = boost::dynamic_pointer_cast<
- OptionVendor>(ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS));
- if (vendor_rsp) {
- vendor_id = vendor_rsp->getVendorId();
+ // Next, try to get the vendor-id from the client packet's
+ // vendor-specific information option (17).
+ map<uint32_t, OptionVendorPtr> vendor_reqs;
+ for (auto opt : query->getOptions(DHO_VIVSO_SUBOPTIONS)) {
+ OptionVendorPtr vendor_req;
+ vendor_req = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ if (vendor_req) {
+ uint32_t vendor_id = vendor_req->getVendorId();
+ vendor_reqs[vendor_id] = vendor_req;
+ static_cast<void>(vendor_ids.insert(vendor_id));
+ }
}
- if (!vendor_req && !vendor_rsp) {
- // Ok, we're out of luck today. Neither client nor server packets
- // have vivso. There is no way to figure out vendor-id here.
- // We give up.
+ // If there's no vendor option in either request or response, then there's no way
+ // to figure out what the vendor-id values are and we give up.
+ if (vendor_ids.empty()) {
return;
}
- std::vector<uint8_t> requested_opts;
+ map<uint32_t, set<uint8_t> > requested_opts;
// Let's try to get ORO within that vendor-option.
// This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
// different policies.
OptionUint8ArrayPtr oro;
- if (vendor_id == VENDOR_ID_CABLE_LABS && vendor_req) {
+ if (vendor_reqs.count(VENDOR_ID_CABLE_LABS) > 0) {
+ OptionVendorPtr vendor_req = vendor_reqs[VENDOR_ID_CABLE_LABS];
OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
if (oro_generic) {
// Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
// created as an OptionUint8Array, but might not be for other
// vendor IDs.
oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
- // Get the list of options that client requested.
- if (oro) {
- requested_opts = oro->getValues();
+ }
+ if (oro) {
+ set<uint8_t> oro_req_opts;
+ for (uint8_t code : oro->getValues()) {
+ static_cast<void>(oro_req_opts.insert(code));
}
+ requested_opts[VENDOR_ID_CABLE_LABS] = oro_req_opts;
}
}
// Iterate on the configured option list to add persistent options
- for (CfgOptionList::const_iterator copts = co_list.begin();
- copts != co_list.end(); ++copts) {
- const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
- if (!opts) {
- continue;
+ for (uint32_t vendor_id : vendor_ids) {
+ for (auto const& copts : co_list) {
+ const OptionContainerPtr& opts = copts->getAll(vendor_id);
+ if (!opts) {
+ continue;
+ }
+ // Get persistent options
+ const OptionContainerPersistIndex& idx = opts->get<2>();
+ const OptionContainerPersistRange& range = idx.equal_range(true);
+ for (OptionContainerPersistIndex::const_iterator desc = range.first;
+ desc != range.second; ++desc) {
+ if (!desc->option_) {
+ continue;
+ }
+ // Add the persistent option code to requested options
+ static_cast<void>(requested_opts[vendor_id].insert(desc->option_->getType()));
+ }
}
- // Get persistent options
- const OptionContainerPersistIndex& idx = opts->get<2>();
- const OptionContainerPersistRange& range = idx.equal_range(true);
- for (OptionContainerPersistIndex::const_iterator desc = range.first;
- desc != range.second; ++desc) {
- // Add the persistent option code to requested options
- if (desc->option_) {
- uint8_t code = static_cast<uint8_t>(desc->option_->getType());
- requested_opts.push_back(code);
- }
+ // If there is nothing to add don't do anything with this vendor.
+ // This will explicitly not echo back vendor options from the request
+ // that either correspond to a vendor not known to Kea even if the
+ // option encapsulates data or there are no persistent options
+ // configured for this vendor so Kea does not send any option back.
+ if (requested_opts[vendor_id].empty()) {
+ continue;
}
- }
- // If there is nothing to add don't do anything then.
- if (requested_opts.empty()) {
- return;
- }
+ // It's possible that the vendor opts option was inserted already
+ // by client class or a hook. If that is so, let's use it.
+ OptionVendorPtr vendor_rsp;
+ if (vendor_rsps.count(vendor_id) > 0) {
+ vendor_rsp = vendor_rsps[vendor_id];
+ } else {
+ vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
+ }
- if (!vendor_rsp) {
- // It's possible that vivso was inserted already by client class or
- // a hook. If that is so, let's use it.
- vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
- }
+ // Get the list of options that client requested.
+ bool added = false;
- // Get the list of options that client requested.
- bool added = false;
- for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
- code != requested_opts.end(); ++code) {
- if (!vendor_rsp->getOption(*code)) {
- for (CfgOptionList::const_iterator copts = co_list.begin();
- copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get(vendor_id, *code);
- if (desc.option_) {
- vendor_rsp->addOption(desc.option_);
- added = true;
- break;
+ for (uint8_t opt : requested_opts[vendor_id]) {
+ if (!vendor_rsp->getOption(opt)) {
+ for (auto const& copts : co_list) {
+ OptionDescriptor desc = copts->get(vendor_id, opt);
+ if (desc.option_) {
+ vendor_rsp->addOption(desc.option_);
+ added = true;
+ break;
+ }
}
}
}
- }
- // If we added some sub-options and the vivso option is not in
- // the response already, then add it.
- if (added && !ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS)) {
- ex.getResponse()->addOption(vendor_rsp);
+ // If we added some sub-options and the vendor opts option is not in
+ // the response already, then add it.
+ if (added && (vendor_rsps.count(vendor_id) == 0)) {
+ resp->Pkt::addOption(vendor_rsp);
+ }
}
}
Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
// Identify options that we always want to send to the
// client (if they are configured).
- static const uint16_t required_options[] = {
+ static const std::vector<uint16_t> required_options = {
DHO_ROUTERS,
DHO_DOMAIN_NAME_SERVERS,
DHO_DOMAIN_NAME,
DHO_DHCP_SERVER_IDENTIFIER };
- static size_t required_options_size =
- sizeof(required_options) / sizeof(required_options[0]);
-
// Get the subnet.
Subnet4Ptr subnet = ex.getContext()->subnet_;
if (!subnet) {
// Try to find all 'required' options in the outgoing
// message. Those that are not present will be added.
- for (int i = 0; i < required_options_size; ++i) {
- OptionPtr opt = resp->getOption(required_options[i]);
+ for (auto const& required : required_options) {
+ OptionPtr opt = resp->getOption(required);
if (!opt) {
// Check whether option has been configured.
- for (CfgOptionList::const_iterator copts = co_list.begin();
- copts != co_list.end(); ++copts) {
- OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
- required_options[i]);
+ for (auto const& copts : co_list) {
+ OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
if (desc.option_) {
resp->addOption(desc.option_);
break;
// vendor options in DHCPv4:
//
// vivso (125) - vendor independent vendor specific option. This is by far the
-// most popular
+// most popular.
// vendor specific (43) - this is probably the second most popular.
-// Unfortunately, its definition is blurry, so there are many
-// similar, but not exact implementations that do things in
-// different ways.
+// Unfortunately, its definition is blurry, so there are
+// many similar, but not exact implementations that do
+// things in different ways.
// vivco (124) - vendor independent vendor class option.
// class identifier (60) - not exactly vendor specific. It's a string, but the
-// content of that string identifies what kind of vendor device
-// this is.
+// content of that string identifies what kind of vendor
+// device this is.
+// client-class (77) - this specifies (as a plain string) what kind of device
+// this is.
#include <config.h>
#include <asiolink/io_address.h>
/// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
/// vendor options is parsed correctly and the requested options are
- /// actually assigned. Also covers negative tests - that options are not
+ /// actually assigned. Also covers negative tests that options are not
/// provided when a different vendor ID is given.
- void testVendorOptionsORO(int vendor_id) {
- // Create a config with a custom option for Cable Labs.
+ ///
+ /// @note Kea only knows how to process VENDOR_ID_CABLE_LABS DOCSIS3_V4_ORO
+ /// (suboption 1).
+ ///
+ /// @param configured_vendor_ids The vendor IDs that are configured in the
+ /// server: 4491 or both 4491 and 3561.
+ /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+ /// @param requested_options The requested options in ORO.
+ void testVendorOptionsORO(std::vector<uint32_t> configured_vendor_ids,
+ std::vector<uint32_t> requested_vendor_ids,
+ std::vector<uint32_t> requested_options) {
+ std::vector<uint32_t> result_vendor_ids;
+ ASSERT_TRUE(configured_vendor_ids.size());
+ ASSERT_EQ(configured_vendor_ids[0], VENDOR_ID_CABLE_LABS);
+ for (const auto& req : requested_vendor_ids) {
+ if (req == VENDOR_ID_CABLE_LABS) {
+ result_vendor_ids.push_back(req);
+ }
+ }
+ // Create a config with custom options.
string config = R"(
{
"interfaces-config": {
"data": "192.0.2.1, 192.0.2.2",
"name": "tftp-servers",
"space": "vendor-4491"
+ },
+ {
+ "code": 22,
+ "csv-format": true,
+ "data": "first",
+ "name": "tag",
+ "space": "vendor-4491"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 2,
+ "csv-format": true,
+ "data": "10.0.2.1, 10.0.2.2",
+ "name": "custom",
+ "space": "vendor-3561",
+ },
+ {
+ "code": 22,
+ "csv-format": true,
+ "data": "last",
+ "name": "special",
+ "space": "vendor-3561"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "option-def": [
+ {
+ "code": 22,
+ "name": "tag",
+ "space": "vendor-4491",
+ "type": "string"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 2,
+ "name": "custom",
+ "space": "vendor-3561",
+ "type": "ipv4-address",
+ "array": true
+ },
+ {
+ "code": 22,
+ "name": "special",
+ "space": "vendor-3561",
+ "type": "string"
+ )";
+ }
+ config += R"(
}
],
"subnet4": [
// Set interface. It is required by the server to generate server id.
dis->setIface("eth0");
dis->setIndex(ETH0_INDEX);
+
OptionPtr clientid = generateClientId();
dis->addOption(clientid);
- // Pass it to the server and get an advertise
+ // Pass it to the server and get an offer
Pkt4Ptr offer = srv.processDiscover(dis);
// Check if we get a response at all.
// 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 2.
- OptionPtr vendor(new OptionVendor(Option::V4, vendor_id));
- vendor->addOption(vendor_oro);
- dis->addOption(vendor);
+ for (auto const& option : requested_options) {
+ vendor_oro->addValue(option);
+ }
+
+ for (auto const& vendor_id : requested_vendor_ids) {
+ OptionVendorPtr vendor(new OptionVendor(Option::V4, vendor_id));
+ vendor->addOption(vendor_oro);
+ dis->Pkt::addOption(vendor);
+ }
// Need to process DHCPDISCOVER again after requesting new option.
offer = srv.processDiscover(dis);
// vendor ID was provided in the request. Otherwise, check that there is
// no vendor and stop processing since the following checks are built on
// top of the now-absent options.
- OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
- if (vendor_id != VENDOR_ID_CABLE_LABS) {
- EXPECT_FALSE(tmp);
+ OptionCollection tmp = offer->getOptions(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+ if (!result_vendor_ids.size()) {
return;
}
- ASSERT_TRUE(tmp);
-
- // The response should be an OptionVendor.
- boost::shared_ptr<OptionVendor> vendor_resp =
- boost::dynamic_pointer_cast<OptionVendor>(tmp);
- ASSERT_TRUE(vendor_resp);
-
- // Option 2 should be present.
- OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
- ASSERT_TRUE(docsis2);
-
- // It should be an Option4AddrLst.
- Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
- ASSERT_TRUE(tftp_srvs);
-
- // Check that the provided addresses match the ones in configuration.
- 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());
+
+ for (const auto& opt : tmp) {
+ // The response should be an OptionVendor.
+ OptionVendorPtr vendor_resp;
+
+ for (auto const& vendor_id : result_vendor_ids) {
+ vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == vendor_id) {
+ break;
+ }
+ vendor_resp.reset();
+ }
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ for (auto const& option : requested_options) {
+ if (option == DOCSIS3_V4_TFTP_SERVERS) {
+ // Option 2 should be present.
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(docsis2);
+
+ // It should be an Option4AddrLst.
+ Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+ ASSERT_TRUE(tftp_srvs);
+
+ // Check that the provided addresses match the ones in configuration.
+ 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());
+ }
+
+ if (option == 22) {
+ // Option 22 should be present.
+ OptionPtr custom = vendor_resp->getOption(22);
+ ASSERT_TRUE(custom);
+
+ // It should be an OptionString.
+ OptionStringPtr tag = boost::dynamic_pointer_cast<OptionString>(custom);
+ ASSERT_TRUE(tag);
+
+ // Check that the provided value match the ones in configuration.
+ EXPECT_EQ(tag->getValue(), "first");
+ }
+ }
+ } else {
+ // If explicitly sending OptionVendor and the vendor is not
+ // requested, options should not be present. Kea only knows how
+ // to process VENDOR_ID_CABLE_LABS DOCSIS3_V4_ORO (suboption 1).
+ // Option 2 should not be present.
+ OptionPtr docsis2 = vendor_resp->getOption(2);
+ ASSERT_FALSE(docsis2);
+
+ // Option 22 should not be present.
+ OptionPtr custom = vendor_resp->getOption(22);
+ ASSERT_FALSE(custom);
+ }
+ }
+ }
+
+ /// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+ /// vendor options is parsed correctly and the configured options are
+ /// actually assigned.
+ ///
+ /// @param configured_vendor_ids The vendor IDs that are configured in the
+ /// server: 4491 or both 4491 and 3561.
+ /// @param requested_vendor_ids Then vendor IDs that are present in ORO.
+ /// @param requested_options The requested options in ORO.
+ /// @param add_vendor_option The flag which indicates if the request should
+ /// contain a OptionVendor option or should the server always send all the
+ /// OptionVendor options and suboptions.
+ void testVendorOptionsPersistent(std::vector<uint32_t> configured_vendor_ids,
+ std::vector<uint32_t> requested_vendor_ids,
+ std::vector<uint32_t> configured_options,
+ bool add_vendor_option) {
+ std::vector<uint32_t> result_vendor_ids;
+ ASSERT_TRUE(configured_vendor_ids.size());
+ ASSERT_EQ(configured_vendor_ids[0], VENDOR_ID_CABLE_LABS);
+ if (add_vendor_option) {
+ for (const auto& req : requested_vendor_ids) {
+ if (std::find(configured_vendor_ids.begin(), configured_vendor_ids.end(), req) != configured_vendor_ids.end()) {
+ result_vendor_ids.push_back(req);
+ }
+ }
+ } else {
+ result_vendor_ids = configured_vendor_ids;
+ }
+ ASSERT_TRUE(configured_options.size());
+ ASSERT_EQ(configured_options[0], DOCSIS3_V4_TFTP_SERVERS);
+ // Create a config with a custom options.
+ string config = R"(
+ {
+ "interfaces-config": {
+ "interfaces": [ "*" ]
+ },
+ "option-data": [
+ {
+ "always-send": true,
+ "code": 2,
+ "csv-format": true,
+ "data": "192.0.2.1, 192.0.2.2",
+ "name": "tftp-servers",
+ "space": "vendor-4491"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 22,
+ "csv-format": true,
+ "data": "first",
+ "name": "tag",
+ "space": "vendor-4491"
+ )";
+ }
+ if (!add_vendor_option) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vivso-suboptions",
+ "data": "4491",
+ "space": "dhcp4"
+ )";
+ }
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 2,
+ "csv-format": true,
+ "data": "10.0.2.1, 10.0.2.2",
+ "name": "custom",
+ "space": "vendor-3561"
+ )";
+ if (configured_options.size() > 1) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "code": 22,
+ "csv-format": true,
+ "data": "last",
+ "name": "special",
+ "space": "vendor-3561"
+ )";
+ }
+ if (!add_vendor_option) {
+ config += R"(
+ },
+ {
+ "always-send": true,
+ "name": "vivso-suboptions",
+ "data": "3561",
+ "space": "dhcp4"
+ )";
+ }
+ }
+ config += R"(
+ }
+ ],
+ "option-def": [
+ {
+ "code": 22,
+ "name": "tag",
+ "space": "vendor-4491",
+ "type": "string"
+ )";
+ if (configured_vendor_ids.size() > 1) {
+ config += R"(
+ },
+ {
+ "code": 2,
+ "name": "custom",
+ "space": "vendor-3561",
+ "type": "ipv4-address",
+ "array": true
+ },
+ {
+ "code": 22,
+ "name": "special",
+ "space": "vendor-3561",
+ "type": "string"
+ )";
+ }
+ config += R"(
+ }
+ ],
+ "subnet4": [
+ {
+ "interface": "eth0",
+ "pools": [
+ {
+ "pool": "192.0.2.0/25"
+ }
+ ],
+ "subnet": "192.0.2.0/24"
+ }
+ ]
+ }
+ )";
+
+ // Parse the configuration.
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config));
+
+ // Configure a mocked server.
+ NakedDhcpv4Srv srv(0);
+ ConstElementPtr x;
+ EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ CfgMgr::instance().commit();
+
+ // Set the giaddr and hops to non-zero address as if it was relayed.
+ boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setGiaddr(IOAddress("192.0.2.1"));
+ dis->setHops(1);
+
+ // Set interface. It is required by the server to generate server id.
+ dis->setIface("eth0");
+ dis->setIndex(ETH0_INDEX);
+
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ if (add_vendor_option) {
+ for (auto const& vendor_id : requested_vendor_ids) {
+ // Let's add a vendor-option (vendor-id=4491).
+ OptionVendorPtr vendor(new OptionVendor(Option::V4, vendor_id));
+
+ dis->Pkt::addOption(vendor);
+ }
+ }
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv.processDiscover(dis);
+
+ // check if we get response at all
+ ASSERT_TRUE(offer);
+
+ // Check if there is a vendor option response
+ OptionCollection tmp = offer->getOptions(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_EQ(tmp.size(), result_vendor_ids.size());
+
+ for (const auto& opt : tmp) {
+ // The response should be an OptionVendor.
+ OptionVendorPtr vendor_resp;
+
+ for (auto const& vendor_id : result_vendor_ids) {
+ vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
+ ASSERT_TRUE(vendor_resp);
+ if (vendor_resp->getVendorId() == vendor_id) {
+ break;
+ }
+ }
+ ASSERT_TRUE(vendor_resp);
+
+ for (auto const& option : configured_options) {
+ if (add_vendor_option &&
+ std::find(requested_vendor_ids.begin(), requested_vendor_ids.end(),
+ vendor_resp->getVendorId()) == requested_vendor_ids.end()) {
+ // If explicitly sending OptionVendor and the vendor is not
+ // requested, options should not be present.
+ if (option == DOCSIS3_V4_TFTP_SERVERS) {
+ // Option 2 should not be present.
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_FALSE(docsis2);
+ }
+ if (option == 22) {
+ // Option 22 should not be present.
+ OptionPtr custom = vendor_resp->getOption(22);
+ ASSERT_FALSE(custom);
+ }
+ } else {
+ if (option == DOCSIS3_V4_TFTP_SERVERS) {
+ // Option 2 should be present.
+ OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(docsis2);
+
+ // It should be an Option4AddrLst.
+ Option4AddrLstPtr tftp_srvs;
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+ } else {
+ // The option is serialized as Option so it needs to be converted to
+ // Option4AddrLst.
+ auto const& buffer = docsis2->toBinary();
+ tftp_srvs.reset(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS, buffer.begin(), buffer.end()));
+ }
+ ASSERT_TRUE(tftp_srvs);
+
+ // Check that the provided addresses match the ones in configuration.
+ Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ("192.0.2.1", addrs[0].toText());
+ EXPECT_EQ("192.0.2.2", addrs[1].toText());
+ } else {
+ EXPECT_EQ("10.0.2.1", addrs[0].toText());
+ EXPECT_EQ("10.0.2.2", addrs[1].toText());
+ }
+ }
+
+ if (option == 22) {
+ // Option 22 should be present.
+ OptionPtr custom = vendor_resp->getOption(22);
+ ASSERT_TRUE(custom);
+
+ // It should be an OptionString.
+ // The option is serialized as Option so it needs to be converted to
+ // OptionString.
+ auto const& buffer = custom->toBinary();
+ OptionStringPtr tag(new OptionString(Option::V4, 22, buffer.begin(), buffer.end()));
+ ASSERT_TRUE(tag);
+
+ // Check that the provided value match the ones in configuration.
+ if (vendor_resp->getVendorId() == VENDOR_ID_CABLE_LABS) {
+ EXPECT_EQ(tag->getValue(), "first");
+ } else {
+ EXPECT_EQ(tag->getValue(), "last");
+ }
+ }
+ }
+ }
+ }
}
std::unique_ptr<IfaceMgrTestConfig> iface_mgr_test_config_;
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);
+ OptionVendorPtr vendor_opt = boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
ASSERT_TRUE(vendor_opt);
+ ASSERT_EQ(vendor_opt->getVendorId(), VENDOR_ID_CABLE_LABS);
// Get Relay Agent Info from response...
OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS);
OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_TRUE(opt);
- boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
// This particular capture that we have included options 1 and 5
EXPECT_TRUE(vendor->getOption(1));
// Let's get a traffic capture from DOCSIS3.0 modem
Pkt4Ptr dis = PktCaptures::captureRelayedDiscover();
- EXPECT_NO_THROW(dis->unpack());
+ ASSERT_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);
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
opt = vendor->getOption(DOCSIS3_V4_ORO);
ASSERT_TRUE(opt);
// 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(VendorOptsTest, vendorOptionsORO) {
- testVendorOptionsORO(VENDOR_ID_CABLE_LABS);
+TEST_F(VendorOptsTest, vendorOptionsOROOneOption) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS});
+}
+
+// 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(VendorOptsTest, vendorOptionsOROMultipleOptions) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22});
+}
+
+// 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(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchOne) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS});
+}
+
+// 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(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchOne) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22});
+}
+
+// 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(VendorOptsTest, vendorOptionsOROOneOptionMultipleVendorsMatchAll) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS});
+}
+
+// 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(VendorOptsTest, vendorOptionsOROMultipleOptionsMultipleVendorsMatchAll) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS, 22});
}
// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
// provided and vendor options are expected to not be present in the response.
-TEST_F(VendorOptsTest, vendorOptionsORODifferentVendorID) {
- testVendorOptionsORO(32768);
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorID) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {32768}, {DOCSIS3_V4_TFTP_SERVERS});
}
-// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
-// vendor options is parsed correctly and persistent options are actually assigned.
-TEST_F(VendorOptsTest, vendorPersistentOptions) {
- NakedDhcpv4Srv srv(0);
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionsDifferentVendorID) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {32768}, {DOCSIS3_V4_TFTP_SERVERS, 22});
+}
- ConstElementPtr x;
- string config = "{ \"interfaces-config\": {"
- " \"interfaces\": [ \"*\" ]"
- "},"
- " \"option-data\": [ {"
- " \"name\": \"tftp-servers\","
- " \"space\": \"vendor-4491\","
- " \"code\": 2,"
- " \"data\": \"192.0.2.1, 192.0.2.2\","
- " \"csv-format\": true,"
- " \"always-send\": true"
- " }],"
- "\"subnet4\": [ { "
- " \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
- " \"subnet\": \"192.0.2.0/24\", "
- " \"interface\": \"eth0\" "
- " } ]"
- "}";
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {32768, 16384}, {DOCSIS3_V4_TFTP_SERVERS});
+}
- ConstElementPtr json;
- ASSERT_NO_THROW(json = parseDHCP4(config));
+// Same as vendorOptionsORO except a different vendor ID than Cable Labs is
+// provided and vendor options are expected to not be present in the response.
+TEST_F(VendorOptsTest, vendorOptionsOROMultipleOptionDifferentVendorIDMultipleVendorsMatchNone) {
+ testVendorOptionsORO({VENDOR_ID_CABLE_LABS, 3561}, {32768, 16384}, {DOCSIS3_V4_TFTP_SERVERS, 22});
+}
- EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
- ASSERT_TRUE(x);
- comment_ = parseAnswer(rcode_, x);
- ASSERT_EQ(0, rcode_);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, false);
+}
- CfgMgr::instance().commit();
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, false);
+}
- boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
- // Set the giaddr and hops to non-zero address as if it was relayed.
- dis->setGiaddr(IOAddress("192.0.2.1"));
- dis->setHops(1);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOne) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, false);
+}
- OptionPtr clientid = generateClientId();
- dis->addOption(clientid);
- // Set interface. It is required by the server to generate server id.
- dis->setIface("eth0");
- dis->setIndex(ETH0_INDEX);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOne) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, false);
+}
- // Let's add a vendor-option (vendor-id=4491).
- OptionPtr vendor(new OptionVendor(Option::V4, 4491));
- dis->addOption(vendor);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAll) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS}, false);
+}
- // Pass it to the server and get an advertise
- Pkt4Ptr offer = srv.processDiscover(dis);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAll) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS, 22}, false);
+}
- // check if we get response at all
- ASSERT_TRUE(offer);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, true);
+}
- // Check if there is a vendor option response
- OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
- ASSERT_TRUE(tmp);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, true);
+}
- // The response should be OptionVendor object
- boost::shared_ptr<OptionVendor> vendor_resp =
- boost::dynamic_pointer_cast<OptionVendor>(tmp);
- ASSERT_TRUE(vendor_resp);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchOneAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, true);
+}
- OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
- ASSERT_TRUE(docsis2);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOneAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, true);
+}
- Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
- ASSERT_TRUE(tftp_srvs);
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAllAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS}, true);
+}
- 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());
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAllAddVendorOption) {
+ testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS, 22}, true);
}
// Test checks whether it is possible to use option definitions defined in
// Check that it includes vivso with vendor-id = 25167
OptionVendorPtr rsp_vivso = boost::dynamic_pointer_cast<OptionVendor>(rsp);
ASSERT_TRUE(rsp_vivso);
- EXPECT_EQ(25167, rsp_vivso->getVendorId());
+ EXPECT_EQ(rsp_vivso->getVendorId(), 25167);
// Now check that it contains suboption 2 with appropriate content.
OptionPtr subopt2 = rsp_vivso->getOption(2);
};
// Provide DHCP message type option, truncated vendor option and domain name.
- // Parsing this message should be successful but domain name following the
- // truncated vendor option should be skipped.
+ // Parsing this message should be successful but vendor option should be
+ // skipped.
std::vector<uint8_t> options = {
53, 1, 1, // Message type = DHCPDISCOVER
125, 6, // vendor options
// Check that the message has been parsed. The DHCP message type should
// be set in this case.
EXPECT_EQ(DHCPDISCOVER, static_cast<int>(query4->getType()));
- // Domain name should be skipped because the vendor option was truncated.
- EXPECT_FALSE(query4->getOption(DHO_DOMAIN_NAME));
+ // Vendor option should be skipped because it was truncated.
+ EXPECT_FALSE(query4->getOption(DHO_VIVSO_SUBOPTIONS));
+ // Domain name should not be skipped because the vendor option was truncated.
+ EXPECT_TRUE(query4->getOption(DHO_DOMAIN_NAME));
}
// Tests for buffer6_receive callout implementation.
return (it->second(u, type, buf));
}
-
size_t
LibDHCP::unpackOptions6(const OptionBuffer& buf,
const std::string& option_space,
}
}
+ if (space_is_dhcp4 && opt_type == DHO_VIVSO_SUBOPTIONS) {
+ num_defs = 0;
+ }
+
OptionPtr opt;
if (num_defs > 1) {
// Multiple options of the same code are not supported right now!
return (result);
}
+void
+LibDHCP::extendVendorOptions4(isc::dhcp::OptionCollection& options) {
+ map<uint32_t, OptionCollection> vendors_data;
+ for (auto const& option : options) {
+ if (option.second->getType() == DHO_VIVSO_SUBOPTIONS) {
+ uint32_t offset = 0;
+ auto const& data = option.second->getData();
+ auto const& begin = data.begin();
+ auto const& end = data.end();
+ size_t size;
+ while ((size = distance(begin + offset, end)) != 0) {
+ if (size < sizeof(uint32_t)) {
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ isc_throw(SkipRemainingOptionsError,
+ "Truncated vendor-specific information option"
+ << ", length=" << size);
+ }
+ uint32_t vendor_id = isc::util::readUint32(&(*begin) + offset, distance(begin, end));
+ offset += 4;
+ OptionBuffer vendor_buffer(begin + offset, end);
+ try {
+ offset += LibDHCP::unpackVendorOptions4(vendor_id, vendor_buffer, vendors_data[vendor_id]);
+ } catch (const SkipThisOptionError&) {
+ } catch (const Exception&) {
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ throw;
+ }
+ }
+ }
+ }
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ for (auto const& vendor : vendors_data) {
+ OptionVendorPtr vendor_opt(new OptionVendor(Option::V4, vendor.first));
+ for (auto const& option : vendor.second) {
+ vendor_opt->addOption(option.second);
+ }
+ options.insert(std::make_pair(DHO_VIVSO_SUBOPTIONS, vendor_opt));
+ }
+}
+
size_t
LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
const OptionBuffer& buf,
/// @return True if any option has been fused, false otherwise.
static bool fuseOptions4(isc::dhcp::OptionCollection& options);
+ /// @brief Extend vendor options from fused options in multiple OptionVendor
+ /// options and add respective suboptions.
+ ///
+ /// @param options The option container which needs to be updated with
+ /// extended vendor options.
+ static void extendVendorOptions4(isc::dhcp::OptionCollection& options);
+
/// @brief Parses provided buffer as DHCPv4 options and creates
/// Option objects.
///
OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id)
: Option(u, u == Option::V4 ?
static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
- static_cast<uint16_t>(D6O_VENDOR_OPTS)),
- vendor_id_(vendor_id) {
+ static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(vendor_id) {
}
OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
OptionBufferConstIter end)
: Option(u, u == Option::V4?
static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
- static_cast<uint16_t>(D6O_VENDOR_OPTS)),
- vendor_id_(0) {
+ static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(0) {
unpack(begin, end);
}
+OptionVendor::OptionVendor(const OptionVendor& option) : Option(option),
+ vendor_id_(option.vendor_id_) {
+ Option::operator=(option);
+}
+
OptionPtr
OptionVendor::clone() const {
return (cloneInternal<OptionVendor>());
// Calculate and store data-len as follows:
// data-len = total option length - header length
// - enterprise id field length - data-len field size
- buf.writeUint8(dataLen());
+ // length of all suboptions
+ uint8_t length = 0;
+ for (auto const& opt : options_) {
+ length += opt.second->len();
+ }
+ buf.writeUint8(length);
}
packOptions(buf, check);
void OptionVendor::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
-
// We throw SkipRemainingOptionsError so callers can
// abandon further unpacking, if desired.
if (distance(begin, end) < sizeof(uint32_t)) {
<< ", length=" << distance(begin, end));
}
+ options_.clear();
+
vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
OptionBuffer vendor_buffer(begin + 4, end);
}
// length of all suboptions
- for (OptionCollection::const_iterator it = options_.begin();
- it != options_.end();
- ++it) {
- length += (*it).second->len();
+ for (auto const& opt : options_) {
+ length += opt.second->len();
}
return (length);
}
-uint8_t
-OptionVendor::dataLen() const {
- // Calculate and store data-len as follows:
- // data-len = total option length - header length
- // - enterprise id field length - data-len field size
- return (len() - getHeaderLen() - sizeof(uint32_t) - sizeof(uint8_t));
-}
-
std::string
OptionVendor::toText(int indent) const {
std::stringstream output;
- output << headerToText(indent) << ": "
- << getVendorId() << " (uint32)";
+ output << headerToText(indent) << ": ";
+
+ output << vendor_id_ << " (uint32)";
// For the DHCPv4 there is one more field.
if (getUniverse() == Option::V4) {
- output << " " << static_cast<int>(dataLen()) << " (uint8)";
+ uint32_t length = 0;
+ for (auto const& opt : options_) {
+ length += opt.second->len();
+ }
+ output << " " << length << " (uint8)";
}
// Append suboptions.
return (output.str());
}
+
+OptionVendor& OptionVendor::operator=(const OptionVendor& rhs) {
+ if (&rhs != this) {
+ Option::operator=(rhs);
+ vendor_id_ = rhs.vendor_id_;
+ }
+ return (*this);
+}
OptionVendor(Option::Universe u, OptionBufferConstIter begin,
OptionBufferConstIter end);
+ /// @brief Copy constructor.
+ ///
+ /// This constructor makes a deep copy of the option and all of the
+ /// suboptions. It calls @ref getOptionsCopy to deep copy suboptions.
+ ///
+ /// @param source Option to be copied.
+ OptionVendor(const OptionVendor& option);
+
/// @brief Copies this option and returns a pointer to the copy.
OptionPtr clone() const;
/// @brief Sets enterprise identifier
///
/// @param vendor_id vendor identifier
- void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
+ 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_); }
+ uint32_t getVendorId() const {
+ return (vendor_id_);
+ }
/// @brief returns complete length of option
///
/// @return Vendor option in the textual format.
virtual std::string toText(int indent = 0) const;
-private:
-
- /// @brief Calculates the data-len value for DHCPv4.
+ /// @brief Assignment operator.
///
- /// The data-len field is only present in DHCPv4 space. It follows
- /// the vendor-id field. This method is called from the
- /// @c OptionVendor::pack and @c OptionVendor::toText to calculate
- /// this value.
+ /// The assignment operator performs a deep copy of the option and
+ /// its suboptions. It calls @ref getOptionsCopy to deep copy
+ /// suboptions.
///
- /// @return Returns calculated data-len value.
- uint8_t dataLen() const;
+ /// @param rhs Option to be assigned.
+ OptionVendor& operator=(const OptionVendor& rhs);
+
+private:
- uint32_t vendor_id_; ///< Enterprise-id
+ /// @brief Enterprise-id
+ uint32_t vendor_id_;
};
/// Pointer to a vendor option
// access the entire data.
LibDHCP::fuseOptions4(options_);
+ // Kea supports multiple vendor options so it needs to split received and
+ // fused options in multiple OptionVendor instances.
+ LibDHCP::extendVendorOptions4(options_);
+
// No need to call check() here. There are thorough tests for this
// later (see Dhcp4Srv::accept()). We want to drop the packet later,
// so we'll be able to log more detailed drop reason.
OptionBuffer(v6packed + 46, v6packed + 50)));
boost::shared_ptr<OptionInt<uint32_t> >
- vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS, 4491));
+ vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS, VENDOR_ID_CABLE_LABS));
vsi->addOption(cm_mac);
vsi->addOption(cmts_caps);
ASSERT_EQ(5, x->second->getData().size());
EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5
- x = options.find(2);
+ x = options.find(2);
ASSERT_FALSE(x == options.end()); // option 2 should exist
EXPECT_EQ(2, x->second->getType()); // this should be option 2
ASSERT_EQ(7, x->second->len()); // it should be of length 7
EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType());
EXPECT_EQ(26, x->second->len());
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(x->second);
+ ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
+
// CM MAC Address Option
- OptionPtr cm_mac = x->second->getOption(OPTION_CM_MAC);
+ OptionPtr cm_mac = vendor->getOption(OPTION_CM_MAC);
ASSERT_TRUE(cm_mac);
EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType());
ASSERT_EQ(10, cm_mac->len());
EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6));
// CMTS Capabilities
- OptionPtr cmts_caps = x->second->getOption(OPTION_CMTS_CAPS);
+ OptionPtr cmts_caps = vendor->getOption(OPTION_CMTS_CAPS);
ASSERT_TRUE(cmts_caps);
EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType());
ASSERT_EQ(8, cmts_caps->len());
}
}
+TEST_F(LibDhcpTest, extentVendorOptions4) {
+ OptionPtr suboption;
+ OptionVendorPtr opt1(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 16, "first"));
+ opt1->addOption(suboption);
+ OptionVendorPtr opt2(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 32, "second"));
+ opt2->addOption(suboption);
+ OptionVendorPtr opt3(new OptionVendor(Option::V4, 2));
+ suboption.reset(new OptionString(Option::V4, 128, "extra"));
+ opt3->addOption(suboption);
+ OptionVendorPtr opt4(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 64, "third"));
+ opt4->addOption(suboption);
+ OptionCollection container;
+ container.insert(make_pair(1, opt1));
+ container.insert(make_pair(1, opt2));
+ OptionCollection options;
+ for (auto const& option : container) {
+ const OptionBuffer& buffer = option.second->toBinary();
+ options.insert(make_pair(option.second->getType(),
+ OptionPtr(new Option(Option::V4,
+ option.second->getType(),
+ buffer))));
+ }
+ ASSERT_NO_THROW(LibDHCP::fuseOptions4(options));
+ ASSERT_EQ(options.size(), 1);
+ container.clear();
+ container.insert(make_pair(1, options.begin()->second));
+ container.insert(make_pair(2, opt3));
+ container.insert(make_pair(1, opt4));
+ ASSERT_EQ(container.size(), 3);
+ options.clear();
+ for (auto const& option : container) {
+ const OptionBuffer& buffer = option.second->toBinary();
+ options.insert(make_pair(option.second->getType(),
+ OptionPtr(new Option(Option::V4,
+ option.second->getType(),
+ buffer))));
+ }
+ ASSERT_EQ(options.size(), 3);
+ LibDHCP::extendVendorOptions4(options);
+ ASSERT_EQ(options.size(), 2);
+ for (auto const& option : options) {
+ OptionCollection suboptions = option.second->getOptions();
+ OptionPtr suboption;
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(option.second);
+ ASSERT_TRUE(vendor);
+ if (vendor->getVendorId() == 1) {
+ ASSERT_EQ(suboptions.size(), 3);
+ suboption = option.second->getOption(16);
+ ASSERT_TRUE(suboption);
+ suboption = option.second->getOption(32);
+ ASSERT_TRUE(suboption);
+ suboption = option.second->getOption(64);
+ ASSERT_TRUE(suboption);
+ } else if (vendor->getVendorId() == 2) {
+ ASSERT_EQ(suboptions.size(), 1);
+ suboption = option.second->getOption(128);
+ ASSERT_TRUE(suboption);
+ } else {
+ FAIL() << "unexpected vendor type: " << vendor->getVendorId();
+ }
+ }
+}
+
// This test verifies that pack options for v4 is working correctly.
TEST_F(LibDhcpTest, packOptions4) {
vector<uint8_t> payload[5];
for (unsigned i = 0; i < 5; i++) {
payload[i].resize(3);
- payload[i][0] = i*10;
- payload[i][1] = i*10+1;
- payload[i][2] = i*10+2;
+ payload[i][0] = i * 10;
+ payload[i][1] = i * 10 + 1;
+ payload[i][2] = i * 10 + 2;
}
OptionPtr opt1(new Option(Option::V4, 12, payload[0]));
OptionPtr opt2(new Option(Option::V4, 60, payload[1]));
OptionPtr opt3(new Option(Option::V4, 14, payload[2]));
- OptionPtr opt4(new Option(Option::V4,254, payload[3]));
- OptionPtr opt5(new Option(Option::V4,128, payload[4]));
+ OptionPtr opt4(new Option(Option::V4, 254, payload[3]));
+ OptionPtr opt5(new Option(Option::V4, 128, payload[4]));
// Create vendor option instance with DOCSIS3.0 enterprise id.
- OptionVendorPtr vivsi(new OptionVendor(Option::V4, 4491));
+ OptionVendorPtr vivsi(new OptionVendor(Option::V4, VENDOR_ID_CABLE_LABS));
vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS,
IOAddress("10.0.0.10"))));
OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS,
- OptionBuffer()));
+ OptionBuffer()));
vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer())));
// Add RAI option, which comprises 3 sub-options.
deferred, false);
);
+ ASSERT_NO_THROW(LibDHCP::extendVendorOptions4(options));
+
isc::dhcp::OptionCollection::const_iterator x = options.find(12);
ASSERT_FALSE(x == options.end()); // option 1 should exist
// Option 12 holds a string so let's cast it to an appropriate type.
OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second);
ASSERT_TRUE(vivsi);
EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
- EXPECT_EQ(4491, vivsi->getVendorId());
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vivsi->getVendorId());
OptionCollection suboptions = vivsi->getOptions();
// There should be one suboption of V-I VSI.
LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
vivco_buf.end(), typeid(OptionVendorClass));
-
LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(),
vivsio_buf.end(), typeid(OptionVendor));
// Let's investigate if the option content is correct
// 3 fields expected: vendor-id, data-len and data
- EXPECT_EQ(4491, vclass->getVendorId());
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vclass->getVendorId());
EXPECT_EQ(20, vclass->len());
ASSERT_EQ(1, vclass->getTuplesNum());
EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText());