2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-01 22:45:18 +00:00

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

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

View File

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