]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2826] Options in db are not encapsulated
authorMarcin Siodelski <marcin@isc.org>
Mon, 26 Jun 2023 10:09:23 +0000 (12:09 +0200)
committerMarcin Siodelski <marcin@isc.org>
Thu, 29 Jun 2023 19:00:51 +0000 (21:00 +0200)
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.

18 files changed:
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/cfg_option.cc
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/host.cc
src/lib/dhcpsrv/host.h
src/lib/dhcpsrv/mysql_host_data_source.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.h
src/lib/dhcpsrv/parsers/host_reservation_parser.cc
src/lib/dhcpsrv/parsers/host_reservation_parser.h
src/lib/dhcpsrv/parsers/option_data_parser.cc
src/lib/dhcpsrv/parsers/option_data_parser.h
src/lib/dhcpsrv/parsers/shared_network_parser.cc
src/lib/dhcpsrv/parsers/shared_network_parser.h
src/lib/dhcpsrv/pgsql_host_data_source.cc
src/lib/dhcpsrv/tests/cfg_option_unittest.cc
src/lib/dhcpsrv/tests/host_unittest.cc
src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc

index c96a34317cd91cebb0f5f2b2d80a410ecff29d55..e7b1d826327d34001f05b1004c4bbe3777d26ab6 100644 (file)
@@ -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
index faa66dbd02f60907cc6c2bdbc3e339ce2f2d6ee5..e7380236eb3eff54b133f9e1b5713bc8be3ef855 100644 (file)
@@ -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
index bc17cf2eaa6a84564533f82070bedeb79139f425..a0c6d0b68d3f3a8bbe1c1d840e974660f3165759 100644 (file)
@@ -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<OptionContainer,
                        OptionDescriptor, Selector>& 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<OptionContainer, OptionDescriptor,
                                  std::string> OptionSpaceCollection;
index 1d0f85548536454a0fd80a11ac0685f39963435f..30ac4766a55396e62193e93ac836a2672ee0bc7a 100644 (file)
@@ -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;
index 16dc2264818517dc1e88796e9d9bfa14917912f4..7ed01d8feba966868f864d0616940ca1a1fc3a3b 100644 (file)
@@ -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;
 
index 038b2575c2774a994fcd7fd4ec261afe7b6769f8..effdc3791fdc368d8e5ced064dd594f00a527d5a 100644 (file)
@@ -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<std::string> split_vec;
index 41935570ddae0a1cae2ea09eeaed0a9bfbe638cf..4671ce77bd1593e1f35063c6bcec828395fe1314 100644 (file)
@@ -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);
index 808b5b3c4b121ce2caaf44817be0a32463edfa57..b41653907dab311bb1b7759722ea487a32b248a2 100644 (file)
@@ -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:
 
index 2be464397155a9c873504fc53e88984f5dc652f9..751ca094d2e169afe57ee86c902f0dd84e16060c 100644 (file)
@@ -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,
index b6542c4af7c7f13cad3291e235476202ff24ce2b..e4d4fd65f5507684edf9568a96b124f3c40451bc 100644 (file)
@@ -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.
     ///
index c75e6b145eeeac25580563f3b89fe2c0901e77fc..6a321cde6daba9d0dfe6dce9c7edb9b9cddb4bcd 100644 (file)
@@ -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<OptionDescriptor, std::string> 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();
+        }
     }
 }
 
index ad8ed9afda0b51d581b3802d399e505c30e9fcee..f6b44b35794c30a88dabd3d0a9ab3c859ed04c90 100644 (file)
@@ -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
index 1d6d73c66ce35e484e79530c2bde58d821e92f66..ef9cc1dc008a72951ea996d0a809cfab4254eeed 100644 (file)
@@ -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")) {
index 8b4ea01ec30d44da9953a84adc314038ca24059e..63e2e548767f7f1e378d39ac07895a0d46c3ebe0 100644 (file)
@@ -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:
 
index f9f6df11d8ad5dc69a0a41a52c6293e6f05b8a01..b9cea846c9d75ef4d5ffe95652eef83a7e6ef9cd 100644 (file)
@@ -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) {
index 0333818725e9cc7a5ff80f5753ebbd5c246c69ab..ab3c1de824eadb787ded9f3c7c0fc6e61c5dd973 100644 (file)
@@ -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());
index 690c6d2974073deaf2b0ab9c8c8daff60e9f4526..be8c674448a3f6d9240ed9fd1099115aaa9a8b2e 100644 (file)
@@ -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) {
index e466dffcd22dc210e3681e2f66ad986e89cc7101..df03d26398a0da9e79fe7354d3ebf6a02e8036b3 100644 (file)
@@ -128,7 +128,7 @@ GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
                   DHCP4_OPTION_SPACE);
         opts->add(createOption<OptionUint32>(Option::V4, 1, false, false,
                                              formatted, 312131),
-                  "vendor-encapsulated-options");
+                  "vendor-encapsulated-options-space");
         opts->add(createAddressOption<Option4AddrLst>(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());