From: Marcin Siodelski Date: Mon, 26 Jun 2023 10:09:23 +0000 (+0200) Subject: [#2826] Options in db are not encapsulated X-Git-Tag: Kea-2.4.0~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=132ef88b47cf5a9932f2fd2dd30fceb02c0bafec;p=thirdparty%2Fkea.git [#2826] Options in db are not encapsulated Returned options from db are not encapsulated and have to be encapsulated before the server returns them to a client. Parsers, depending on the use case, may or may not encapsulate options. When they are used for parsing a config file, the options are encapsulated. When they are used by the host_cmds or cb_cmds, they are not. --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index c96a34317c..e7b1d82632 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -420,6 +420,13 @@ AllocEngine::findReservation(ClientContext6& ctx) { // done here. subnet = subnet->getNextSubnet(ctx.subnet_, classes); } + + // The hosts can be used by the server to return reserved options to + // the DHCP client. Such options must be encapsulated (i.e., they must + // include suboptions). + for (auto host : ctx.hosts_) { + host.second->encapsulateOptions(); + } } ConstHostPtr @@ -3776,6 +3783,13 @@ AllocEngine::findReservation(ClientContext4& ctx) { // done here. subnet = subnet->getNextSubnet(ctx.subnet_, classes); } + + // The hosts can be used by the server to return reserved options to + // the DHCP client. Such options must be encapsulated (i.e., they must + // include suboptions). + for (auto host : ctx.hosts_) { + host.second->encapsulateOptions(); + } } ConstHostPtr diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc index faa66dbd02..e7380236eb 100644 --- a/src/lib/dhcpsrv/cfg_option.cc +++ b/src/lib/dhcpsrv/cfg_option.cc @@ -51,7 +51,8 @@ OptionDescriptor::equals(const OptionDescriptor& other) const { option_->equals(other.option_)); } -CfgOption::CfgOption() { +CfgOption::CfgOption() + : encapsulated_(false) { } bool @@ -263,6 +264,7 @@ CfgOption::encapsulate() { encapsulateInternal(DHCP4_OPTION_SPACE); // Append sub-options to the top level "dhcp6" option space. encapsulateInternal(DHCP6_OPTION_SPACE); + encapsulated_ = true; } void diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index bc17cf2eaa..a0c6d0b68d 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -543,6 +543,13 @@ public: /// options from this option space are appended to top-level options. void encapsulate(); + /// @brief Checks if options have been encapsulated. + /// + /// @return true if options have been encapsulated, false otherwise. + bool isEncapsulated() const { + return (encapsulated_); + } + /// @brief Returns all options for the specified option space. /// /// This method will not return vendor options, i.e. having option space @@ -774,6 +781,9 @@ private: OptionSpaceContainer& dest_container) const; + /// @brief A flag indicating if options have been encapsulated. + bool encapsulated_; + /// @brief Type of the container holding options grouped by option space. typedef OptionSpaceContainer OptionSpaceCollection; diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc index 1d0f855485..30ac4766a5 100644 --- a/src/lib/dhcpsrv/host.cc +++ b/src/lib/dhcpsrv/host.cc @@ -637,6 +637,16 @@ Host::toElement6() const { return (map); } +void +Host::encapsulateOptions() const { + if (!cfg_option4_->isEncapsulated()) { + cfg_option4_->encapsulate(); + } + if (!cfg_option6_->isEncapsulated()) { + cfg_option6_->encapsulate(); + } +} + std::string Host::toText() const { std::ostringstream s; diff --git a/src/lib/dhcpsrv/host.h b/src/lib/dhcpsrv/host.h index 16dc226481..7ed01d8feb 100644 --- a/src/lib/dhcpsrv/host.h +++ b/src/lib/dhcpsrv/host.h @@ -669,6 +669,12 @@ public: return (cfg_option6_); } + /// @brief Encapsulates host-specific options with their suboptions. + /// + /// This function must be called before the server returns host-specific + /// DHCP options to the client. + void encapsulateOptions() const; + /// @brief Returns information about the host in the textual format. std::string toText() const; diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc index 038b2575c2..effdc3791f 100644 --- a/src/lib/dhcpsrv/mysql_host_data_source.cc +++ b/src/lib/dhcpsrv/mysql_host_data_source.cc @@ -994,13 +994,27 @@ private: def = LibDHCP::getRuntimeOptionDef(space, code_); } + // Finish with a last resort option definition. + if (!def) { + def = LibDHCP::getLastResortOptionDef(space, code_); + } + OptionPtr option; + // If no definition found, we use generic option type. if (!def) { - // If no definition found, we use generic option type. - OptionBuffer buf(value_, value_ + value_length_); - option.reset(new Option(universe_, code_, buf.begin(), - buf.end())); + // We have to pay attention if the value is NULL. If it is, + // we must create an empty option instance. We can't rely on + // the value_length_ because it may contain garbage for the + // null values. Thus we check explicitly whether or not the + // NULL flag is set. + if (value_null_ == MLM_FALSE) { + OptionBuffer buf(value_, value_ + value_length_); + option.reset(new Option(universe_, code_, buf.begin(), + buf.end())); + } else { + option.reset(new Option(universe_, code_)); + } } else { // The option value may be specified in textual or binary format // in the database. If formatted_value is empty, the binary @@ -1008,9 +1022,12 @@ private: // variant of the optionFactory function. if (formatted_value.empty()) { OptionBuffer buf(value_, value_ + value_length_); + // Again, check if the value is null before submitting the + // buffer to the factory function. option = def->optionFactory(universe_, code_, buf.begin(), - buf.end()); - } else { + value_null_ == MLM_FALSE ? buf.end() : + buf.begin()); + } else { // Spit the value specified in comma separated values // format. std::vector split_vec; diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index 41935570dd..4671ce77bd 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -350,7 +350,8 @@ RelayInfoParser::addAddress(const std::string& name, void PoolParser::parse(PoolStoragePtr pools, ConstElementPtr pool_structure, - const uint16_t address_family) { + const uint16_t address_family, + bool encapsulate_options) { if (address_family == AF_INET) { checkKeywords(SimpleParser4::POOL4_PARAMETERS, pool_structure); @@ -484,7 +485,7 @@ PoolParser::parse(PoolStoragePtr pools, try { CfgOptionPtr cfg = pool->getCfgOption(); auto option_parser = createOptionDataListParser(address_family); - option_parser->parse(cfg, option_data); + option_parser->parse(cfg, option_data, encapsulate_options); } catch (const std::exception& ex) { isc_throw(isc::dhcp::DhcpConfigError, ex.what() << " (" << option_data->getPosition() << ")"); @@ -537,10 +538,11 @@ Pool4Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t) { //****************************** Pools4ListParser ************************* void -Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) { +Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list, + bool encapsulate_options) { BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) { auto parser = createPoolConfigParser(); - parser->parse(pools, pool, AF_INET); + parser->parse(pools, pool, AF_INET, encapsulate_options); } } @@ -555,19 +557,12 @@ Pools4ListParser::createPoolConfigParser() const { SubnetConfigParser::SubnetConfigParser(uint16_t family, bool check_iface) : pools_(new PoolStorage()), address_family_(family), - options_(new CfgOption()), check_iface_(check_iface) { relay_info_.reset(new isc::dhcp::Network::RelayInfo()); } SubnetPtr -SubnetConfigParser::parse(ConstElementPtr subnet) { - - ConstElementPtr options_params = subnet->get("option-data"); - if (options_params) { - auto opt_parser = createOptionDataListParser(); - opt_parser->parse(options_, options_params); - } +SubnetConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) { ConstElementPtr relay_params = subnet->get("relay"); if (relay_params) { @@ -584,6 +579,12 @@ SubnetConfigParser::parse(ConstElementPtr subnet) { "subnet configuration failed: " << ex.what()); } + ConstElementPtr options_params = subnet->get("option-data"); + if (options_params) { + auto opt_parser = createOptionDataListParser(); + opt_parser->parse(subnet_->getCfgOption(), options_params, encapsulate_options); + } + return (subnet_); } @@ -686,7 +687,7 @@ Subnet4ConfigParser::Subnet4ConfigParser(bool check_iface) } Subnet4Ptr -Subnet4ConfigParser::parse(ConstElementPtr subnet) { +Subnet4ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) { // Check parameters. checkKeywords(SimpleParser4::SUBNET4_PARAMETERS, subnet); @@ -694,10 +695,10 @@ Subnet4ConfigParser::parse(ConstElementPtr subnet) { ConstElementPtr pools = subnet->get("pools"); if (pools) { auto parser = createPoolsListParser(); - parser->parse(pools_, pools); + parser->parse(pools_, pools, encapsulate_options); } - SubnetPtr generic = SubnetConfigParser::parse(subnet); + SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options); if (!generic) { // Sanity check: not supposed to fail. @@ -945,9 +946,6 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, // options but this is no longer the case (they have a different // and not consecutive priority). - // Copy options to the subnet configuration. - options_->copyTo(*subnet4->getCfgOption()); - // Parse t1-percent and t2-percent parseTeePercents(params, network); @@ -993,12 +991,13 @@ Subnets4ListConfigParser::Subnets4ListConfigParser(bool check_iface) size_t Subnets4ListConfigParser::parse(SrvConfigPtr cfg, - ConstElementPtr subnets_list) { + ConstElementPtr subnets_list, + bool encapsulate_options) { size_t cnt = 0; BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { auto parser = createSubnetConfigParser(); - Subnet4Ptr subnet = parser->parse(subnet_json); + Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options); if (subnet) { // Adding a subnet to the Configuration Manager may fail if the @@ -1018,12 +1017,13 @@ Subnets4ListConfigParser::parse(SrvConfigPtr cfg, size_t Subnets4ListConfigParser::parse(Subnet4Collection& subnets, - data::ConstElementPtr subnets_list) { + data::ConstElementPtr subnets_list, + bool encapsulate_options) { size_t cnt = 0; BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { auto parser = createSubnetConfigParser(); - Subnet4Ptr subnet = parser->parse(subnet_json); + Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options); if (subnet) { try { auto ret = subnets.insert(subnet); @@ -1067,10 +1067,11 @@ Pool6Parser::poolMaker(IOAddress &min, IOAddress &max, int32_t ptype) //**************************** Pool6ListParser *************************** void -Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list) { +Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list, + bool encapsulate_options) { BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) { auto parser = createPoolConfigParser(); - parser->parse(pools, pool, AF_INET6); + parser->parse(pools, pool, AF_INET6, encapsulate_options); } } @@ -1082,11 +1083,12 @@ Pools6ListParser::createPoolConfigParser() const { //**************************** PdPoolParser ****************************** -PdPoolParser::PdPoolParser() : options_(new CfgOption()) { +PdPoolParser::PdPoolParser() { } void -PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { +PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_, + bool encapsulate_options) { checkKeywords(SimpleParser6::PD_POOL6_PARAMETERS, pd_pool_); std::string addr_str = getString(pd_pool_, "prefix"); @@ -1105,12 +1107,6 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { excluded_prefix_len = getUint8(pd_pool_, "excluded-prefix-len"); } - ConstElementPtr option_data = pd_pool_->get("option-data"); - if (option_data) { - auto opts_parser = createOptionDataListParser(); - opts_parser->parse(options_, option_data); - } - ConstElementPtr user_context = pd_pool_->get("user-context"); if (user_context) { user_context_ = user_context; @@ -1132,8 +1128,6 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { delegated_len, IOAddress(excluded_prefix_str), excluded_prefix_len)); - // Merge options specified for a pool into pool configuration. - options_->copyTo(*pool_->getCfgOption()); } catch (const std::exception& ex) { // Some parameters don't exist or are invalid. Since we are not // aware whether they don't exist or are invalid, let's append @@ -1142,6 +1136,12 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) { << " (" << pd_pool_->getPosition() << ")"); } + ConstElementPtr option_data = pd_pool_->get("option-data"); + if (option_data) { + auto opts_parser = createOptionDataListParser(); + opts_parser->parse(pool_->getCfgOption(), option_data, encapsulate_options); + } + if (user_context_) { pool_->setContext(user_context_); } @@ -1200,7 +1200,7 @@ Subnet6ConfigParser::Subnet6ConfigParser(bool check_iface) } Subnet6Ptr -Subnet6ConfigParser::parse(ConstElementPtr subnet) { +Subnet6ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) { // Check parameters. checkKeywords(SimpleParser6::SUBNET6_PARAMETERS, subnet); @@ -1208,7 +1208,7 @@ Subnet6ConfigParser::parse(ConstElementPtr subnet) { ConstElementPtr pools = subnet->get("pools"); if (pools) { auto parser = createPoolsListParser(); - parser->parse(pools_, pools); + parser->parse(pools_, pools, encapsulate_options); } ConstElementPtr pd_pools = subnet->get("pd-pools"); if (pd_pools) { @@ -1216,7 +1216,7 @@ Subnet6ConfigParser::parse(ConstElementPtr subnet) { parser->parse(pools_, pd_pools); } - SubnetPtr generic = SubnetConfigParser::parse(subnet); + SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options); if (!generic) { // Sanity check: not supposed to fail. @@ -1420,9 +1420,6 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, /// client-class processing is now generic and handled in the common /// code (see isc::data::SubnetConfigParser::createSubnet) - // Copy options to the subnet configuration. - options_->copyTo(*subnet6->getCfgOption()); - // Parse t1-percent and t2-percent parseTeePercents(params, network); @@ -1466,12 +1463,13 @@ Subnets6ListConfigParser::Subnets6ListConfigParser(bool check_iface) size_t Subnets6ListConfigParser::parse(SrvConfigPtr cfg, - ConstElementPtr subnets_list) { + ConstElementPtr subnets_list, + bool encapsulate_options) { size_t cnt = 0; BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { auto parser = createSubnetConfigParser(); - Subnet6Ptr subnet = parser->parse(subnet_json); + Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options); // Adding a subnet to the Configuration Manager may fail if the // subnet id is invalid (duplicate). Thus, we catch exceptions @@ -1489,12 +1487,13 @@ Subnets6ListConfigParser::parse(SrvConfigPtr cfg, size_t Subnets6ListConfigParser::parse(Subnet6Collection& subnets, - ConstElementPtr subnets_list) { + ConstElementPtr subnets_list, + bool encapsulate_options) { size_t cnt = 0; BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) { auto parser = createSubnetConfigParser(); - Subnet6Ptr subnet = parser->parse(subnet_json); + Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options); if (subnet) { try { auto ret = subnets.insert(subnet); diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h index 808b5b3c4b..b41653907d 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h @@ -302,10 +302,13 @@ public: /// @param pools is the storage in which to store the parsed pool /// @param pool_structure a single entry on a list of pools /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6). + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @throw isc::dhcp::DhcpConfigError when pool parsing fails virtual void parse(PoolStoragePtr pools, isc::data::ConstElementPtr pool_structure, - const uint16_t address_family); + const uint16_t address_family, + bool encapsulate_options = true); protected: /// @brief Creates a Pool object given a IPv4 prefix and the prefix length. @@ -389,9 +392,12 @@ public: /// /// @param pools is the storage in which to store the parsed pools. /// @param pools_list a list of pool structures + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @throw isc::dhcp::DhcpConfigError when pool parsing fails virtual void parse(PoolStoragePtr pools, - isc::data::ConstElementPtr pools_list) = 0; + isc::data::ConstElementPtr pools_list, + bool encapsulate_options) = 0; protected: @@ -415,8 +421,11 @@ public: /// /// @param pools storage container in which to store the parsed pool. /// @param pools_list a list of pool structures + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @throw isc::dhcp::DhcpConfigError when pool parsing fails - void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list); + void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list, + bool encapsulate_options = true); protected: @@ -520,10 +529,13 @@ protected: /// Subnet6ConfigParser) classes. /// /// @param subnet pointer to the content of subnet definition + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @return a pointer to newly created subnet /// /// @throw isc::DhcpConfigError if subnet configuration parsing failed. - SubnetPtr parse(isc::data::ConstElementPtr subnet); + SubnetPtr parse(isc::data::ConstElementPtr subnet, + bool encapsulate_options); /// @brief Instantiates the subnet based on a given IP prefix and prefix /// length. @@ -574,9 +586,6 @@ protected: /// Pointer to relay information isc::dhcp::Network::RelayInfoPtr relay_info_; - /// Pointer to the options configuration. - CfgOptionPtr options_; - /// Check if the specified interface exists in the system. bool check_iface_; }; @@ -601,8 +610,11 @@ public: /// Configuration Manager. /// /// @param subnet A new subnet being configured. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @return a pointer to created Subnet4 object - Subnet4Ptr parse(data::ConstElementPtr subnet); + Subnet4Ptr parse(data::ConstElementPtr subnet, + bool encapsulate_options = true); protected: @@ -659,16 +671,22 @@ public: /// /// @param cfg Pointer to server configuration. /// @param subnets_list pointer to a list of IPv4 subnets + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @return number of subnets created - size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list); + size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list, + bool encapsulate_options = true); /// @brief Parses contents of the subnet4 list. /// /// @param [out] subnets Container where parsed subnets will be stored. /// @param subnets_list pointer to a list of IPv4 subnets + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @return Number of subnets created. size_t parse(Subnet4Collection& subnets, - data::ConstElementPtr subnets_list); + data::ConstElementPtr subnets_list, + bool encapsulate_options = true); protected: @@ -727,8 +745,11 @@ public: /// /// @param pools storage container in which to store the parsed pool. /// @param pools_list a list of pool structures + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @throw isc::dhcp::DhcpConfigError when pool parsing fails - void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list); + void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list, + bool encapsulate_options = true); protected: @@ -778,9 +799,12 @@ public: /// @param pools storage container in which to store the parsed pool. /// @param pd_pool_ pointer to an element that holds configuration entries /// that define a prefix delegation pool. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @throw DhcpConfigError if configuration parsing fails. - void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_); + void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_, + bool encapsulate_options = true); protected: @@ -797,9 +821,6 @@ protected: /// Pointer to the created pool object. isc::dhcp::Pool6Ptr pool_; - /// A storage for pool specific option values. - CfgOptionPtr options_; - /// @brief User context (optional, may be null) /// /// User context is arbitrary user data, to be used by hooks. @@ -869,8 +890,11 @@ public: /// Configuration Manager. /// /// @param subnet A new subnet being configured. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @return a pointer to created Subnet6 object - Subnet6Ptr parse(data::ConstElementPtr subnet); + Subnet6Ptr parse(data::ConstElementPtr subnet, + bool encapsulate_options = true); protected: /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet @@ -947,8 +971,11 @@ public: /// /// @param cfg configuration (parsed subnets will be stored here) /// @param subnets_list pointer to a list of IPv6 subnets + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// @throw DhcpConfigError if CfgMgr rejects the subnet (e.g. subnet-id is a duplicate) - size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list); + size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list, + bool encapsulate_options = true); /// @brief Parses contents of the subnet6 list. /// @@ -956,7 +983,8 @@ public: /// @param subnets_list pointer to a list of IPv6 subnets /// @return Number of subnets created. size_t parse(Subnet6Collection& subnets, - data::ConstElementPtr subnets_list); + data::ConstElementPtr subnets_list, + bool encapsulate_options = true); protected: diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc index 2be4643971..751ca094d2 100644 --- a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc +++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc @@ -101,13 +101,15 @@ namespace dhcp { HostPtr HostReservationParser::parse(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data) { - return (parseInternal(subnet_id, reservation_data)); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options) { + return (parseInternal(subnet_id, reservation_data, encapsulate_options)); } HostPtr HostReservationParser::parseInternal(const SubnetID&, - isc::data::ConstElementPtr reservation_data) { + isc::data::ConstElementPtr reservation_data, + bool) { std::string identifier; std::string identifier_name; std::string hostname; @@ -187,8 +189,10 @@ HostReservationParser::isSupportedParameter(const std::string& param_name) const HostPtr HostReservationParser4::parseInternal(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data) { - HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options) { + HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data, + encapsulate_options); host->setIPv4SubnetID(subnet_id); @@ -203,7 +207,7 @@ HostReservationParser4::parseInternal(const SubnetID& subnet_id, // parses the Element structure immediately, there's no need // to go through build/commit phases. OptionDataListParser parser(AF_INET); - parser.parse(cfg_option, element.second); + parser.parse(cfg_option, element.second, encapsulate_options); // Everything else should be surrounded with try-catch to append // position. @@ -246,8 +250,10 @@ HostReservationParser4::getSupportedParameters(const bool identifiers_only) cons HostPtr HostReservationParser6::parseInternal(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data) { - HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options) { + HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data, + encapsulate_options); host->setIPv6SubnetID(subnet_id); @@ -263,7 +269,7 @@ HostReservationParser6::parseInternal(const SubnetID& subnet_id, // parses the Element structure immediately, there's no need // to go through build/commit phases. OptionDataListParser parser(AF_INET6); - parser.parse(cfg_option, element.second); + parser.parse(cfg_option, element.second, encapsulate_options); } else if (element.first == "ip-addresses" || element.first == "prefixes") { BOOST_FOREACH(ConstElementPtr prefix_element, diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.h b/src/lib/dhcpsrv/parsers/host_reservation_parser.h index b6542c4af7..e4d4fd65f5 100644 --- a/src/lib/dhcpsrv/parsers/host_reservation_parser.h +++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.h @@ -27,12 +27,15 @@ public: /// connected to. /// @param reservation_data Data element holding map with a host /// reservation configuration. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to the object representing parsed host. /// @throw DhcpConfigError If the configuration is invalid. virtual HostPtr parse(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data) final; + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options = true) final; protected: @@ -45,11 +48,14 @@ protected: /// connected to. /// @param reservation_data Data element holding map with a host /// reservation configuration. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to the object representing parsed host. /// @throw DhcpConfigError If the configuration is invalid. virtual HostPtr parseInternal(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options); /// @brief Checks if the specified parameter is a host identifier. /// @@ -87,11 +93,14 @@ protected: /// connected to. /// @param reservation_data Data element holding map with a host /// reservation configuration. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to the object representing parsed host. /// @throw DhcpConfigError If the configuration is invalid. virtual HostPtr parseInternal(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options); /// @brief Returns set of the supported parameters for DHCPv4. /// @@ -114,11 +123,14 @@ protected: /// connected to. /// @param reservation_data Data element holding map with a host /// reservation configuration. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to the object representing parsed host. /// @throw DhcpConfigError If the configuration is invalid. virtual HostPtr parseInternal(const SubnetID& subnet_id, - isc::data::ConstElementPtr reservation_data); + isc::data::ConstElementPtr reservation_data, + bool encapsulate_options); /// @brief Returns set of the supported parameters for DHCPv6. /// diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc index c75e6b145e..6a321cde6d 100644 --- a/src/lib/dhcpsrv/parsers/option_data_parser.cc +++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc @@ -455,14 +455,17 @@ OptionDataListParser::OptionDataListParser(//const std::string&, void OptionDataListParser::parse(const CfgOptionPtr& cfg, - isc::data::ConstElementPtr option_data_list) { + isc::data::ConstElementPtr option_data_list, + bool encapsulate) { auto option_parser = createOptionDataParser(); BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) { std::pair option = option_parser->parse(data); // Use the option description to keep the formatted value cfg->add(option.first, option.second); - cfg->encapsulate(); + if (encapsulate) { + cfg->encapsulate(); + } } } diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.h b/src/lib/dhcpsrv/parsers/option_data_parser.h index ad8ed9afda..f6b44b3579 100644 --- a/src/lib/dhcpsrv/parsers/option_data_parser.h +++ b/src/lib/dhcpsrv/parsers/option_data_parser.h @@ -209,8 +209,12 @@ public: /// /// @param cfg created options will be stored here /// @param option_data_list configuration that describes the options + /// @param encapsulate a boolean value indicating whether or not the + /// parser should encapsulate options with suboptions. The default + /// value is true (encapsulate). void parse(const CfgOptionPtr& cfg, - isc::data::ConstElementPtr option_data_list); + isc::data::ConstElementPtr option_data_list, + bool encapsulate = true); protected: /// @brief Returns an instance of the @c OptionDataListParser to diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc index 1d6d73c66c..ef9cc1dc00 100644 --- a/src/lib/dhcpsrv/parsers/shared_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -31,7 +31,8 @@ SharedNetwork4Parser::SharedNetwork4Parser(bool check_iface) } SharedNetwork4Ptr -SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) { +SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data, + bool encapsulate_options) { SharedNetwork4Ptr shared_network; try { @@ -75,7 +76,7 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) { // Create parser instance for option-data. CfgOptionPtr cfg_option = shared_network->getCfgOption(); auto parser = createOptionDataListParser(); - parser->parse(cfg_option, json); + parser->parse(cfg_option, json, encapsulate_options); } if (shared_network_data->contains("subnet4")) { @@ -240,7 +241,8 @@ SharedNetwork6Parser::SharedNetwork6Parser(bool check_iface) } SharedNetwork6Ptr -SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) { +SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data, + bool encapsulate_options) { SharedNetwork6Ptr shared_network; std::string name; try { @@ -324,7 +326,7 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) { // Create parser instance for option-data. CfgOptionPtr cfg_option = shared_network->getCfgOption(); auto parser = createOptionDataListParser(); - parser->parse(cfg_option, json); + parser->parse(cfg_option, json, encapsulate_options); } if (shared_network_data->contains("client-class")) { diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.h b/src/lib/dhcpsrv/parsers/shared_network_parser.h index 8b4ea01ec3..63e2e54876 100644 --- a/src/lib/dhcpsrv/parsers/shared_network_parser.h +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.h @@ -37,11 +37,14 @@ public: /// /// @param shared_network_data Data element holding shared network /// configuration to be parsed. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to an object representing shared network. /// @throw DhcpConfigError when shared network configuration is invalid. SharedNetwork4Ptr - parse(const data::ConstElementPtr& shared_network_data); + parse(const data::ConstElementPtr& shared_network_data, + bool encapsulate_options = true); protected: @@ -84,11 +87,14 @@ public: /// /// @param shared_network_data Data element holding shared network /// configuration to be parsed. + /// @param encapsulate_options a boolean parameter indicating if the + /// parsed options should be encapsulated with suboptions. /// /// @return Pointer to an object representing shared network. /// @throw DhcpConfigError when shared network configuration is invalid. SharedNetwork6Ptr - parse(const data::ConstElementPtr& shared_network_data); + parse(const data::ConstElementPtr& shared_network_data, + bool encapsulate_options = true); protected: diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc index f9f6df11d8..b9cea846c9 100644 --- a/src/lib/dhcpsrv/pgsql_host_data_source.cc +++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc @@ -651,6 +651,11 @@ private: def = LibDHCP::getRuntimeOptionDef(space, code); } + // Finish with a last resort option definition. + if (!def) { + def = LibDHCP::getLastResortOptionDef(space, code); + } + OptionPtr option; if (!def) { diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc index 0333818725..ab3c1de824 100644 --- a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc @@ -694,11 +694,15 @@ TEST_F(CfgOptionTest, encapsulate) { generateEncapsulatedOptions(cfg); + EXPECT_FALSE(cfg.isEncapsulated()); + // Append options from "foo" and "bar" space as sub-options and options // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar" // options. ASSERT_NO_THROW(cfg.encapsulate()); + EXPECT_TRUE(cfg.isEncapsulated()); + // Verify that we have 40 top-level options. OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE); ASSERT_EQ(40, options->size()); diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc index 690c6d2974..be8c674448 100644 --- a/src/lib/dhcpsrv/tests/host_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_unittest.cc @@ -951,6 +951,29 @@ TEST_F(HostTest, addOptions4) { EXPECT_TRUE(options->empty()); } +// This test checks that host-specific DHCPv4 options can be encapsulated. +TEST_F(HostTest, encapsulateOptions4) { + Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2), + IOAddress("192.0.2.3")); + + OptionPtr option43(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS)); + option43->setEncapsulatedSpace(VENDOR_ENCAPSULATED_OPTION_SPACE); + ASSERT_NO_THROW(host.getCfgOption4()->add(option43, false, false, DHCP4_OPTION_SPACE)); + + OptionPtr option1(new Option(Option::V4, 1)); + ASSERT_NO_THROW(host.getCfgOption4()->add(option1, false, false, + VENDOR_ENCAPSULATED_OPTION_SPACE)); + + ASSERT_NO_THROW(host.encapsulateOptions()); + + auto returned_option43 = host.getCfgOption4()->get(DHCP4_OPTION_SPACE, + DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(returned_option43.option_); + + auto returned_option1 = returned_option43.option_->getOption(1); + ASSERT_TRUE(returned_option1); +} + // This test checks that it is possible to add DHCPv6 options for a host. TEST_F(HostTest, addOptions6) { Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2), @@ -1016,6 +1039,29 @@ TEST_F(HostTest, addOptions6) { EXPECT_TRUE(options->empty()); } +// This test checks that it is possible to add DHCPv6 options for a host. +TEST_F(HostTest, encapsulateOptions6) { + Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2), + IOAddress("192.0.2.3")); + + OptionPtr option94(new Option(Option::V6, D6O_S46_CONT_MAPE)); + option94->setEncapsulatedSpace(MAPE_V6_OPTION_SPACE); + ASSERT_NO_THROW(host.getCfgOption6()->add(option94, false, false, DHCP6_OPTION_SPACE)); + + OptionPtr option1(new Option(Option::V6, 1)); + ASSERT_NO_THROW(host.getCfgOption6()->add(option1, false, false, + MAPE_V6_OPTION_SPACE)); + + ASSERT_NO_THROW(host.encapsulateOptions()); + + auto returned_option94 = host.getCfgOption6()->get(DHCP6_OPTION_SPACE, + D6O_S46_CONT_MAPE); + ASSERT_TRUE(returned_option94.option_); + + auto returned_option1 = returned_option94.option_->getOption(1); + ASSERT_TRUE(returned_option1); +} + // This test verifies that it is possible to retrieve a textual // representation of the host identifier. TEST_F(HostTest, getIdentifierAsText) { diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc index e466dffcd2..df03d26398 100644 --- a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc +++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc @@ -128,7 +128,7 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host, DHCP4_OPTION_SPACE); opts->add(createOption(Option::V4, 1, false, false, formatted, 312131), - "vendor-encapsulated-options"); + "vendor-encapsulated-options-space"); opts->add(createAddressOption(254, false, false, formatted, "192.0.2.3"), DHCP4_OPTION_SPACE); @@ -137,11 +137,17 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host, formatted, "10.0.0.5", "10.0.0.3", "10.0.3.4"), "isc"); + auto def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, + DHO_VENDOR_ENCAPSULATED_OPTIONS); + opts->add(OptionDescriptor(def->optionFactory(Option::V4, + DHO_VENDOR_ENCAPSULATED_OPTIONS, + OptionBuffer()), + true, false), DHCP4_OPTION_SPACE); // Add definitions for DHCPv4 non-standard options. defs.addItem(OptionDefinitionPtr(new OptionDefinition( "vendor-encapsulated-1", 1, - "vendor-encapsulated-options", "uint32"))); + "vendor-encapsulated-options-space", "uint32"))); defs.addItem(OptionDefinitionPtr(new OptionDefinition( "option-254", 254, DHCP4_OPTION_SPACE, "ipv4-address", true))); @@ -2047,6 +2053,18 @@ GenericHostDataSourceTest::testOptionsReservations4(const bool formatted, ASSERT_EQ(1, hosts_by_subnet.size()); ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_subnet.begin())); + auto returned_host = *hosts_by_subnet.begin(); + ASSERT_NO_THROW(returned_host->encapsulateOptions()); + auto cfg_option = returned_host->getCfgOption4(); + + auto option43 = cfg_option->get(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS); + ASSERT_TRUE(option43.option_); + + EXPECT_TRUE(cfg_option->get("vendor-encapsulated-options-space", 1).option_); + + auto option43_1 = option43.option_->getOption(1); + EXPECT_TRUE(option43_1); + // getAll4(address) ConstHostCollection hosts_by_addr = hdsptr_->getAll4(host->getIPv4Reservation());