summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/flex_option/flex_option.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks/dhcp/flex_option/flex_option.h')
-rw-r--r--src/hooks/dhcp/flex_option/flex_option.h649
1 files changed, 649 insertions, 0 deletions
diff --git a/src/hooks/dhcp/flex_option/flex_option.h b/src/hooks/dhcp/flex_option/flex_option.h
new file mode 100644
index 0000000..bda8ca1
--- /dev/null
+++ b/src/hooks/dhcp/flex_option/flex_option.h
@@ -0,0 +1,649 @@
+// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef FLEX_OPTION_H
+#define FLEX_OPTION_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcp/classify.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/std_option_defs.h>
+#include <eval/evaluate.h>
+#include <eval/token.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace flex_option {
+
+/// @brief Flex Option implementation.
+///
+/// The implementation can be divided into two parts:
+/// - the configuration parsed and stored by load()
+/// - the response packet processing performed by the process method
+///
+class FlexOptionImpl {
+public:
+
+ /// @brief Action.
+ ///
+ /// Currently supported actions are:
+ /// - add (if not already existing)
+ /// - supersede (as add but also when already existing)
+ /// - remove
+ enum Action {
+ NONE,
+ ADD,
+ SUPERSEDE,
+ REMOVE
+ };
+
+ /// @brief Base option configuration.
+ ///
+ /// Per option configuration.
+ class OptionConfig {
+ public:
+ /// @brief Constructor.
+ ///
+ /// @param code the option code.
+ /// @param def the option definition.
+ OptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def);
+
+ /// @brief Destructor.
+ virtual ~OptionConfig();
+
+ /// @brief Return option code.
+ ///
+ /// @return option code.
+ uint16_t getCode() const {
+ return (code_);
+ }
+
+ /// @brief Return option definition.
+ ///
+ /// @return option definition.
+ isc::dhcp::OptionDefinitionPtr getOptionDef() const {
+ return (def_);
+ }
+
+ /// @brief Set action.
+ ///
+ /// @param action the action.
+ void setAction(Action action) {
+ action_ = action;
+ }
+
+ /// @brief Return action.
+ ///
+ /// @return action.
+ Action getAction() const {
+ return (action_);
+ }
+
+ /// @brief Set textual expression.
+ ///
+ /// @param text the textual expression.
+ void setText(const std::string& text) {
+ text_ = text;
+ };
+
+ /// @brief Get textual expression.
+ ///
+ /// @return textual expression.
+ const std::string& getText() const {
+ return (text_);
+ }
+
+ /// @brief Set match expression.
+ ///
+ /// @param expr the match expression.
+ void setExpr(const isc::dhcp::ExpressionPtr& expr) {
+ expr_ = expr;
+ }
+
+ /// @brief Get match expression.
+ ///
+ /// @return the match expression.
+ const isc::dhcp::ExpressionPtr& getExpr() const {
+ return (expr_);
+ }
+
+ /// @brief Set client class.
+ ///
+ /// @param class_name the client class aka guard name.
+ void setClass(const isc::dhcp::ClientClass& class_name) {
+ class_ = class_name;
+ }
+
+ /// @brief Get client class.
+ ///
+ /// @return client class aka guard name.
+ const isc::dhcp::ClientClass& getClass() const {
+ return (class_);
+ }
+
+ private:
+ /// @brief The code.
+ uint16_t code_;
+
+ /// @brief The option definition.
+ /// @note This value is set only when csv-format is true.
+ isc::dhcp::OptionDefinitionPtr def_;
+
+ /// @brief The action.
+ Action action_;
+
+ /// @brief The textual expression.
+ std::string text_;
+
+ /// @brief The match expression.
+ isc::dhcp::ExpressionPtr expr_;
+
+ /// @brief The client class aka guard name.
+ isc::dhcp::ClientClass class_;
+ };
+
+ /// @brief The type of shared pointers to option config.
+ typedef boost::shared_ptr<OptionConfig> OptionConfigPtr;
+
+ /// @brief The type of lists of shared pointers to option config.
+ typedef std::list<OptionConfigPtr> OptionConfigList;
+
+ /// @brief The type of the option config map.
+ typedef std::map<uint16_t, OptionConfigList> OptionConfigMap;
+
+ /// @brief Sub-option configuration.
+ ///
+ /// Per sub-option configuration.
+ class SubOptionConfig : public OptionConfig {
+ public:
+ /// @brief Constructor.
+ ///
+ /// @param code the sub-option code.
+ /// @param def the sub-option definition.
+ /// @param container pointer to the container option.
+ SubOptionConfig(uint16_t code, isc::dhcp::OptionDefinitionPtr def,
+ OptionConfigPtr container);
+
+ /// @brief Destructor.
+ virtual ~SubOptionConfig();
+
+ /// @brief Set vendor id.
+ ///
+ /// @param vendor_id the vendor id.
+ void setVendorId(uint32_t vendor_id) {
+ vendor_id_ = vendor_id;
+ }
+
+ /// @brief Return vendor id.
+ ///
+ /// @return vendor id.
+ uint32_t getVendorId() const {
+ return (vendor_id_);
+ }
+
+ /// @brief Return container code.
+ ///
+ /// @return container code.
+ uint16_t getContainerCode() const {
+ return (container_->getCode());
+ }
+
+ /// @brief Return container definition.
+ ///
+ /// @return container definition.
+ isc::dhcp::OptionDefinitionPtr getContainerDef() const {
+ return (container_->getOptionDef());
+ }
+
+ /// @brief Return container client class.
+ ///
+ /// @return container client class name.
+ const isc::dhcp::ClientClass& getContainerClass() const {
+ return (container_->getClass());
+ }
+
+ /// @brief Set action on the container.
+ ///
+ /// @param action the action.
+ void setContainerAction(Action action) {
+ container_action_ = action;
+ }
+
+ /// @brief Return action on the container.
+ ///
+ /// @return action on the container.
+ Action getContainerAction() const {
+ return (container_action_);
+ }
+
+ private:
+ /// @brief Pointer to the container option configuration.
+ OptionConfigPtr container_;
+
+ /// @brief Vendor id (0 when the container is not a vendor one).
+ uint32_t vendor_id_;
+
+ /// @brief The action on the container.
+ Action container_action_;
+ };
+
+ /// @brief The type of shared pointers to sub-option config.
+ typedef boost::shared_ptr<SubOptionConfig> SubOptionConfigPtr;
+
+ /// @brief The type of the sub-option config map.
+ /// @note the index is the sub-option code.
+ typedef std::map<uint16_t, SubOptionConfigPtr> SubOptionConfigMap;
+
+ /// @brief The type of the map of sub-option config maps.
+ /// @note the index is the container option code.
+ typedef std::map<uint16_t, SubOptionConfigMap> SubOptionConfigMapMap;
+
+ /// @brief Constructor.
+ FlexOptionImpl();
+
+ /// @brief Destructor.
+ ~FlexOptionImpl();
+
+ /// @brief Get the option config map.
+ ///
+ /// @return The option config map.
+ const OptionConfigMap& getOptionConfigMap() const {
+ return (option_config_map_);
+ }
+
+ /// @brief Get the sub-option config map of maps.
+ ///
+ /// @return The sub-option config map of maps.
+ const SubOptionConfigMapMap& getSubOptionConfigMap() const {
+ return (sub_option_config_map_);
+ }
+
+ /// @brief Configure the Flex Option implementation.
+ ///
+ /// @param options The element with option config list.
+ /// @throw BadValue and similar exceptions on error.
+ void configure(isc::data::ConstElementPtr options);
+
+ /// @brief Process a query / response pair.
+ ///
+ /// @tparam PktType The type of pointers to packets: Pkt4Ptr or Pkt6Ptr.
+ /// @param universe The option universe: Option::V4 or Option::V6.
+ /// @param query The query packet.
+ /// @param response The response packet.
+ template <typename PktType>
+ void process(isc::dhcp::Option::Universe universe,
+ PktType query, PktType response) {
+ for (auto pair : getOptionConfigMap()) {
+ for (const OptionConfigPtr& opt_cfg : pair.second) {
+ const isc::dhcp::ClientClass& client_class =
+ opt_cfg->getClass();
+ if (!client_class.empty()) {
+ if (!query->inClass(client_class)) {
+ logClass(client_class, opt_cfg->getCode());
+ continue;
+ }
+ }
+ std::string value;
+ isc::dhcp::OptionBuffer buffer;
+ uint16_t code = opt_cfg->getCode();
+ isc::dhcp::OptionPtr opt = response->getOption(code);
+ isc::dhcp::OptionDefinitionPtr def = opt_cfg->getOptionDef();
+ switch (opt_cfg->getAction()) {
+ case NONE:
+ break;
+ case ADD:
+ // Don't add if option is already there.
+ if (opt) {
+ break;
+ }
+ // Do nothing is the expression evaluates to empty.
+ value = isc::dhcp::evaluateString(*opt_cfg->getExpr(),
+ *query);
+ if (value.empty()) {
+ break;
+ }
+ // Set the value.
+ if (def) {
+ std::vector<std::string> split_vec =
+ isc::util::str::tokens(value, ",", true);
+ opt = def->optionFactory(universe, code, split_vec);
+ } else {
+ buffer.assign(value.begin(), value.end());
+ opt.reset(new isc::dhcp::Option(universe, code,
+ buffer));
+ }
+ // Add the option.
+ response->addOption(opt);
+ logAction(ADD, code, value);
+ break;
+ case SUPERSEDE:
+ // Do nothing is the expression evaluates to empty.
+ value = isc::dhcp::evaluateString(*opt_cfg->getExpr(),
+ *query);
+ if (value.empty()) {
+ break;
+ }
+ // Set the value.
+ if (def) {
+ std::vector<std::string> split_vec =
+ isc::util::str::tokens(value, ",", true);
+ opt = def->optionFactory(universe, code,
+ split_vec);
+ } else {
+ buffer.assign(value.begin(), value.end());
+ opt.reset(new isc::dhcp::Option(universe,
+ code,
+ buffer));
+ }
+ // Remove the option if already there.
+ while (response->getOption(code)) {
+ response->delOption(code);
+ }
+ // Add the option.
+ response->addOption(opt);
+ logAction(SUPERSEDE, code, value);
+ break;
+ case REMOVE:
+ // Nothing to remove if option is not present.
+ if (!opt) {
+ break;
+ }
+ // Do nothing is the expression evaluates to false.
+ if (!isc::dhcp::evaluateBool(*opt_cfg->getExpr(), *query)) {
+ break;
+ }
+ // Remove the option.
+ while (response->getOption(code)) {
+ response->delOption(code);
+ }
+ logAction(REMOVE, code, "");
+ break;
+ }
+ }
+ }
+ for (auto pair : getSubOptionConfigMap()) {
+ for (auto sub_pair : pair.second) {
+ const SubOptionConfigPtr& sub_cfg = sub_pair.second;
+ uint16_t sub_code = sub_cfg->getCode();
+ uint16_t opt_code = sub_cfg->getContainerCode();
+ const isc::dhcp::ClientClass& opt_class =
+ sub_cfg->getContainerClass();
+ if (!opt_class.empty()) {
+ if (!query->inClass(opt_class)) {
+ logClass(opt_class, opt_code);
+ continue;
+ }
+ }
+ const isc::dhcp::ClientClass& sub_class =
+ sub_cfg->getClass();
+ if (!sub_class.empty()) {
+ if (!query->inClass(sub_class)) {
+ logSubClass(sub_class, sub_code, opt_code);
+ continue;
+ }
+ }
+ std::string value;
+ isc::dhcp::OptionBuffer buffer;
+ isc::dhcp::OptionPtr opt = response->getOption(opt_code);
+ isc::dhcp::OptionPtr sub;
+ isc::dhcp::OptionDefinitionPtr def = sub_cfg->getOptionDef();
+ uint32_t vendor_id = sub_cfg->getVendorId();
+ switch (sub_cfg->getAction()) {
+ case NONE:
+ break;
+ case ADD:
+ // If no container and no magic add return
+ if (!opt && (sub_cfg->getContainerAction() != ADD)) {
+ break;
+ }
+ // Do nothing is the expression evaluates to empty.
+ value = isc::dhcp::evaluateString(*sub_cfg->getExpr(),
+ *query);
+ if (value.empty()) {
+ break;
+ }
+ // Check vendor id mismatch.
+ if (opt && vendor_id && !checkVendor(opt, vendor_id)) {
+ break;
+ }
+ // Don't add if sub-option is already there.
+ if (opt && opt->getOption(sub_code)) {
+ break;
+ }
+ // Set the value.
+ if (def) {
+ std::vector<std::string> split_vec =
+ isc::util::str::tokens(value, ",", true);
+ sub = def->optionFactory(universe, sub_code,
+ split_vec);
+ } else {
+ buffer.assign(value.begin(), value.end());
+ sub.reset(new isc::dhcp::Option(universe, sub_code,
+ buffer));
+ }
+ // If the container does not exist add it.
+ if (!opt) {
+ if (!vendor_id) {
+ opt.reset(new isc::dhcp::Option(universe,
+ opt_code));
+ } else {
+ opt.reset(new isc::dhcp::OptionVendor(universe,
+ vendor_id));
+ }
+ response->addOption(opt);
+ if (vendor_id) {
+ logAction(ADD, opt_code, vendor_id);
+ } else {
+ logAction(ADD, opt_code, "");
+ }
+ }
+ // Add the sub-option.
+ opt->addOption(sub);
+ logSubAction(ADD, sub_code, opt_code, value);
+ break;
+ case SUPERSEDE:
+ // If no container and no magic add return
+ if (!opt && (sub_cfg->getContainerAction() != ADD)) {
+ break;
+ }
+ // Do nothing is the expression evaluates to empty.
+ value = isc::dhcp::evaluateString(*sub_cfg->getExpr(),
+ *query);
+ if (value.empty()) {
+ break;
+ }
+ // Check vendor id mismatch.
+ if (opt && vendor_id && !checkVendor(opt, vendor_id)) {
+ break;
+ }
+ // Set the value.
+ if (def) {
+ std::vector<std::string> split_vec =
+ isc::util::str::tokens(value, ",", true);
+ sub = def->optionFactory(universe, sub_code,
+ split_vec);
+ } else {
+ buffer.assign(value.begin(), value.end());
+ sub.reset(new isc::dhcp::Option(universe, sub_code,
+ buffer));
+ }
+ // Remove the sub-option if already there.
+ if (opt) {
+ while (opt->getOption(sub_code)) {
+ opt->delOption(sub_code);
+ }
+ }
+ // If the container does not exist add it.
+ if (!opt) {
+ if (!vendor_id) {
+ opt.reset(new isc::dhcp::Option(universe,
+ opt_code));
+ } else {
+ opt.reset(new isc::dhcp::OptionVendor(universe,
+ vendor_id));
+ }
+ response->addOption(opt);
+ if (vendor_id) {
+ logAction(ADD, opt_code, vendor_id);
+ } else {
+ logAction(ADD, opt_code, "");
+ }
+ }
+ // Add the sub-option.
+ opt->addOption(sub);
+ logSubAction(SUPERSEDE, sub_code, opt_code, value);
+ break;
+ case REMOVE:
+ // Nothing to remove if container is not present.
+ if (!opt) {
+ break;
+ }
+ sub = opt->getOption(sub_code);
+ // Nothing to remove if sub-option is not present.
+ if (!sub) {
+ break;
+ }
+ // Do nothing is the expression evaluates to false.
+ if (!isc::dhcp::evaluateBool(*sub_cfg->getExpr(), *query)) {
+ break;
+ }
+ // Check vendor id mismatch.
+ if (opt && vendor_id && !checkVendor(opt, vendor_id)) {
+ break;
+ }
+ // Remove the sub-option.
+ while (opt->getOption(sub_code)) {
+ opt->delOption(sub_code);
+ }
+ logSubAction(REMOVE, sub_code, opt_code, "");
+ // Remove the empty container when wanted.
+ if ((sub_cfg->getContainerAction() == REMOVE) &&
+ opt->getOptions().empty()) {
+ response->delOption(opt_code);
+ logAction(REMOVE, opt_code, "");
+ }
+ break;
+ }
+ }
+ }
+ }
+
+
+ /// @brief Log the client class for option.
+ ///
+ /// @param client_class The client class aka guard name.
+ /// @param code The option code.
+ static void logClass(const isc::dhcp::ClientClass &client_class,
+ uint16_t code);
+
+ /// @brief Log the action for option.
+ ///
+ /// @param action The action.
+ /// @param code The option code.
+ /// @param value The option value ("" for remove).
+ static void logAction(Action action, uint16_t code,
+ const std::string& value);
+
+ /// @brief Log the action for option.
+ ///
+ /// @param action The action.
+ /// @param code The option code.
+ /// @param vendor_id The vendor option vendor id.
+ static void logAction(Action action, uint16_t code, uint32_t vendor_id);
+
+ /// @brief Log the client class for sub-option.
+ ///
+ /// @param client_class The client class aka guard name.
+ /// @param code The sub-option code.
+ /// @param container_code The container option code.
+ static void logSubClass(const isc::dhcp::ClientClass &client_class,
+ uint16_t code, uint16_t container_code);
+
+ /// @brief Log the action for sub-option.
+ ///
+ /// @param action The action.
+ /// @param code The sub-option code.
+ /// @param container_code The container option code.
+ /// @param value The option value ("" for remove).
+ static void logSubAction(Action action, uint16_t code,
+ uint16_t container_code,
+ const std::string& value);
+
+ /// @brief Check vendor option vendor id mismatch.
+ ///
+ /// @param opt The pointer to the option.
+ /// @param vendor_id The vendor option vendor id.
+ static bool checkVendor(isc::dhcp::OptionPtr opt, uint32_t vendor_id);
+
+protected:
+ /// @brief Get a mutable reference to the option config map
+ ///
+ /// @return The option config map.
+ OptionConfigMap& getMutableOptionConfigMap() {
+ return (option_config_map_);
+ }
+
+ /// @brief Get a mutable reference to the sub-option config map of maps.
+ ///
+ /// @return The sub-option config map of maps.
+ SubOptionConfigMapMap& getMutableSubOptionConfigMap() {
+ return (sub_option_config_map_);
+ }
+
+private:
+ /// @brief Option parameters.
+ static const data::SimpleKeywords OPTION_PARAMETERS;
+
+ /// @brief Sub-option parameters.
+ static const data::SimpleKeywords SUB_OPTION_PARAMETERS;
+
+ /// @brief The option config map (code and pointer to option config).
+ OptionConfigMap option_config_map_;
+
+ /// @brief The sub-option config map of maps.
+ SubOptionConfigMapMap sub_option_config_map_;
+
+ /// @brief Parse an option config.
+ ///
+ /// @param option The element with option config.
+ /// @throw BadValue and similar exceptions on error.
+ void parseOptionConfig(isc::data::ConstElementPtr option);
+
+ /// @brief Parse a sub-option.
+ ///
+ /// @param sub_option The sub-option element.
+ /// @param opt_cfg The container option configuration.
+ /// @param universe The universe.
+ void parseSubOption(isc::data::ConstElementPtr sub_option,
+ OptionConfigPtr opt_cfg,
+ isc::dhcp::Option::Universe universe);
+
+ /// @brief Parse sub-options.
+ ///
+ /// @param sub_options The sub-option list.
+ /// @param opt_cfg The container option configuration.
+ /// @param universe The universe.
+ void parseSubOptions(isc::data::ConstElementPtr sub_options,
+ OptionConfigPtr opt_cfg,
+ isc::dhcp::Option::Universe universe);
+};
+
+/// @brief The type of shared pointers to Flex Option implementations.
+typedef boost::shared_ptr<FlexOptionImpl> FlexOptionImplPtr;
+
+} // end of namespace flex_option
+} // end of namespace isc
+#endif