// Copyright (C) 2019-2024 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 OptionConfigPtr; /// @brief The type of lists of shared pointers to option config. typedef std::list OptionConfigList; /// @brief The type of the option config map. typedef std::map 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 SubOptionConfigPtr; /// @brief The type of the sub-option config map. /// @note the index is the sub-option code. typedef std::map SubOptionConfigMap; /// @brief The type of the map of sub-option config maps. /// @note the index is the container option code. typedef std::map 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 void process(isc::dhcp::Option::Universe universe, PktType query, PktType response) { for (auto const& 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 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 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 const& pair : getSubOptionConfigMap()) { for (auto const& 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 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 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 FlexOptionImplPtr; } // end of namespace flex_option } // end of namespace isc #endif