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:
@@ -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
|
||||||
|
@@ -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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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.
|
||||||
|
@@ -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,
|
||||||
|
@@ -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.
|
||||||
///
|
///
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
@@ -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());
|
||||||
|
Reference in New Issue
Block a user