]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3860] More vendor
authorFrancis Dupont <fdupont@isc.org>
Thu, 21 Aug 2025 18:18:58 +0000 (20:18 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 12 Sep 2025 21:44:54 +0000 (23:44 +0200)
src/hooks/dhcp/radius/cfg_attribute.cc
src/hooks/dhcp/radius/cfg_attribute.h
src/hooks/dhcp/radius/client_attribute.cc
src/hooks/dhcp/radius/client_attribute.h
src/hooks/dhcp/radius/tests/attribute_unittests.cc
src/hooks/dhcp/radius/tests/dictionary_unittests.cc

index 81a855d4621dcf3c756ac84c4d3737b1e00abdaf..54bd2c814dbf66aa11400c9b245cbc0ee6ab3496 100644 (file)
@@ -28,13 +28,12 @@ CfgAttributes::add(const AttrDefPtr& def,
     if (!def) {
         isc_throw(BadValue, "no attribute definition");
     }
-    container_.insert(pair<const uint8_t, AttributeValue>
-                (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<int>(it.first)));
-            map->set("expr", Element::create(it.second.test_));
+            map->set("type", Element::create(static_cast<int>(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);
     }
index c8dd89c98896e9a9ba5cdb4eba35a06c5dccb2c6..d97b6d67315270c94b4bc9296bf3b8e5353fac60 100644 (file)
 #include <dhcp/pkt.h>
 #include <eval/token.h>
 #include <util/buffer.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/composite_key.hpp>
 #include <boost/shared_ptr.hpp>
 #include <vector>
 #include <map>
@@ -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<uint8_t, AttributeValue> 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
index 7840bd4b97f85d62a124c86c6ec62098d2dbf8e7..bb77f5e64656e67985f007f303db5a7e7ce7abcf 100644 (file)
@@ -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<uint8_t> 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<uint8_t>(def->value_type_)) {
     case PW_TYPE_STRING:
         return (AttrString::fromText(def->type_, value));
@@ -92,6 +103,17 @@ Attribute::fromBytes(const AttrDefPtr& def, const vector<uint8_t>& 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<uint8_t> vsa_data = attr->toBytes();
+    return (fromVsa(PW_VENDOR_SPECIFIC, def->vendor_, vsa_data));
+}
+
+AttributePtr
+Attribute::fromBytes0(const AttrDefPtr& def, const vector<uint8_t>& value) {
     switch (static_cast<uint8_t>(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<uint8_t>
 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<uint8_t>
+AttrVsa::toVsaData() const {
+    vector<uint8_t> 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();
index d98ee9d601312520ce39d538a888a9b242e7368a..db00b2e2e5ca9710688d07dc2e2c6a59ca72f36f 100644 (file)
@@ -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<uint8_t> 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<uint8_t>& 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<uint8_t> toVsaData() const override;
 
     /// @brief Unparse attribute.
     ///
index e7f51066dd845d24b6e5dabf3d742c37cd2b0c5f..ee97ea0d56ef9b0d777f47bf97b5dc14073664b7 100644 (file)
@@ -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<uint8_t> 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<uint8_t> 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<uint8_t> 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;
index 9cbacbe61efb4cd994bb3d48092a502036ab50f5..6d2115e39daac99648a7781313aaef538fa60d60 100644 (file)
@@ -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);
 }