2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-02 06:55:16 +00:00

[#1518] implemented support for multiple vendor options in v4

This commit is contained in:
Razvan Becheriu
2023-01-13 15:52:56 +02:00
parent 63b57123ac
commit 216aadb45a
10 changed files with 972 additions and 347 deletions

View File

@@ -2479,16 +2479,15 @@ The standard spaces defined in Kea and their options are:
| 2 | tftp-servers | a list of IPv4 addresses of TFTP servers to be used by the cable modem | | 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 In Kea each vendor is represented by its own vendor space. Since there are
are hundreds of vendors and sometimes they use different option hundreds of vendors and sometimes they use different option definitions for
definitions for different hardware, it is impossible for Kea to support different hardware, it is impossible for Kea to support them all natively.
them all natively. Fortunately, it's easy to define support for Fortunately, it's easy to define support for new vendor options. Let's take an
new vendor options. Let's take an example of the Genexis home gateway. This example of the Genexis home gateway. This device requires sending the vivso 125
device requires sending the vivso 125 option with a sub-option 2 that option with a sub-option 2 that contains a string with the TFTP server URL. To
contains a string with the TFTP server URL. To support such a device, three support such a device, three steps are needed: first, we need to define option
steps are needed: first, we need to define option definitions that will definitions that will explain how the option is supposed to be formed. Second,
explain how the option is supposed to be formed. Second, we need to we need to define option values. Third, we need to tell Kea when to send those
define option values. Third, we need to tell Kea when to send those
specific options, which we can do via client classification. specific options, which we can do via client classification.
An example snippet of a configuration could look similar to the An example snippet of a configuration could look similar to the
@@ -2497,7 +2496,7 @@ following:
:: ::
{ {
// 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). // 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. // After this definition, we can specify values for option tftp.
"option-def": [ "option-def": [
@@ -2524,15 +2523,15 @@ following:
"test": "substring(option[60].hex,0,7) == 'HMC1000'", "test": "substring(option[60].hex,0,7) == 'HMC1000'",
// Once the device is recognized, we want to send two options: // 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": [ "option-data": [
{ {
"name": "vivso-suboptions", "name": "vivso-suboptions",
"data": "25167" "data": "25167"
}, },
// The suboption 2 value is defined as any other option. However, // The sub-option 2 value is defined as any other option. However,
// we want to send this suboption 2, even when the client didn't // 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 // explicitly request it (often there is no way to do that for
// vendor options). Therefore we use always-send to force Kea // vendor options). Therefore we use always-send to force Kea
// to always send this option when 25167 vendor space is involved. // to always send this option when 25167 vendor space is involved.
@@ -2546,27 +2545,26 @@ following:
} ] } ]
} }
By default, Kea sends back By default, Kea sends back only those options that are requested by a client,
only those options that are requested by a client, unless there are unless there are protocol rules that tell the DHCP server to always send an
protocol rules that tell the DHCP server to always send an option. This option. This approach works nicely in most cases and avoids problems with
approach works nicely in most cases and avoids problems with clients clients refusing responses with options they do not understand. However, the
refusing responses with options they do not understand. However, situation with vendor options is more complex, as they are not requested the
the situation with vendor options is more complex, as they same way as other options, are not well-documented in official RFCs, or vary by
are not requested the same way as other options, are vendor.
not well-documented in official RFCs, or vary by vendor.
Some vendors (such Some vendors (such as DOCSIS, identified by vendor option 4491) have a mechanism
as DOCSIS, identified by vendor option 4491) have a mechanism to to request specific vendor options and Kea is able to honor those (sub-option 1).
request specific vendor options and Kea is able to honor those. Unfortunately, for many other vendors, such as Genexis (25167, discussed above),
Unfortunately, for many other vendors, such as Genexis (25167, discussed Kea does not have such a mechanism, so it cannot send any sub-options on its own.
above), Kea does not have such a mechanism, so it cannot send any To solve this issue, we devised the concept of persistent options. Kea can be
sub-options on its own. To solve this issue, we devised the concept of told to always send options, even if the client did not request them. This can
persistent options. Kea can be told to always send options, even if the be achieved by adding ``"always-send": true`` to the option definition. Note
client did not request them. This can be achieved by adding that in this particular case an option is defined in vendor space 25167. With
``"always-send": true`` to the option definition. Note that in this ``always-send`` enabled, the option is sent every time there is a need to deal
particular case an option is defined in vendor space 25167. With with vendor space 25167.
``always-send`` enabled, the option is sent every time there is a This is also how the Kea server can be configured to send multiple vendor
need to deal with vendor space 25167. enterprise numbers and multiple options, specific for each vendor.
Another possibility is to redefine the option; see :ref:`dhcp4-private-opts`. Another possibility is to redefine the option; see :ref:`dhcp4-private-opts`.
@@ -2576,10 +2574,16 @@ and ``doc/examples/kea6/vivso.json`` in the Kea sources.
.. note:: .. note::
Currently only one vendor is supported for the ``vivco-suboptions`` (code 124) Multiple vendor enterprise numbers are supported by ``vivso-suboptions``
and ``vivso-suboptions`` (code 125) options. Specifying (code 125) option. The option can contain multiple options for each vendor.
multiple enterprise numbers within a single option instance or multiple
options with different enterprise numbers is not supported. 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: .. _dhcp4-option-spaces:
@@ -2741,11 +2745,11 @@ Support for Long Options
------------------------ ------------------------
The kea-dhcp4 server partially supports long options (RFC3396). The kea-dhcp4 server partially supports long options (RFC3396).
Since Kea 2.1.6, the server accepts configuring long options and suboptions Since Kea 2.1.6, the server accepts configuring long options and sub-options
(longer than 255 bytes). The options and suboptions are stored internally (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 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 language. On send, the server splits long options and sub-options into multiple
options and suboptions, using the respective option code. options and sub-options, using the respective option code.
:: ::
@@ -2800,8 +2804,8 @@ into two options, each with the code 240.
The server is also able to receive packets with split options (options using 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 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 also supported for sub-options if each sub-option data chunk also contains the
suboption code and suboption length. sub-option code and sub-option length.
.. _dhcp4-stateless-configuration: .. _dhcp4-stateless-configuration:
@@ -4084,7 +4088,7 @@ retained on the lease. The lease's user-context looks something like this:
{ "ISC": { "relay-agent-info": { "sub-options": "0x0104AABBCCDD" } } } { "ISC": { "relay-agent-info": { "sub-options": "0x0104AABBCCDD" } } }
Or with remote and relay suboptions: Or with remote and relay sub-options:
:: ::
@@ -7292,12 +7296,12 @@ Ignore RAI Link Selection
------------------------- -------------------------
With ``"ignore-rai-link-selection": true``, Relay Agent Information Link 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 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 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 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. and include incorrect Link Selection information.
.. code-block:: json .. code-block:: json

View File

@@ -1736,41 +1736,38 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
// Retrieve subnet. // Retrieve subnet.
Subnet4Ptr subnet = ex.getContext()->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. // Firstly, host specific options.
const ConstHostPtr& host = ex.getContext()->currentHost(); const ConstHostPtr& host = ex.getContext()->currentHost();
if (host && !host->getCfgOption4()->empty()) { if (host && !host->getCfgOption4()->empty()) {
co_list.push_back(host->getCfgOption4()); co_list.push_back(host->getCfgOption4());
} }
// Secondly, pool specific options. // Secondly, pool specific options. Pools are defined within a subnet, so
Pkt4Ptr resp = ex.getResponse(); // if there is no subnet, there is nothing to do.
IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS(); if (subnet) {
if (resp) { Pkt4Ptr resp = ex.getResponse();
addr = resp->getYiaddr(); IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
} if (resp) {
if (!addr.isV4Zero()) { addr = resp->getYiaddr();
PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false); }
if (pool && !pool->getCfgOption()->empty()) { if (!addr.isV4Zero()) {
co_list.push_back(pool->getCfgOption()); PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
if (pool && !pool->getCfgOption()->empty()) {
co_list.push_back(pool->getCfgOption());
}
} }
}
// Thirdly, subnet configured options. // Thirdly, subnet configured options.
if (!subnet->getCfgOption()->empty()) { if (!subnet->getCfgOption()->empty()) {
co_list.push_back(subnet->getCfgOption()); co_list.push_back(subnet->getCfgOption());
} }
// Fourthly, shared network specific options. // Fourthly, shared network specific options.
SharedNetwork4Ptr network; SharedNetwork4Ptr network;
subnet->getSharedNetwork(network); subnet->getSharedNetwork(network);
if (network && !network->getCfgOption()->empty()) { if (network && !network->getCfgOption()->empty()) {
co_list.push_back(network->getCfgOption()); co_list.push_back(network->getCfgOption());
}
} }
// Each class in the incoming packet // Each class in the incoming packet
@@ -1807,17 +1804,6 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
void void
Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) { 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 // Unlikely short cut
const CfgOptionList& co_list = ex.getCfgOptionList(); const CfgOptionList& co_list = ex.getCfgOptionList();
if (co_list.empty()) { if (co_list.empty()) {
@@ -1826,20 +1812,23 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
Pkt4Ptr query = ex.getQuery(); Pkt4Ptr query = ex.getQuery();
Pkt4Ptr resp = ex.getResponse(); 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 // try to get the 'Parameter Request List' option which holds the
// codes of requested options. // codes of requested options.
OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast< OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST)); 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) { 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 // Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin(); for (auto const& copts : co_list) {
copts != co_list.end(); ++copts) { const OptionContainerPtr& opts = copts->getAll(DHCP4_OPTION_SPACE);
const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
if (!opts) { if (!opts) {
continue; continue;
} }
@@ -1850,86 +1839,127 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
desc != range.second; ++desc) { desc != range.second; ++desc) {
// Add the persistent option code to requested options // Add the persistent option code to requested options
if (desc->option_) { if (desc->option_) {
uint8_t code = static_cast<uint8_t>(desc->option_->getType()); static_cast<void>(requested_opts.insert(desc->option_->getType()));
requested_opts.push_back(code);
} }
} }
} }
// For each requested option code get the instance of the option // For each requested option code get the instance of the option
// to be returned to the client. // to be returned to the client.
for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin(); for (auto const& opt : requested_opts) {
opt != requested_opts.end(); ++opt) { // Add nothing when it is already there.
// Add nothing when it is already there // Skip special cases: DHO_VIVSO_SUBOPTIONS
if (!resp->getOption(*opt)) { if (opt == DHO_VIVSO_SUBOPTIONS) {
continue;
}
if (!resp->getOption(opt)) {
// Iterate on the configured option list // Iterate on the configured option list
for (CfgOptionList::const_iterator copts = co_list.begin(); for (auto const& copts : co_list) {
copts != co_list.end(); ++copts) { OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, opt);
OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE, *opt);
// Got it: add it and jump to the outer loop // Got it: add it and jump to the outer loop
if (desc.option_) { if (desc.option_) {
resp->addOption(desc.option_); resp->Pkt::addOption(desc.option_);
break; 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 void
Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) { Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
// Get the configured subnet suitable for the incoming packet. // Get the configured subnet suitable for the incoming packet.
Subnet4Ptr subnet = ex.getContext()->subnet_; Subnet4Ptr subnet = ex.getContext()->subnet_;
const CfgOptionList& co_list = ex.getCfgOptionList();
// Leave if there is no subnet matching the incoming packet. // Leave if there is no subnet matching the incoming packet.
// There is no need to log the error message here because // There is no need to log the error message here because
// it will be logged in the assignLease() when it fails to // it will be logged in the assignLease() when it fails to
// pick the suitable subnet. We don't want to duplicate // pick the suitable subnet. We don't want to duplicate
// error messages in such case. // error messages in such case.
if (!subnet) { //
// Also, if there's no options to possibly assign, give up.
if (!subnet || co_list.empty()) {
return; return;
} }
// Unlikely short cut Pkt4Ptr query = ex.getQuery();
const CfgOptionList& co_list = ex.getCfgOptionList(); Pkt4Ptr resp = ex.getResponse();
if (co_list.empty()) { 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));
}
}
// 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 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; return;
} }
uint32_t vendor_id = 0; map<uint32_t, set<uint8_t> > requested_opts;
// 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();
}
// 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();
}
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.
return;
}
std::vector<uint8_t> requested_opts;
// Let's try to get ORO within that vendor-option. // Let's try to get ORO within that vendor-option.
// This is specific to vendor-id=4491 (Cable Labs). Other vendors may have // This is specific to vendor-id=4491 (Cable Labs). Other vendors may have
// different policies. // different policies.
OptionUint8ArrayPtr oro; 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); OptionPtr oro_generic = vendor_req->getOption(DOCSIS3_V4_ORO);
if (oro_generic) { if (oro_generic) {
// Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS // Vendor ID 4491 makes Kea look at DOCSIS3_V4_OPTION_DEFINITIONS
@@ -1937,66 +1967,75 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
// created as an OptionUint8Array, but might not be for other // created as an OptionUint8Array, but might not be for other
// vendor IDs. // vendor IDs.
oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic); oro = boost::dynamic_pointer_cast<OptionUint8Array>(oro_generic);
// Get the list of options that client requested. }
if (oro) { if (oro) {
requested_opts = oro->getValues(); 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 // Iterate on the configured option list to add persistent options
for (CfgOptionList::const_iterator copts = co_list.begin(); for (uint32_t vendor_id : vendor_ids) {
copts != co_list.end(); ++copts) { for (auto const& copts : co_list) {
const OptionContainerPtr& opts = (*copts)->getAll(vendor_id); const OptionContainerPtr& opts = copts->getAll(vendor_id);
if (!opts) { 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()));
}
}
// 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; continue;
} }
// Get persistent options // It's possible that the vendor opts option was inserted already
const OptionContainerPersistIndex& idx = opts->get<2>(); // by client class or a hook. If that is so, let's use it.
const OptionContainerPersistRange& range = idx.equal_range(true); OptionVendorPtr vendor_rsp;
for (OptionContainerPersistIndex::const_iterator desc = range.first; if (vendor_rsps.count(vendor_id) > 0) {
desc != range.second; ++desc) { vendor_rsp = vendor_rsps[vendor_id];
// Add the persistent option code to requested options } else {
if (desc->option_) { vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id));
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 then. // Get the list of options that client requested.
if (requested_opts.empty()) { bool added = false;
return;
}
if (!vendor_rsp) { for (uint8_t opt : requested_opts[vendor_id]) {
// It's possible that vivso was inserted already by client class or if (!vendor_rsp->getOption(opt)) {
// a hook. If that is so, let's use it. for (auto const& copts : co_list) {
vendor_rsp.reset(new OptionVendor(Option::V4, vendor_id)); OptionDescriptor desc = copts->get(vendor_id, opt);
} if (desc.option_) {
vendor_rsp->addOption(desc.option_);
// Get the list of options that client requested. added = true;
bool added = false; break;
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;
} }
} }
} }
}
// If we added some sub-options and the vivso option is not in // If we added some sub-options and the vendor opts option is not in
// the response already, then add it. // the response already, then add it.
if (added && !ex.getResponse()->getOption(DHO_VIVSO_SUBOPTIONS)) { if (added && (vendor_rsps.count(vendor_id) == 0)) {
ex.getResponse()->addOption(vendor_rsp); resp->Pkt::addOption(vendor_rsp);
}
} }
} }
@@ -2004,15 +2043,12 @@ void
Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) { Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
// Identify options that we always want to send to the // Identify options that we always want to send to the
// client (if they are configured). // client (if they are configured).
static const uint16_t required_options[] = { static const std::vector<uint16_t> required_options = {
DHO_ROUTERS, DHO_ROUTERS,
DHO_DOMAIN_NAME_SERVERS, DHO_DOMAIN_NAME_SERVERS,
DHO_DOMAIN_NAME, DHO_DOMAIN_NAME,
DHO_DHCP_SERVER_IDENTIFIER }; DHO_DHCP_SERVER_IDENTIFIER };
static size_t required_options_size =
sizeof(required_options) / sizeof(required_options[0]);
// Get the subnet. // Get the subnet.
Subnet4Ptr subnet = ex.getContext()->subnet_; Subnet4Ptr subnet = ex.getContext()->subnet_;
if (!subnet) { if (!subnet) {
@@ -2029,14 +2065,12 @@ Dhcpv4Srv::appendBasicOptions(Dhcpv4Exchange& ex) {
// Try to find all 'required' options in the outgoing // Try to find all 'required' options in the outgoing
// message. Those that are not present will be added. // message. Those that are not present will be added.
for (int i = 0; i < required_options_size; ++i) { for (auto const& required : required_options) {
OptionPtr opt = resp->getOption(required_options[i]); OptionPtr opt = resp->getOption(required);
if (!opt) { if (!opt) {
// Check whether option has been configured. // Check whether option has been configured.
for (CfgOptionList::const_iterator copts = co_list.begin(); for (auto const& copts : co_list) {
copts != co_list.end(); ++copts) { OptionDescriptor desc = copts->get(DHCP4_OPTION_SPACE, required);
OptionDescriptor desc = (*copts)->get(DHCP4_OPTION_SPACE,
required_options[i]);
if (desc.option_) { if (desc.option_) {
resp->addOption(desc.option_); resp->addOption(desc.option_);
break; break;

View File

@@ -8,15 +8,17 @@
// vendor options in DHCPv4: // vendor options in DHCPv4:
// //
// vivso (125) - vendor independent vendor specific option. This is by far the // 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. // vendor specific (43) - this is probably the second most popular.
// Unfortunately, its definition is blurry, so there are many // Unfortunately, its definition is blurry, so there are
// similar, but not exact implementations that do things in // many similar, but not exact implementations that do
// different ways. // things in different ways.
// vivco (124) - vendor independent vendor class option. // vivco (124) - vendor independent vendor class option.
// class identifier (60) - not exactly vendor specific. It's a string, but the // class identifier (60) - not exactly vendor specific. It's a string, but the
// content of that string identifies what kind of vendor device // content of that string identifies what kind of vendor
// this is. // device this is.
// client-class (77) - this specifies (as a plain string) what kind of device
// this is.
#include <config.h> #include <config.h>
#include <asiolink/io_address.h> #include <asiolink/io_address.h>
@@ -63,10 +65,28 @@ public:
/// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491) /// @brief Checks if Option Request Option (ORO) in docsis (vendor-id=4491)
/// vendor options is parsed correctly and the requested options are /// 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. /// 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"( string config = R"(
{ {
"interfaces-config": { "interfaces-config": {
@@ -79,6 +99,60 @@ public:
"data": "192.0.2.1, 192.0.2.2", "data": "192.0.2.1, 192.0.2.2",
"name": "tftp-servers", "name": "tftp-servers",
"space": "vendor-4491" "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": [ "subnet4": [
@@ -116,10 +190,11 @@ public:
// Set interface. It is required by the server to generate server id. // Set interface. It is required by the server to generate server id.
dis->setIface("eth0"); dis->setIface("eth0");
dis->setIndex(ETH0_INDEX); dis->setIndex(ETH0_INDEX);
OptionPtr clientid = generateClientId(); OptionPtr clientid = generateClientId();
dis->addOption(clientid); 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); Pkt4Ptr offer = srv.processDiscover(dis);
// Check if we get a response at all. // Check if we get a response at all.
@@ -133,10 +208,15 @@ public:
// That suboption has code 1 and is a docsis ORO option. // That suboption has code 1 and is a docsis ORO option.
boost::shared_ptr<OptionUint8Array> vendor_oro(new OptionUint8Array(Option::V4, boost::shared_ptr<OptionUint8Array> vendor_oro(new OptionUint8Array(Option::V4,
DOCSIS3_V4_ORO)); DOCSIS3_V4_ORO));
vendor_oro->addValue(DOCSIS3_V4_TFTP_SERVERS); // Request option 2. for (auto const& option : requested_options) {
OptionPtr vendor(new OptionVendor(Option::V4, vendor_id)); vendor_oro->addValue(option);
vendor->addOption(vendor_oro); }
dis->addOption(vendor);
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. // Need to process DHCPDISCOVER again after requesting new option.
offer = srv.processDiscover(dis); offer = srv.processDiscover(dis);
@@ -146,31 +226,341 @@ public:
// vendor ID was provided in the request. Otherwise, check that there is // vendor ID was provided in the request. Otherwise, check that there is
// no vendor and stop processing since the following checks are built on // no vendor and stop processing since the following checks are built on
// top of the now-absent options. // top of the now-absent options.
OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS); OptionCollection tmp = offer->getOptions(DHO_VIVSO_SUBOPTIONS);
if (vendor_id != VENDOR_ID_CABLE_LABS) { ASSERT_EQ(tmp.size(), result_vendor_ids.size());
EXPECT_FALSE(tmp); if (!result_vendor_ids.size()) {
return; return;
} }
ASSERT_TRUE(tmp);
// The response should be an OptionVendor. for (const auto& opt : tmp) {
boost::shared_ptr<OptionVendor> vendor_resp = // The response should be an OptionVendor.
boost::dynamic_pointer_cast<OptionVendor>(tmp); OptionVendorPtr vendor_resp;
ASSERT_TRUE(vendor_resp);
// Option 2 should be present. for (auto const& vendor_id : result_vendor_ids) {
OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS); vendor_resp = boost::dynamic_pointer_cast<OptionVendor>(opt.second);
ASSERT_TRUE(docsis2); 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. // It should be an Option4AddrLst.
Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2); Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
ASSERT_TRUE(tftp_srvs); ASSERT_TRUE(tftp_srvs);
// Check that the provided addresses match the ones in configuration. // Check that the provided addresses match the ones in configuration.
Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses(); Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
ASSERT_EQ(2, addrs.size()); ASSERT_EQ(2, addrs.size());
EXPECT_EQ("192.0.2.1", addrs[0].toText()); EXPECT_EQ("192.0.2.1", addrs[0].toText());
EXPECT_EQ("192.0.2.2", addrs[1].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_; std::unique_ptr<IfaceMgrTestConfig> iface_mgr_test_config_;
@@ -240,9 +630,9 @@ TEST_F(VendorOptsTest, vendorOptionsDocsis) {
ASSERT_TRUE(vendor_opt_response); ASSERT_TRUE(vendor_opt_response);
// Check if it's of a correct type // Check if it's of a correct type
boost::shared_ptr<OptionVendor> vendor_opt = OptionVendorPtr vendor_opt = boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
ASSERT_TRUE(vendor_opt); ASSERT_TRUE(vendor_opt);
ASSERT_EQ(vendor_opt->getVendorId(), VENDOR_ID_CABLE_LABS);
// Get Relay Agent Info from response... // Get Relay Agent Info from response...
OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS); OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS);
@@ -269,8 +659,9 @@ TEST_F(VendorOptsTest, docsisVendorOptionsParse) {
OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS); OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_TRUE(opt); 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_TRUE(vendor);
ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
// This particular capture that we have included options 1 and 5 // This particular capture that we have included options 1 and 5
EXPECT_TRUE(vendor->getOption(1)); EXPECT_TRUE(vendor->getOption(1));
@@ -287,14 +678,15 @@ TEST_F(VendorOptsTest, docsisVendorORO) {
// Let's get a traffic capture from DOCSIS3.0 modem // Let's get a traffic capture from DOCSIS3.0 modem
Pkt4Ptr dis = PktCaptures::captureRelayedDiscover(); Pkt4Ptr dis = PktCaptures::captureRelayedDiscover();
EXPECT_NO_THROW(dis->unpack()); ASSERT_NO_THROW(dis->unpack());
// Check if the packet contains vendor specific information option // Check if the packet contains vendor specific information option
OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS); OptionPtr opt = dis->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_TRUE(opt); 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_TRUE(vendor);
ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
opt = vendor->getOption(DOCSIS3_V4_ORO); opt = vendor->getOption(DOCSIS3_V4_ORO);
ASSERT_TRUE(opt); ASSERT_TRUE(opt);
@@ -305,90 +697,134 @@ TEST_F(VendorOptsTest, docsisVendorORO) {
// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491) // 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. // vendor options is parsed correctly and the requested options are actually assigned.
TEST_F(VendorOptsTest, vendorOptionsORO) { TEST_F(VendorOptsTest, vendorOptionsOROOneOption) {
testVendorOptionsORO(VENDOR_ID_CABLE_LABS); 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 // 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. // provided and vendor options are expected to not be present in the response.
TEST_F(VendorOptsTest, vendorOptionsORODifferentVendorID) { TEST_F(VendorOptsTest, vendorOptionsOROOneOptionDifferentVendorID) {
testVendorOptionsORO(32768); testVendorOptionsORO({VENDOR_ID_CABLE_LABS}, {32768}, {DOCSIS3_V4_TFTP_SERVERS});
}
// 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});
}
// 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});
}
// 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});
} }
// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491) // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
// vendor options is parsed correctly and persistent options are actually assigned. // vendor options is parsed correctly and persistent options are actually assigned.
TEST_F(VendorOptsTest, vendorPersistentOptions) { TEST_F(VendorOptsTest, vendorPersistentOptionsOneOption) {
NakedDhcpv4Srv srv(0); testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, false);
}
ConstElementPtr x; // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
string config = "{ \"interfaces-config\": {" // vendor options is parsed correctly and persistent options are actually assigned.
" \"interfaces\": [ \"*\" ]" TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOption) {
"}," testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, false);
" \"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\" "
" } ]"
"}";
ConstElementPtr json; // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
ASSERT_NO_THROW(json = parseDHCP4(config)); // 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);
}
EXPECT_NO_THROW(x = configureDhcp4Server(srv, json)); // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
ASSERT_TRUE(x); // vendor options is parsed correctly and persistent options are actually assigned.
comment_ = parseAnswer(rcode_, x); TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchOne) {
ASSERT_EQ(0, rcode_); testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, 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, vendorPersistentOptionsOneOptionMultipleVendorsMatchAll) {
testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS}, false);
}
boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234)); // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
// Set the giaddr and hops to non-zero address as if it was relayed. // vendor options is parsed correctly and persistent options are actually assigned.
dis->setGiaddr(IOAddress("192.0.2.1")); TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAll) {
dis->setHops(1); testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS, 22}, false);
}
OptionPtr clientid = generateClientId(); // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
dis->addOption(clientid); // vendor options is parsed correctly and persistent options are actually assigned.
// Set interface. It is required by the server to generate server id. TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionAddVendorOption) {
dis->setIface("eth0"); testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS}, true);
dis->setIndex(ETH0_INDEX); }
// Let's add a vendor-option (vendor-id=4491). // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
OptionPtr vendor(new OptionVendor(Option::V4, 4491)); // vendor options is parsed correctly and persistent options are actually assigned.
dis->addOption(vendor); TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionAddVendorOption) {
testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS}, {VENDOR_ID_CABLE_LABS}, {DOCSIS3_V4_TFTP_SERVERS, 22}, true);
}
// Pass it to the server and get an advertise // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
Pkt4Ptr offer = srv.processDiscover(dis); // 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);
}
// check if we get response at all // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
ASSERT_TRUE(offer); // 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);
}
// Check if there is a vendor option response // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS); // vendor options is parsed correctly and persistent options are actually assigned.
ASSERT_TRUE(tmp); TEST_F(VendorOptsTest, vendorPersistentOptionsOneOptionMultipleVendorsMatchAllAddVendorOption) {
testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS}, true);
}
// The response should be OptionVendor object // This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
boost::shared_ptr<OptionVendor> vendor_resp = // vendor options is parsed correctly and persistent options are actually assigned.
boost::dynamic_pointer_cast<OptionVendor>(tmp); TEST_F(VendorOptsTest, vendorPersistentOptionsMultipleOptionMultipleVendorsMatchAllAddVendorOption) {
ASSERT_TRUE(vendor_resp); testVendorOptionsPersistent({VENDOR_ID_CABLE_LABS, 3561}, {VENDOR_ID_CABLE_LABS, 3561}, {DOCSIS3_V4_TFTP_SERVERS, 22}, true);
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 // Test checks whether it is possible to use option definitions defined in
@@ -540,7 +976,7 @@ TEST_F(VendorOptsTest, vivsoInResponseOnly) {
// Check that it includes vivso with vendor-id = 25167 // Check that it includes vivso with vendor-id = 25167
OptionVendorPtr rsp_vivso = boost::dynamic_pointer_cast<OptionVendor>(rsp); OptionVendorPtr rsp_vivso = boost::dynamic_pointer_cast<OptionVendor>(rsp);
ASSERT_TRUE(rsp_vivso); 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. // Now check that it contains suboption 2 with appropriate content.
OptionPtr subopt2 = rsp_vivso->getOption(2); OptionPtr subopt2 = rsp_vivso->getOption(2);

View File

@@ -207,8 +207,8 @@ TEST_F(HAImplTest, buffer4Receive) {
}; };
// Provide DHCP message type option, truncated vendor option and domain name. // Provide DHCP message type option, truncated vendor option and domain name.
// Parsing this message should be successful but domain name following the // Parsing this message should be successful but vendor option should be
// truncated vendor option should be skipped. // skipped.
std::vector<uint8_t> options = { std::vector<uint8_t> options = {
53, 1, 1, // Message type = DHCPDISCOVER 53, 1, 1, // Message type = DHCPDISCOVER
125, 6, // vendor options 125, 6, // vendor options
@@ -240,8 +240,10 @@ TEST_F(HAImplTest, buffer4Receive) {
// Check that the message has been parsed. The DHCP message type should // Check that the message has been parsed. The DHCP message type should
// be set in this case. // be set in this case.
EXPECT_EQ(DHCPDISCOVER, static_cast<int>(query4->getType())); EXPECT_EQ(DHCPDISCOVER, static_cast<int>(query4->getType()));
// Domain name should be skipped because the vendor option was truncated. // Vendor option should be skipped because it was truncated.
EXPECT_FALSE(query4->getOption(DHO_DOMAIN_NAME)); 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. // Tests for buffer6_receive callout implementation.

View File

@@ -304,7 +304,6 @@ LibDHCP::optionFactory(Option::Universe u,
return (it->second(u, type, buf)); return (it->second(u, type, buf));
} }
size_t size_t
LibDHCP::unpackOptions6(const OptionBuffer& buf, LibDHCP::unpackOptions6(const OptionBuffer& buf,
const std::string& option_space, const std::string& option_space,
@@ -576,6 +575,10 @@ LibDHCP::unpackOptions4(const OptionBuffer& buf,
} }
} }
if (space_is_dhcp4 && opt_type == DHO_VIVSO_SUBOPTIONS) {
num_defs = 0;
}
OptionPtr opt; OptionPtr opt;
if (num_defs > 1) { if (num_defs > 1) {
// Multiple options of the same code are not supported right now! // Multiple options of the same code are not supported right now!
@@ -682,6 +685,46 @@ LibDHCP::fuseOptions4(OptionCollection& options) {
return (result); 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 size_t
LibDHCP::unpackVendorOptions6(const uint32_t vendor_id, LibDHCP::unpackVendorOptions6(const uint32_t vendor_id,
const OptionBuffer& buf, const OptionBuffer& buf,

View File

@@ -299,6 +299,13 @@ public:
/// @return True if any option has been fused, false otherwise. /// @return True if any option has been fused, false otherwise.
static bool fuseOptions4(isc::dhcp::OptionCollection& options); 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 /// @brief Parses provided buffer as DHCPv4 options and creates
/// Option objects. /// Option objects.
/// ///

View File

@@ -16,19 +16,22 @@ using namespace isc::dhcp;
OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id) OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id)
: Option(u, u == Option::V4 ? : Option(u, u == Option::V4 ?
static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) : static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
static_cast<uint16_t>(D6O_VENDOR_OPTS)), static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(vendor_id) {
vendor_id_(vendor_id) {
} }
OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin, OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
OptionBufferConstIter end) OptionBufferConstIter end)
: Option(u, u == Option::V4? : Option(u, u == Option::V4?
static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) : static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
static_cast<uint16_t>(D6O_VENDOR_OPTS)), static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(0) {
vendor_id_(0) {
unpack(begin, end); unpack(begin, end);
} }
OptionVendor::OptionVendor(const OptionVendor& option) : Option(option),
vendor_id_(option.vendor_id_) {
Option::operator=(option);
}
OptionPtr OptionPtr
OptionVendor::clone() const { OptionVendor::clone() const {
return (cloneInternal<OptionVendor>()); return (cloneInternal<OptionVendor>());
@@ -45,7 +48,12 @@ void OptionVendor::pack(isc::util::OutputBuffer& buf, bool check) const {
// Calculate and store data-len as follows: // Calculate and store data-len as follows:
// data-len = total option length - header length // data-len = total option length - header length
// - enterprise id field length - data-len field size // - 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); packOptions(buf, check);
@@ -53,7 +61,6 @@ void OptionVendor::pack(isc::util::OutputBuffer& buf, bool check) const {
void OptionVendor::unpack(OptionBufferConstIter begin, void OptionVendor::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) { OptionBufferConstIter end) {
// We throw SkipRemainingOptionsError so callers can // We throw SkipRemainingOptionsError so callers can
// abandon further unpacking, if desired. // abandon further unpacking, if desired.
if (distance(begin, end) < sizeof(uint32_t)) { if (distance(begin, end) < sizeof(uint32_t)) {
@@ -62,6 +69,8 @@ void OptionVendor::unpack(OptionBufferConstIter begin,
<< ", length=" << distance(begin, end)); << ", length=" << distance(begin, end));
} }
options_.clear();
vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end)); vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
OptionBuffer vendor_buffer(begin + 4, end); OptionBuffer vendor_buffer(begin + 4, end);
@@ -84,31 +93,26 @@ uint16_t OptionVendor::len() const {
} }
// length of all suboptions // length of all suboptions
for (OptionCollection::const_iterator it = options_.begin(); for (auto const& opt : options_) {
it != options_.end(); length += opt.second->len();
++it) {
length += (*it).second->len();
} }
return (length); 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 std::string
OptionVendor::toText(int indent) const { OptionVendor::toText(int indent) const {
std::stringstream output; std::stringstream output;
output << headerToText(indent) << ": " output << headerToText(indent) << ": ";
<< getVendorId() << " (uint32)";
output << vendor_id_ << " (uint32)";
// For the DHCPv4 there is one more field. // For the DHCPv4 there is one more field.
if (getUniverse() == Option::V4) { 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. // Append suboptions.
@@ -116,3 +120,11 @@ OptionVendor::toText(int indent) const {
return (output.str()); return (output.str());
} }
OptionVendor& OptionVendor::operator=(const OptionVendor& rhs) {
if (&rhs != this) {
Option::operator=(rhs);
vendor_id_ = rhs.vendor_id_;
}
return (*this);
}

View File

@@ -50,6 +50,14 @@ public:
OptionVendor(Option::Universe u, OptionBufferConstIter begin, OptionVendor(Option::Universe u, OptionBufferConstIter begin,
OptionBufferConstIter end); 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. /// @brief Copies this option and returns a pointer to the copy.
OptionPtr clone() const; OptionPtr clone() const;
@@ -77,12 +85,16 @@ public:
/// @brief Sets enterprise identifier /// @brief Sets enterprise identifier
/// ///
/// @param vendor_id vendor 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 /// @brief Returns enterprise identifier
/// ///
/// @return 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 /// @brief returns complete length of option
/// ///
@@ -98,19 +110,19 @@ public:
/// @return Vendor option in the textual format. /// @return Vendor option in the textual format.
virtual std::string toText(int indent = 0) const; virtual std::string toText(int indent = 0) const;
/// @brief Assignment operator.
///
/// The assignment operator performs a deep copy of the option and
/// its suboptions. It calls @ref getOptionsCopy to deep copy
/// suboptions.
///
/// @param rhs Option to be assigned.
OptionVendor& operator=(const OptionVendor& rhs);
private: private:
/// @brief Calculates the data-len value for DHCPv4. /// @brief Enterprise-id
/// uint32_t vendor_id_;
/// 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.
///
/// @return Returns calculated data-len value.
uint8_t dataLen() const;
uint32_t vendor_id_; ///< Enterprise-id
}; };
/// Pointer to a vendor option /// Pointer to a vendor option

View File

@@ -227,6 +227,10 @@ Pkt4::unpack() {
// access the entire data. // access the entire data.
LibDHCP::fuseOptions4(options_); 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 // No need to call check() here. There are thorough tests for this
// later (see Dhcp4Srv::accept()). We want to drop the packet later, // later (see Dhcp4Srv::accept()). We want to drop the packet later,
// so we'll be able to log more detailed drop reason. // so we'll be able to log more detailed drop reason.

View File

@@ -408,7 +408,7 @@ TEST_F(LibDhcpTest, packOptions6) {
OptionBuffer(v6packed + 46, v6packed + 50))); OptionBuffer(v6packed + 46, v6packed + 50)));
boost::shared_ptr<OptionInt<uint32_t> > 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(cm_mac);
vsi->addOption(cmts_caps); vsi->addOption(cmts_caps);
@@ -451,7 +451,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
ASSERT_EQ(5, x->second->getData().size()); ASSERT_EQ(5, x->second->getData().size());
EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5 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 ASSERT_FALSE(x == options.end()); // option 2 should exist
EXPECT_EQ(2, x->second->getType()); // this should be option 2 EXPECT_EQ(2, x->second->getType()); // this should be option 2
ASSERT_EQ(7, x->second->len()); // it should be of length 7 ASSERT_EQ(7, x->second->len()); // it should be of length 7
@@ -510,15 +510,19 @@ TEST_F(LibDhcpTest, unpackOptions6) {
EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType()); EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType());
EXPECT_EQ(26, x->second->len()); 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 // 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); ASSERT_TRUE(cm_mac);
EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType()); EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType());
ASSERT_EQ(10, cm_mac->len()); ASSERT_EQ(10, cm_mac->len());
EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6)); EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6));
// CMTS Capabilities // CMTS Capabilities
OptionPtr cmts_caps = x->second->getOption(OPTION_CMTS_CAPS); OptionPtr cmts_caps = vendor->getOption(OPTION_CMTS_CAPS);
ASSERT_TRUE(cmts_caps); ASSERT_TRUE(cmts_caps);
EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType()); EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType());
ASSERT_EQ(8, cmts_caps->len()); ASSERT_EQ(8, cmts_caps->len());
@@ -1076,30 +1080,96 @@ TEST_F(LibDhcpTest, fuseLongOptionWithLongSuboption) {
} }
} }
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. // This test verifies that pack options for v4 is working correctly.
TEST_F(LibDhcpTest, packOptions4) { TEST_F(LibDhcpTest, packOptions4) {
vector<uint8_t> payload[5]; vector<uint8_t> payload[5];
for (unsigned i = 0; i < 5; i++) { for (unsigned i = 0; i < 5; i++) {
payload[i].resize(3); payload[i].resize(3);
payload[i][0] = i*10; payload[i][0] = i * 10;
payload[i][1] = i*10+1; payload[i][1] = i * 10 + 1;
payload[i][2] = i*10+2; payload[i][2] = i * 10 + 2;
} }
OptionPtr opt1(new Option(Option::V4, 12, payload[0])); OptionPtr opt1(new Option(Option::V4, 12, payload[0]));
OptionPtr opt2(new Option(Option::V4, 60, payload[1])); OptionPtr opt2(new Option(Option::V4, 60, payload[1]));
OptionPtr opt3(new Option(Option::V4, 14, payload[2])); OptionPtr opt3(new Option(Option::V4, 14, payload[2]));
OptionPtr opt4(new Option(Option::V4,254, payload[3])); OptionPtr opt4(new Option(Option::V4, 254, payload[3]));
OptionPtr opt5(new Option(Option::V4,128, payload[4])); OptionPtr opt5(new Option(Option::V4, 128, payload[4]));
// Create vendor option instance with DOCSIS3.0 enterprise id. // 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, vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS,
IOAddress("10.0.0.10")))); IOAddress("10.0.0.10"))));
OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS, OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS,
OptionBuffer())); OptionBuffer()));
vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer()))); vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer())));
// Add RAI option, which comprises 3 sub-options. // Add RAI option, which comprises 3 sub-options.
@@ -1199,6 +1269,8 @@ TEST_F(LibDhcpTest, unpackOptions4) {
deferred, false); deferred, false);
); );
ASSERT_NO_THROW(LibDHCP::extendVendorOptions4(options));
isc::dhcp::OptionCollection::const_iterator x = options.find(12); isc::dhcp::OptionCollection::const_iterator x = options.find(12);
ASSERT_FALSE(x == options.end()); // option 1 should exist ASSERT_FALSE(x == options.end()); // option 1 should exist
// Option 12 holds a string so let's cast it to an appropriate type. // Option 12 holds a string so let's cast it to an appropriate type.
@@ -1245,7 +1317,7 @@ TEST_F(LibDhcpTest, unpackOptions4) {
OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second); OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second);
ASSERT_TRUE(vivsi); ASSERT_TRUE(vivsi);
EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType()); EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
EXPECT_EQ(4491, vivsi->getVendorId()); EXPECT_EQ(VENDOR_ID_CABLE_LABS, vivsi->getVendorId());
OptionCollection suboptions = vivsi->getOptions(); OptionCollection suboptions = vivsi->getOptions();
// There should be one suboption of V-I VSI. // There should be one suboption of V-I VSI.
@@ -2094,7 +2166,6 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(), LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
vivco_buf.end(), typeid(OptionVendorClass)); vivco_buf.end(), typeid(OptionVendorClass));
LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(), LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(),
vivsio_buf.end(), typeid(OptionVendor)); vivsio_buf.end(), typeid(OptionVendor));
@@ -2723,7 +2794,7 @@ TEST_F(LibDhcpTest, vendorClass6) {
// Let's investigate if the option content is correct // Let's investigate if the option content is correct
// 3 fields expected: vendor-id, data-len and data // 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()); EXPECT_EQ(20, vclass->len());
ASSERT_EQ(1, vclass->getTuplesNum()); ASSERT_EQ(1, vclass->getTuplesNum());
EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText()); EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText());