From: Francis Dupont Date: Thu, 21 Aug 2025 18:18:58 +0000 (+0200) Subject: [#3860] More vendor X-Git-Tag: Kea-3.1.2~43 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f4bee33be3b217fa897c61167ce5daccc8f2ee08;p=thirdparty%2Fkea.git [#3860] More vendor --- diff --git a/src/hooks/dhcp/radius/cfg_attribute.cc b/src/hooks/dhcp/radius/cfg_attribute.cc index 81a855d462..54bd2c814d 100644 --- a/src/hooks/dhcp/radius/cfg_attribute.cc +++ b/src/hooks/dhcp/radius/cfg_attribute.cc @@ -28,13 +28,12 @@ CfgAttributes::add(const AttrDefPtr& def, if (!def) { isc_throw(BadValue, "no attribute definition"); } - container_.insert(pair - (def->type_, AttributeValue(def, attr, expr, test))); + container_.insert(AttributeValue(def, attr, expr, test)); } bool -CfgAttributes::del(const uint8_t type) { - auto it = container_.find(type); +CfgAttributes::del(const uint8_t type, const uint32_t vendor) { + auto it = container_.find(boost::make_tuple(vendor, type)); if (it != container_.end()) { container_.erase(it); return (true); @@ -43,46 +42,46 @@ CfgAttributes::del(const uint8_t type) { } AttrDefPtr -CfgAttributes::getDef(const uint8_t type) const { - auto it = container_.find(type); +CfgAttributes::getDef(const uint8_t type, const uint32_t vendor) const { + auto it = container_.find(boost::make_tuple(vendor, type)); if (it == container_.end()) { return (AttrDefPtr()); } - return (it->second.def_); + return (it->def_); } ConstAttributePtr -CfgAttributes::get(const uint8_t type) const { - auto it = container_.find(type); +CfgAttributes::get(const uint8_t type, const uint32_t vendor) const { + auto it = container_.find(boost::make_tuple(vendor, type)); if (it == container_.end()) { return (AttributePtr()); } - return (it->second.attr_); + return (it->attr_); } ExpressionPtr -CfgAttributes::getExpr(const uint8_t type) const { - auto it = container_.find(type); +CfgAttributes::getExpr(const uint8_t type, const uint32_t vendor) const { + auto it = container_.find(boost::make_tuple(vendor, type)); if (it == container_.end()) { return (ExpressionPtr()); } - return (it->second.expr_); + return (it->expr_); } string -CfgAttributes::getTest(const uint8_t type) const { - auto it = container_.find(type); +CfgAttributes::getTest(const uint8_t type, const uint32_t vendor) const { + auto it = container_.find(boost::make_tuple(vendor, type)); if (it == container_.end()) { return (""); } - return (it->second.test_); + return (it->test_); } Attributes CfgAttributes::getAll() const { Attributes attrs; for (auto const& it : container_) { - attrs.add(it.second.attr_); + attrs.add(it.attr_); } return (attrs); } @@ -91,16 +90,16 @@ Attributes CfgAttributes::getEvalAll(Pkt& pkt) { Attributes attrs; for (auto const& it : container_) { - const ExpressionPtr& match_expr = it.second.expr_; + const ExpressionPtr& match_expr = it.expr_; if (!match_expr) { - attrs.add(it.second.attr_); + attrs.add(it.attr_); continue; } string value = evaluateString(*match_expr, pkt); if (value.empty()) { continue; } - AttrDefPtr def = it.second.def_; + AttrDefPtr def = it.def_; if (!def) { continue; } @@ -117,18 +116,18 @@ ElementPtr CfgAttributes::toElement() const { ElementPtr result = Element::createList(); for (auto const& it : container_) { - AttrDefPtr def = it.second.def_; + AttrDefPtr def = it.def_; if (!def) { continue; } ElementPtr map; - if (!it.second.test_.empty()) { + if (!it.test_.empty()) { map = Element::createMap(); - map->set("type", Element::create(static_cast(it.first))); - map->set("expr", Element::create(it.second.test_)); + map->set("type", Element::create(static_cast(def->type_))); + map->set("expr", Element::create(it.test_)); map->set("name", Element::create(def->name_)); - } else if (it.second.attr_) { - map = it.second.attr_->toElement(); + } else if (it.attr_) { + map = it.attr_->toElement(); } result->add(map); } diff --git a/src/hooks/dhcp/radius/cfg_attribute.h b/src/hooks/dhcp/radius/cfg_attribute.h index c8dd89c988..d97b6d6731 100644 --- a/src/hooks/dhcp/radius/cfg_attribute.h +++ b/src/hooks/dhcp/radius/cfg_attribute.h @@ -14,6 +14,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -81,8 +85,9 @@ public: /// Deletes only the first occurrence. /// /// @param type type of the attribute to delete. + /// @param vendor vendor id (default 0). /// @return true if found. - bool del(const uint8_t type); + bool del(const uint8_t type, const uint32_t vendor = 0); /// @brief Clear the container. void clear() { @@ -92,34 +97,41 @@ public: /// @brief Counts instance of the attribute in the configuration. /// /// @param type type of the attribute to count. - /// @return count of attributes with the given type in the configuration. - size_t count(const uint8_t type) const { - return (container_.count(type)); + /// @param vendor vendor id (default 0). + /// @return count of attributes with the given type and vendor + /// in the configuration. + size_t count(const uint8_t type, const uint32_t vendor = 0) const { + return (container_.count(boost::make_tuple(vendor, type))); } /// @brief Returns the definition of an attribute. /// /// @param type type of the attribute. + /// @param vendor vendor id (default 0). /// @return the definition of the attribute. - AttrDefPtr getDef(const uint8_t type) const; + AttrDefPtr getDef(const uint8_t type, const uint32_t vendor = 0) const; /// @brief Get instance of the attribute in the configuration. /// /// @param type type of the attribute to retrieve. + /// @param vendor vendor id (default 0). /// @return the first instance if exists. - ConstAttributePtr get(const uint8_t type) const; + ConstAttributePtr get(const uint8_t type, const uint32_t vendor = 0) const; /// @brief Returns the expression of an attribute. /// /// @param type type of the attribute. + /// @param vendor vendor id (default 0). /// @return the expression of the attribute. - dhcp::ExpressionPtr getExpr(const uint8_t type) const; + dhcp::ExpressionPtr getExpr(const uint8_t type, + const uint32_t vendor = 0) const; /// @brief Returns the text expression of an attribute. /// /// @param type type of the attribute. + /// @param vendor vendor id (default 0). /// @return the text expression of the attribute. - std::string getTest(const uint8_t type) const; + std::string getTest(const uint8_t type, const uint32_t vendor = 0) const; /// @brief Get all attributes in the configuration. /// @@ -182,10 +194,44 @@ protected: /// @brief Original expression. std::string test_; + + /// @brief Return the type. + uint8_t getType() const { + if (!def_) { + isc_throw(BadValue, "no attribute definition"); + } + return (def_->type_); + } + + /// @brief Return the vendor. + uint32_t getVendor() const { + if (!def_) { + isc_throw(BadValue, "no attribute definition"); + } + return (def_->vendor_); + } }; /// @brief The container. - std::multimap container_; + boost::multi_index_container< + // This container stores AttributeValue objects. + AttributeValue, + // Start specification of indexes here. + boost::multi_index::indexed_by< + // Hash index for by vendor and type. + boost::multi_index::hashed_non_unique< + boost::multi_index::composite_key< + AttributeValue, + boost::multi_index::const_mem_fun< + AttributeValue, uint32_t, &AttributeValue::getVendor + >, + boost::multi_index::const_mem_fun< + AttributeValue, uint8_t, &AttributeValue::getType + > + > + > + > + > container_; }; } // end of namespace isc::radius diff --git a/src/hooks/dhcp/radius/client_attribute.cc b/src/hooks/dhcp/radius/client_attribute.cc index 7840bd4b97..bb77f5e646 100644 --- a/src/hooks/dhcp/radius/client_attribute.cc +++ b/src/hooks/dhcp/radius/client_attribute.cc @@ -32,6 +32,17 @@ Attribute::fromText(const AttrDefPtr& def, const string& value) { if (value.empty()) { isc_throw(BadValue, "empty attribute value"); } + AttributePtr attr = fromText0(def, value); + if (def->vendor_ == 0) { + return (attr); + } + // Encapsulate into a Vendor-Specific attribute. + const vector vsa_data = attr->toBytes(); + return (fromVsa(PW_VENDOR_SPECIFIC, def->vendor_, vsa_data)); +} + +AttributePtr +Attribute::fromText0(const AttrDefPtr& def, const string& value) { switch (static_cast(def->value_type_)) { case PW_TYPE_STRING: return (AttrString::fromText(def->type_, value)); @@ -92,6 +103,17 @@ Attribute::fromBytes(const AttrDefPtr& def, const vector& value) { if (value.empty()) { isc_throw(BadValue, "empty attribute value"); } + AttributePtr attr = fromBytes0(def, value); + if (def->vendor_ == 0) { + return (attr); + } + // Encapsulate into a Vendor-Specific attribute. + const vector vsa_data = attr->toBytes(); + return (fromVsa(PW_VENDOR_SPECIFIC, def->vendor_, vsa_data)); +} + +AttributePtr +Attribute::fromBytes0(const AttrDefPtr& def, const vector& value) { switch (static_cast(def->value_type_)) { case PW_TYPE_STRING: return (AttrString::fromBytes(def->type_, value)); @@ -209,7 +231,7 @@ Attribute::toVendorId() const { << attrValueTypeToText(getValueType())); } -string +std::vector Attribute::toVsaData() const { isc_throw(TypeError, "the attribute value type must be vsa, not " << attrValueTypeToText(getValueType())); @@ -695,6 +717,16 @@ AttrVsa::toBytes() const { return (output); } +std::vector +AttrVsa::toVsaData() const { + vector binary; + binary.resize(value_.size()); + if (binary.size() > 0) { + memmove(&binary[0], &value_[0], binary.size()); + } + return (binary); +} + ElementPtr AttrVsa::toElement() const { ElementPtr output = Element::createMap(); diff --git a/src/hooks/dhcp/radius/client_attribute.h b/src/hooks/dhcp/radius/client_attribute.h index d98ee9d601..db00b2e2e5 100644 --- a/src/hooks/dhcp/radius/client_attribute.h +++ b/src/hooks/dhcp/radius/client_attribute.h @@ -78,7 +78,9 @@ public: /// From definition generic factories. - /// @brief From text with definition. + /// @brief From text with definition (handle vendor). + /// + /// Handle Vendor-Specific encapsulation. /// /// @param def pointer to attribute definition. /// @param value textual value. @@ -87,7 +89,9 @@ public: static AttributePtr fromText(const AttrDefPtr& def, const std::string& value); - /// @brief From bytes with definition. + /// @brief From bytes with definition (handle vendor). + /// + /// Handle Vendor-Specific encapsulation. /// /// @param def pointer to attribute definition. /// @param value binary value. @@ -251,10 +255,30 @@ public: /// /// @return the vsa data. /// @throw TypeError if the attribute is not a vsa one. - virtual std::string toVsaData() const; + virtual std::vector toVsaData() const; /// @brief Type. const uint8_t type_; + +private: + + /// @brief From text with definition. + /// + /// @param def pointer to attribute definition. + /// @param value textual value. + /// @return pointer to the attribute. + /// @throw BadValue on errors. + static AttributePtr fromText0(const AttrDefPtr& def, + const std::string& value); + + /// @brief From bytes with definition. + /// + /// @param def pointer to attribute definition. + /// @param value binary value. + /// @return pointer to the attribute. + /// @throw BadValue on errors. + static AttributePtr fromBytes0(const AttrDefPtr& def, + const std::vector& value); }; /// @brief RADIUS attribute derived classes: do not use them directly @@ -764,9 +788,7 @@ public: /// @brief To vsa data. /// /// @return the vsa data. - virtual std::string toVsaData() const override { - return (value_); - } + virtual std::vector toVsaData() const override; /// @brief Unparse attribute. /// diff --git a/src/hooks/dhcp/radius/tests/attribute_unittests.cc b/src/hooks/dhcp/radius/tests/attribute_unittests.cc index e7f51066dd..ee97ea0d56 100644 --- a/src/hooks/dhcp/radius/tests/attribute_unittests.cc +++ b/src/hooks/dhcp/radius/tests/attribute_unittests.cc @@ -590,6 +590,38 @@ TEST_F(AttributeTest, attrVsa) { "the attribute value type must be ipv6prefix, not vsa"); } +// Verifies vendor fromText. +TEST_F(AttributeTest, vendorFromText) { + // Using DSL-Forum (3561) Agent-Circuit-Id (1), + AttrDefPtr def(new AttrDef(1, "Agent-Circuit-Id", PW_TYPE_STRING, 3561)); + AttributePtr attr; + ASSERT_NO_THROW(attr = Attribute::fromText(def, "foobar")); + ASSERT_TRUE(attr); + EXPECT_EQ(PW_VENDOR_SPECIFIC, attr->getType()); + EXPECT_EQ(PW_TYPE_VSA, attr->getValueType()); + EXPECT_EQ(3561, attr->toVendorId()); + EXPECT_EQ(12, attr->getValueLen()); + vector vsa_data = { 1, 8, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72 }; + EXPECT_EQ(vsa_data, attr->toVsaData()); +} + +// Verifies vendor fromBytes. +TEST_F(AttributeTest, vendorFromBytes) { + // Using DSL-Forum (3561) Access-Loop-Encapsulation (144), + AttrDefPtr def(new AttrDef(144, "Access-Loop-Encapsulation", + PW_TYPE_STRING, 3561)); + AttributePtr attr; + vector value = { 2, 0, 0 }; + ASSERT_NO_THROW(attr = Attribute::fromBytes(def, value)); + ASSERT_TRUE(attr); + EXPECT_EQ(PW_VENDOR_SPECIFIC, attr->getType()); + EXPECT_EQ(PW_TYPE_VSA, attr->getValueType()); + EXPECT_EQ(3561, attr->toVendorId()); + EXPECT_EQ(9, attr->getValueLen()); + vector vsa_data = { 144, 5, 2, 0, 0 }; + EXPECT_EQ(vsa_data, attr->toVsaData()); +} + // Verifies basic methods for attribute collection. TEST_F(AttributeTest, attributesBasic) { Attributes attrs; diff --git a/src/hooks/dhcp/radius/tests/dictionary_unittests.cc b/src/hooks/dhcp/radius/tests/dictionary_unittests.cc index 9cbacbe61e..6d2115e39d 100644 --- a/src/hooks/dhcp/radius/tests/dictionary_unittests.cc +++ b/src/hooks/dhcp/radius/tests/dictionary_unittests.cc @@ -370,6 +370,7 @@ TEST_F(AttributeTest, attrDefs) { EXPECT_EQ(1, def->type_); EXPECT_EQ("User-Name", def->name_); EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(0, def->vendor_); def.reset(); // Type 0 is reserved. @@ -377,18 +378,28 @@ TEST_F(AttributeTest, attrDefs) { EXPECT_FALSE(def); def.reset(); + // Only vendor 0 i.e. no vendor was populated. + ASSERT_NO_THROW(def = AttrDefs::instance().getByType(1, 1234)); + EXPECT_FALSE(def); + def.reset(); + // getByName. ASSERT_NO_THROW(def = AttrDefs::instance().getByName("User-Name")); ASSERT_TRUE(def); EXPECT_EQ(1, def->type_); EXPECT_EQ("User-Name", def->name_); EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(0, def->vendor_); def.reset(); ASSERT_NO_THROW(def = AttrDefs::instance().getByName("Does-not-exist")); EXPECT_FALSE(def); def.reset(); + ASSERT_NO_THROW(def = AttrDefs::instance().getByName("User-Name", 1234)); + EXPECT_FALSE(def); + def.reset(); + // getName. string name; ASSERT_NO_THROW(name = AttrDefs::instance().getName(1)); @@ -396,6 +407,10 @@ TEST_F(AttributeTest, attrDefs) { name.clear(); ASSERT_NO_THROW(name = AttrDefs::instance().getName(252)); EXPECT_EQ("Attribute-252", name); + name.clear(); + ASSERT_NO_THROW(name = AttrDefs::instance().getName(1, 1234)); + EXPECT_EQ("Attribute-1", name); + name.clear(); // add (new). AttrDefPtr def1(new AttrDef(252, "Foo-Bar", PW_TYPE_IPADDR)); @@ -405,12 +420,14 @@ TEST_F(AttributeTest, attrDefs) { EXPECT_EQ(252, def->type_); EXPECT_EQ("Foo-Bar", def->name_); EXPECT_EQ(PW_TYPE_IPADDR, def->value_type_); + EXPECT_EQ(0, def->vendor_); def.reset(); ASSERT_NO_THROW(def = AttrDefs::instance().getByName("Foo-Bar")); ASSERT_TRUE(def); EXPECT_EQ(252, def->type_); EXPECT_EQ("Foo-Bar", def->name_); EXPECT_EQ(PW_TYPE_IPADDR, def->value_type_); + EXPECT_EQ(0, def->vendor_); def.reset(); // add (alias). @@ -421,12 +438,37 @@ TEST_F(AttributeTest, attrDefs) { ASSERT_TRUE(got); EXPECT_EQ(18, got->type_); EXPECT_EQ(PW_TYPE_STRING, got->value_type_); + EXPECT_EQ(0, got->vendor_); + def.reset(); + + // add (vendor). + AttrDefPtr defv(new AttrDef(1, "Agent-Circuit-Id", PW_TYPE_STRING, 3561)); + ASSERT_NO_THROW(AttrDefs::instance().add(defv)); + ASSERT_NO_THROW(def = AttrDefs::instance().getByType(1, 3561)); + ASSERT_TRUE(def); + EXPECT_EQ(1, def->type_); + EXPECT_EQ("Agent-Circuit-Id", def->name_); + EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(3561, def->vendor_); + def.reset(); + ASSERT_NO_THROW(def = + AttrDefs::instance().getByName("Agent-Circuit-Id", 3561)); + ASSERT_TRUE(def); + EXPECT_EQ(1, def->type_); + EXPECT_EQ("Agent-Circuit-Id", def->name_); + EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(3561, def->vendor_); + def.reset(); + ASSERT_NO_THROW(name = AttrDefs::instance().getName(1, 3561)); + EXPECT_EQ("Agent-Circuit-Id", name); + name.clear(); // add (change type). ASSERT_NO_THROW(def = AttrDefs::instance().getByName("User-Password")); ASSERT_TRUE(def); EXPECT_EQ(2, def->type_); EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(0, def->vendor_); AttrDefPtr def3(new AttrDef(17, "User-Password", PW_TYPE_STRING)); string expected = "Illegal attribute redefinition of "; expected += "'User-Password' type 2 value type string by 17 string"; @@ -444,11 +486,35 @@ TEST_F(AttributeTest, attrDefs) { expected += "type 2 value type string by 'Password' 2 integer"; EXPECT_THROW_MSG(AttrDefs::instance().add(def5), BadValue, expected); + // Same with vendor. + ASSERT_NO_THROW(def = + AttrDefs::instance().getByName("Agent-Circuit-Id", 3561)); + ASSERT_TRUE(def); + EXPECT_EQ(1, def->type_); + EXPECT_EQ(PW_TYPE_STRING, def->value_type_); + EXPECT_EQ(3561, def->vendor_); + AttrDefPtr def3v(new AttrDef(2, "Agent-Circuit-Id", PW_TYPE_STRING, 3561)); + expected = "Illegal attribute redefinition of 'Agent-Circuit-Id' "; + expected += "vendor 3561 type 1 value type string by 2 string"; + EXPECT_THROW_MSG(AttrDefs::instance().add(def3v), BadValue, expected); + AttrDefPtr def4v(new AttrDef(1, "Agent-Circuit-Id", PW_TYPE_INTEGER, 3561)); + expected = "Illegal attribute redefinition of 'Agent-Circuit-Id' "; + expected += "vendor 3561 type 1 value type string by 1 integer"; + EXPECT_THROW_MSG(AttrDefs::instance().add(def4v), BadValue, expected); + AttrDefPtr def5v(new AttrDef(1, "Agent-Remote-Id", PW_TYPE_INTEGER, 3561)); + expected = "Illegal attribute redefinition of 'Agent-Circuit-Id' "; + expected += "vendor 3561 type 1 value type string by "; + expected += "'Agent-Remote-Id' 1 integer"; + EXPECT_THROW_MSG(AttrDefs::instance().add(def5v), BadValue, expected); + // clear. ASSERT_NO_THROW(AttrDefs::instance().clear()); ASSERT_NO_THROW(def = AttrDefs::instance().getByType(1)); EXPECT_FALSE(def); def.reset(); + ASSERT_NO_THROW(def = AttrDefs::instance().getByType(1, 3561)); + EXPECT_FALSE(def); + def.reset(); ASSERT_NO_THROW(def = AttrDefs::instance().getByName("Foo-Bar")); EXPECT_FALSE(def); }