// Copyright (C) 2015-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/. #include #include #include #include #include #include #include #include #include using namespace isc::data; namespace isc { namespace dhcp { //********** ClientClassDef ******************// ClientClassDef::ClientClassDef(const std::string& name, const ExpressionPtr& match_expr, const CfgOptionPtr& cfg_option) : UserContext(), CfgToElement(), StampedElement(), name_(name), match_expr_(match_expr), required_(false), depend_on_known_(false), cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()), valid_(), preferred_() { // Name can't be blank if (name_.empty()) { isc_throw(BadValue, "Client Class name cannot be blank"); } // We permit an empty expression for now. This will likely be useful // for automatic classes such as vendor class. // For classes without options, make sure we have an empty collection if (!cfg_option_) { cfg_option_.reset(new CfgOption()); } } ClientClassDef::ClientClassDef(const ClientClassDef& rhs) : UserContext(rhs), CfgToElement(rhs), StampedElement(rhs), name_(rhs.name_), match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_), depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()), next_server_(rhs.next_server_), sname_(rhs.sname_), filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_), offer_lft_(rhs.offer_lft_) { if (rhs.match_expr_) { match_expr_.reset(new Expression()); *match_expr_ = *rhs.match_expr_; } if (rhs.cfg_option_def_) { cfg_option_def_.reset(new CfgOptionDef()); rhs.cfg_option_def_->copyTo(*cfg_option_def_); } if (rhs.cfg_option_) { rhs.cfg_option_->copyTo(*cfg_option_); } } ClientClassDef::~ClientClassDef() { } std::string ClientClassDef::getName() const { return (name_); } void ClientClassDef::setName(const std::string& name) { name_ = name; } const ExpressionPtr& ClientClassDef::getMatchExpr() const { return (match_expr_); } void ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) { match_expr_ = match_expr; } std::string ClientClassDef::getTest() const { return (test_); } void ClientClassDef::setTest(const std::string& test) { test_ = test; } bool ClientClassDef::getRequired() const { return (required_); } void ClientClassDef::setRequired(bool required) { required_ = required; } bool ClientClassDef::getDependOnKnown() const { return (depend_on_known_); } void ClientClassDef::setDependOnKnown(bool depend_on_known) { depend_on_known_ = depend_on_known; } const CfgOptionDefPtr& ClientClassDef::getCfgOptionDef() const { return (cfg_option_def_); } void ClientClassDef::setCfgOptionDef(const CfgOptionDefPtr& cfg_option_def) { cfg_option_def_ = cfg_option_def; } const CfgOptionPtr& ClientClassDef::getCfgOption() const { return (cfg_option_); } void ClientClassDef::setCfgOption(const CfgOptionPtr& cfg_option) { cfg_option_ = cfg_option; } void ClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) { // Evaluate the expression which can return false (no match), // true (match) or raise an exception (error) try { bool status = evaluateBool(*expr_ptr, *pkt); if (status) { LOG_INFO(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg("true"); // Matching: add the class pkt->addClass(getName()); } else { LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, EVAL_RESULT) .arg(getName()) .arg("false"); } } catch (const Exception& ex) { LOG_ERROR(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg(ex.what()); } catch (...) { LOG_ERROR(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg("get exception?"); } } const std::string TemplateClientClassDef::SPAWN_CLASS_PREFIX("SPAWN_"); TemplateClientClassDef::TemplateClientClassDef(const std::string& name, const ExpressionPtr& match_expr, const CfgOptionPtr& options) : ClientClassDef(name, match_expr, options) { } void TemplateClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) { // Evaluate the expression which can return false (no match), // true (match) or raise an exception (error) try { std::string subclass = evaluateString(*expr_ptr, *pkt); if (!subclass.empty()) { LOG_INFO(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg(subclass); // Matching: add the subclass std::string value(TemplateClientClassDef::SPAWN_CLASS_PREFIX); value += getName(); value += "_"; value += subclass; pkt->addSubClass(getName(), value); } } catch (const Exception& ex) { LOG_ERROR(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg(ex.what()); } catch (...) { LOG_ERROR(dhcpsrv_logger, EVAL_RESULT) .arg(getName()) .arg("get exception?"); } } bool ClientClassDef::dependOnClass(const std::string& name) const { return (isc::dhcp::dependOnClass(match_expr_, name)); } bool ClientClassDef::equals(const ClientClassDef& other) const { return ((name_ == other.name_) && ((!match_expr_ && !other.match_expr_) || (match_expr_ && other.match_expr_ && (*match_expr_ == *other.match_expr_))) && ((!cfg_option_ && !other.cfg_option_) || (cfg_option_ && other.cfg_option_ && (*cfg_option_ == *other.cfg_option_))) && ((!cfg_option_def_ && !other.cfg_option_def_) || (cfg_option_def_ && other.cfg_option_def_ && (*cfg_option_def_ == *other.cfg_option_def_))) && (required_ == other.required_) && (depend_on_known_ == other.depend_on_known_) && (next_server_ == other.next_server_) && (sname_ == other.sname_) && (filename_ == other.filename_)); } ElementPtr ClientClassDef::toElement() const { uint16_t family = CfgMgr::instance().getFamily(); ElementPtr result = Element::createMap(); // Set user-context contextToElement(result); // Set name result->set("name", Element::create(name_)); // Set original match expression (empty string won't parse) if (!test_.empty()) { result->set("test", Element::create(test_)); } // Set only-if-required if (required_) { result->set("only-if-required", Element::create(required_)); } // Set option-def (used only by DHCPv4) if (cfg_option_def_ && (family == AF_INET)) { result->set("option-def", cfg_option_def_->toElement()); } // Set option-data result->set("option-data", cfg_option_->toElement()); if (family == AF_INET) { // V4 only // Set next-server result->set("next-server", Element::create(next_server_.toText())); // Set server-hostname result->set("server-hostname", Element::create(sname_)); // Set boot-file-name result->set("boot-file-name", Element::create(filename_)); // Set offer-lifetime if (!offer_lft_.unspecified()) { result->set("offer-lifetime", Element::create(static_cast(offer_lft_.get()))); } } else { // V6 only // Set preferred-lifetime if (!preferred_.unspecified()) { result->set("preferred-lifetime", Element::create(static_cast(preferred_.get()))); } if (preferred_.getMin() < preferred_.get()) { result->set("min-preferred-lifetime", Element::create(static_cast(preferred_.getMin()))); } if (preferred_.getMax() > preferred_.get()) { result->set("max-preferred-lifetime", Element::create(static_cast(preferred_.getMax()))); } } // Set valid-lifetime if (!valid_.unspecified()) { result->set("valid-lifetime", Element::create(static_cast(valid_.get()))); if (valid_.getMin() < valid_.get()) { result->set("min-valid-lifetime", Element::create(static_cast(valid_.getMin()))); } if (valid_.getMax() > valid_.get()) { result->set("max-valid-lifetime", Element::create(static_cast(valid_.getMax()))); } } return (result); } ElementPtr TemplateClientClassDef::toElement() const { auto const& result = ClientClassDef::toElement(); auto const& test = result->get("test"); if (test) { result->set("template-test", test); result->remove("test"); } else { result->set("template-test", Element::create("")); } return (result); } std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) { os << "ClientClassDef:" << x.getName(); return (os); } //********** ClientClassDictionary ******************// ClientClassDictionary::ClientClassDictionary() : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) { } ClientClassDictionary::ClientClassDictionary(const ClientClassDictionary& rhs) : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) { for (auto const& cclass : *rhs.list_) { ClientClassDefPtr copy(new ClientClassDef(*cclass)); addClass(copy); } } ClientClassDictionary::~ClientClassDictionary() { } void ClientClassDictionary::addClass(const std::string& name, const ExpressionPtr& match_expr, const std::string& test, bool required, bool depend_on_known, const CfgOptionPtr& cfg_option, CfgOptionDefPtr cfg_option_def, ConstElementPtr user_context, asiolink::IOAddress next_server, const std::string& sname, const std::string& filename, const util::Triplet& valid, const util::Triplet& preferred, bool is_template, const util::Optional& offer_lft) { ClientClassDefPtr cclass; if (is_template) { cclass.reset(new TemplateClientClassDef(name, match_expr, cfg_option)); } else { cclass.reset(new ClientClassDef(name, match_expr, cfg_option)); } cclass->setTest(test); cclass->setRequired(required); cclass->setDependOnKnown(depend_on_known); cclass->setCfgOptionDef(cfg_option_def); cclass->setContext(user_context), cclass->setNextServer(next_server); cclass->setSname(sname); cclass->setFilename(filename); cclass->setValid(valid); cclass->setPreferred(preferred); cclass->setOfferLft(offer_lft); addClass(cclass); } void ClientClassDictionary::addClass(const ClientClassDefPtr& class_def) { if (!class_def) { isc_throw(BadValue, "ClientClassDictionary::addClass " " - class definition cannot be null"); } if (findClass(class_def->getName())) { isc_throw(DuplicateClientClassDef, "Client Class: " << class_def->getName() << " has already been defined"); } list_->push_back(class_def); (*map_)[class_def->getName()] = class_def; } ClientClassDefPtr ClientClassDictionary::findClass(const std::string& name) const { ClientClassDefMap::iterator it = map_->find(name); if (it != map_->end()) { return (*it).second; } return (ClientClassDefPtr()); } void ClientClassDictionary::removeClass(const std::string& name) { for (ClientClassDefList::iterator this_class = list_->begin(); this_class != list_->end(); ++this_class) { if ((*this_class)->getName() == name) { list_->erase(this_class); break; } } map_->erase(name); } void ClientClassDictionary::removeClass(const uint64_t id) { // Class id equal to 0 means it wasn't set. if (id == 0) { return; } for (ClientClassDefList::iterator this_class = list_->begin(); this_class != list_->end(); ++this_class) { if ((*this_class)->getId() == id) { map_->erase((*this_class)->getName()); list_->erase(this_class); break; } } } const ClientClassDefListPtr& ClientClassDictionary::getClasses() const { return (list_); } bool ClientClassDictionary::empty() const { return (list_->empty()); } bool ClientClassDictionary::dependOnClass(const std::string& name, std::string& dependent_class) const { // Skip previous classes as they should not depend on name. bool found = false; for (auto const& this_class : *list_) { if (found) { if (this_class->dependOnClass(name)) { dependent_class = this_class->getName(); return (true); } } else { if (this_class->getName() == name) { found = true; } } } return (false); } bool ClientClassDictionary::equals(const ClientClassDictionary& other) const { if (list_->size() != other.list_->size()) { return (false); } ClientClassDefList::const_iterator this_class = list_->cbegin(); ClientClassDefList::const_iterator other_class = other.list_->cbegin(); while (this_class != list_->cend() && other_class != other.list_->cend()) { if (!*this_class || !*other_class || **this_class != **other_class) { return false; } ++this_class; ++other_class; } return (true); } void ClientClassDictionary::initMatchExpr(uint16_t family) { std::queue expressions; for (auto const& c : *list_) { if (!c->getTest().empty()) { ExpressionPtr match_expr = boost::make_shared(); ExpressionParser parser; EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL; if (dynamic_cast(c.get())) { parser_type = EvalContext::PARSER_STRING; } parser.parse(match_expr, Element::create(c->getTest()), family, EvalContext::acceptAll, parser_type); expressions.push(match_expr); } } // All expressions successfully initialized. Let's set them for the // client classes in the dictionary. for (auto const& c : *list_) { if (!c->getTest().empty()) { c->setMatchExpr(expressions.front()); expressions.pop(); } } } void ClientClassDictionary::createOptions(const CfgOptionDefPtr& external_defs) { for (auto const& c : *list_) { // If the class has no options, skip it. CfgOptionPtr class_options = c->getCfgOption(); if (!class_options || class_options->empty()) { continue; } // If the class has no option definitions, use the set // of definitions we were given as is to create its // options. if (!c->getCfgOptionDef()) { class_options->createOptions(external_defs); } else { // Class has its own option definitions, we need a // composite set of definitions to recreate its options. // We make copies of both sets of definitions, then merge // the external defs copy into the class defs copy. // We do this because merging actually effects both sets // of definitions and we cannot alter either set. // Seed the composite set with the class's definitions. CfgOptionDefPtr composite_defs(new CfgOptionDef()); c->getCfgOptionDef()->copyTo(*composite_defs); // Make a copy of the external definitions and // merge those into the composite set. This should give // us a set of options with class definitions taking // precedence. CfgOptionDefPtr external_defs_copy(new CfgOptionDef()); external_defs->copyTo(*external_defs_copy); composite_defs->merge(*external_defs_copy); // Now create the class options using the composite // set of definitions. class_options->createOptions(composite_defs); } } } ElementPtr ClientClassDictionary::toElement() const { ElementPtr result = Element::createList(); // Iterate on the map for (auto const& this_class : *list_) { result->add(this_class->toElement()); } return (result); } ClientClassDictionary& ClientClassDictionary::operator=(const ClientClassDictionary& rhs) { if (this != &rhs) { list_->clear(); map_->clear(); for (auto const& cclass : *rhs.list_) { ClientClassDefPtr copy(new ClientClassDef(*cclass)); addClass(copy); } } return (*this); } /// @brief List of classes for which test expressions cannot be defined. std::list builtinNames = { // DROP is not in this list because it is special but not built-in. // In fact DROP is set from an expression as callouts can drop // directly the incoming packet. The expression must not depend on // KNOWN/UNKNOWN which are set far after the drop point. // SKIP_DDNS, used by DDNS-tuning is also omitted from this list // so users may assign it a test expression. "ALL", "KNOWN", "UNKNOWN", "BOOTP" }; /// @brief The prefixes used to check if a class is BuiltIn class. /// /// The 'SPAWN_' prefix is not added to this list to permit other template /// classes to associate the packet to regular classes which use this prefix in /// their name. This guarantees that regular classes are never treated as /// built-in classes. std::list builtinPrefixes = { "VENDOR_CLASS_", "HA_", "AFTER_", "EXTERNAL_" }; bool isClientClassBuiltIn(const ClientClass& client_class) { for (auto const& bn : builtinNames) { if (client_class == bn) { return true; } } for (auto const& bt : builtinPrefixes) { if (client_class.size() <= bt.size()) { continue; } auto mis = std::mismatch(bt.cbegin(), bt.cend(), client_class.cbegin()); if (mis.first == bt.cend()) { return true; } } return false; } bool isClientClassDefined(ClientClassDictionaryPtr& class_dictionary, bool& depend_on_known, const ClientClass& client_class) { // First check built-in classes if (isClientClassBuiltIn(client_class)) { // Check direct dependency on [UN]KNOWN if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) { depend_on_known = true; } return (true); } // Second check already defined, i.e. in the dictionary ClientClassDefPtr def = class_dictionary->findClass(client_class); if (def) { // Check indirect dependency on [UN]KNOWN if (def->getDependOnKnown()) { depend_on_known = true; } return (true); } // Not defined... return (false); } } // namespace isc::dhcp } // namespace isc