]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3860] Checkpoint: doing vsa
authorFrancis Dupont <fdupont@isc.org>
Wed, 20 Aug 2025 23:00:46 +0000 (01:00 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 12 Sep 2025 21:44:54 +0000 (23:44 +0200)
src/hooks/dhcp/radius/client_attribute.cc
src/hooks/dhcp/radius/client_attribute.h
src/hooks/dhcp/radius/client_dictionary.cc
src/hooks/dhcp/radius/client_dictionary.h
src/hooks/dhcp/radius/tests/attribute_unittests.cc
src/hooks/dhcp/radius/tests/dictionary_unittests.cc

index 403f283d16644091890d046d9561d74147012b3a..41aac45726ed3366b55305a05be8a2c9b51430ff 100644 (file)
@@ -80,6 +80,8 @@ Attribute::fromText(const AttrDefPtr& def, const string& value) {
         return (AttrIpv6Addr::fromText(def->type_, value));
     case PW_TYPE_IPV6PREFIX:
         return (AttrIpv6Prefix::fromText(def->type_, value));
+    case PW_TYPE_VSA:
+        return (AttrVSA::fromText(def->type_, value));
     default:
         // Impossible case.
         isc_throw(OutOfRange, "unknown value type "
@@ -131,6 +133,8 @@ Attribute::fromBytes(const AttrDefPtr& def, const vector<uint8_t>& value) {
         return (AttrIpv6Addr::fromBytes(def->type_, value));
     case PW_TYPE_IPV6PREFIX:
         return (AttrIpv6Prefix::fromBytes(def->type_, value));
+    case PW_TYPE_VSA:
+        return (AttrVSA::fromBytes(def->type_, value));
     default:
         // Impossible case.
         isc_throw(OutOfRange, "unknown value type "
@@ -177,13 +181,13 @@ Attribute::fromIpv6Prefix(const uint8_t type, const uint8_t len,
 
 string
 Attribute::toString() const {
-    isc_throw(TypeError, "the attribute value type must be string, not "
+    isc_throw(TypeError, "the attribute value type must be string or vsa, not "
               << attrValueTypeToText(getValueType()));
 }
 
 vector<uint8_t>
 Attribute::toBinary() const {
-    isc_throw(TypeError, "the attribute value type must be string, not "
+    isc_throw(TypeError, "the attribute value type must be string or vsa, not "
               << attrValueTypeToText(getValueType()));
 }
 
@@ -217,6 +221,18 @@ Attribute::toIpv6PrefixLen() const {
               << attrValueTypeToText(getValueType()));
 }
 
+uint32_t
+Attribute::toVendorId() const {
+    isc_throw(TypeError, "the attribute value type must be vsa, not "
+              << attrValueTypeToText(getValueType()));
+}
+
+void
+Attribute::setVendorId(const uint32_t vendor) {
+    isc_throw(TypeError, "the attribute value type must be vsa, not "
+              << attrValueTypeToText(getValueType()));
+}
+
 AttrString::AttrString(const uint8_t type, const vector<uint8_t>& value)
     : Attribute(type), value_() {
     if (value.empty()) {
@@ -612,6 +628,100 @@ AttrIpv6Prefix::toElement() const {
     return (output);
 }
 
+AttrVSA::AttrVSA(const uint8_t type, const int32_t vendor,
+                 const vector<uint8_t>& value)
+    : Attribute(type), vendor_(vendor), value_() {
+    if (value.empty()) {
+        isc_throw(BadValue, "value is empty");
+    }
+    if (value.size() > MAX_VSA_DATA_LEN) {
+        isc_throw(BadValue, "value is too large " << value.size()
+                  << " > " << MAX_VSA_DATA_LEN);
+    }
+    value_.resize(value.size());
+    memmove(&value_[0], &value[0], value_.size());
+}
+
+AttributePtr
+AttrVSA::fromText(const uint8_t type, const string& repr) {
+    isc_throw(NotImplemented, "Can't decode VSA from text");
+}
+
+AttributePtr
+AttrVSA::fromBytes(const uint8_t type, const vector<uint8_t>& bytes) {
+    if (bytes.empty()) {
+        isc_throw(BadValue, "empty attribute value");
+    }
+    if (bytes.size() < 5) {
+        isc_throw(BadValue, "value is too small " << bytes.size() << " < 5");
+    } else if (bytes.size() > MAX_STRING_LEN) {
+        isc_throw(BadValue, "value is too large " << bytes.size()
+                  << " > " << MAX_STRING_LEN);
+    }
+    uint32_t vendor = bytes[0] << 24;
+    vendor |= bytes[1] << 16;
+    vendor |= bytes[2] << 8;
+    vendor |= bytes[3];
+    vector<uint8_t> value;
+    value.resize(bytes.size() - 4);
+    if (value.size() > 0) {
+        memmove(&value[0], &bytes[4], value.size());
+    }
+    return (AttributePtr(new AttrVSA(type, vendor, value)));
+}
+
+string
+AttrVSA::toText(size_t indent) const {
+    isc_throw(NotImplemented, "Can't encode VSA into text");
+}
+
+std::vector<uint8_t>
+AttrVSA::toBytes() const {
+    vector<uint8_t> output;
+    output.resize(2 + getValueLen());
+    output[0] = getType();
+    output[1] = 2 + getValueLen();
+    output[2] = (vendor_ & 0xff000000U) >> 24;
+    output[3] = (vendor_ & 0xff0000U) >> 16;
+    output[4] = (vendor_ & 0xff00U) >> 8;
+    output[5] = vendor_ & 0xffU;
+    if (output.size() > 6) {
+        memmove(&output[6], &value_[0], output.size() - 6);
+    }
+    return (output);
+}
+
+std::vector<uint8_t>
+AttrVSA::toBinary() const {
+    vector<uint8_t> binary;
+    binary.resize(getValueLen() - 4);
+    if (binary.size() > 0) {
+        memmove(&binary[0], &value_[0], binary.size());
+    }
+    return (binary);
+}
+
+ElementPtr
+AttrVSA::toElement() const {
+    ElementPtr output = Element::createMap();
+    AttrDefPtr def = AttrDefs::instance().getByType(getType());
+    if (def) {
+        output->set("name", Element::create(def->name_));
+    }
+    output->set("type", Element::create(static_cast<int>(getType())));
+    ostringstream vendor;
+    vendor << vendor_;
+    output->set("vendor", Element::create(vendor.str()));
+    vector<uint8_t> binary;
+    binary.resize(value_.size());
+    if (binary.size() > 0) {
+        memmove(&binary[0], value_.c_str(), binary.size());
+    }
+    string raw = encode::encodeHex(binary);
+    output->set("vsa-raw", Element::create(raw));
+    return (output);
+}
+
 void
 Attributes::add(const ConstAttributePtr& attr) {
     if (!attr) {
index 5e7d642f23241c6b9db5dd21f6876326f7e3d88b..ac13cfbd790e7e10fe69da5173a3c2500aae256f 100644 (file)
@@ -28,6 +28,9 @@ namespace radius {
 /// @brief Maximum string size.
 static constexpr size_t MAX_STRING_LEN = 253;
 
+/// @brief Maximum vsa data size.
+static constexpr size_t MAX_VSA_DATA_LEN = MAX_STRING_LEN - 4;
+
 /// @brief Type error.
 using isc::data::TypeError;
 
@@ -159,6 +162,28 @@ public:
                                        const uint8_t len,
                                        const asiolink::IOAddress& value);
 
+    /// @brief From Vendor ID and string data with type.
+    ///
+    /// @note Requires the type to be of the Vendor Specific attribute (26).
+    ///
+    /// @param type type of attribute.
+    /// @param vendor vendor id.
+    /// @param value vsa data.
+    static AttributePtr fromVSA(const uint8_t type,
+                                const uint32_t vendor,
+                                const std::string& value);
+
+    /// @brief From Vendor ID and binary data with type.
+    ///
+    /// @note Requires the type to be of the Vendor Specific attribute (26).
+    ///
+    /// @param type type of attribute.
+    /// @param vendor vendor id.
+    /// @param value vsa data.
+    static AttributePtr fromVSA(const uint8_t type,
+                                const uint32_t vendor,
+                                const std::vector<uint8_t>& value);
+
     /// Generic get methods.
 
     /// @brief Value length.
@@ -184,13 +209,13 @@ public:
     /// @brief To string.
     ///
     /// @return the string value.
-    /// @throw TypeError if the attribute is not a string one.
+    /// @throw TypeError if the attribute is not a string or vsa one.
     virtual std::string toString() const;
 
     /// @brief To binary.
     ///
     /// @return the string value as a binary.
-    /// @throw TypeError if the attribute is not a string one.
+    /// @throw TypeError if the attribute is not a string or vsa one.
     virtual std::vector<uint8_t> toBinary() const;
 
     /// @brief To integer.
@@ -223,6 +248,20 @@ public:
     /// @throw TypeError if the attribute is not an ipv6prefix one.
     virtual uint8_t toIpv6PrefixLen() const;
 
+    /// @brief To vendor id.
+    ///
+    /// @return the vendor id.
+    /// @throw TypeError if the attribute is not a vsa one.
+    virtual uint32_t toVendorId() const;
+
+    /// Generic set methods.
+
+    /// @brief Set vendor id.
+    ///
+    /// @param vendor vendor id.
+    /// @throw TypeError if the attribute is not a vsa one.
+    virtual void setVendorId(const uint32_t vendor);
+
     /// @brief Type.
     const uint8_t type_;
 };
@@ -233,6 +272,7 @@ public:
 /// @brief RADIUS attribute holding strings.
 class AttrString : public Attribute {
 protected:
+
     /// @brief Constructor.
     ///
     /// @param type attribute type.
@@ -648,6 +688,119 @@ private:
     asiolink::IOAddress value_;
 };
 
+/// @brief RADIUS attribute holding vsa.
+class AttrVSA : public Attribute {
+protected:
+
+    /// @brief Constructor.
+    ///
+    /// @param type attribute type.
+    /// @param vendor vendor id.
+    /// @param value string vsa data.
+    AttrVSA(const uint8_t type, const int32_t vendor, const std::string& value)
+        : Attribute(type), vendor_(vendor), value_(value) {
+        if (value.empty()) {
+            isc_throw(BadValue, "value is empty");
+        }
+        if (value.size() > MAX_VSA_DATA_LEN) {
+            isc_throw(BadValue, "value is too large " << value.size()
+                      << " > " << MAX_VSA_DATA_LEN);
+        }
+    }
+
+    /// @brief Constructor.
+    ///
+    /// @param type attribute type.
+    /// @param vendor vendor id.
+    /// @param value binary vsa data.
+    AttrVSA(const uint8_t type, const int32_t vendor,
+            const std::vector<uint8_t>& value);
+
+    /// @brief From text.
+    ///
+    /// @param type attribute type.
+    /// @param repr value representation.
+    /// @return pointer to the attribute or null.
+    static AttributePtr fromText(const uint8_t type, const std::string& repr);
+
+    /// @brief From bytes.
+    ///
+    /// @param type attribute type.
+    /// @param bytes binary value.
+    /// @return pointer to the attribute or null.
+    static AttributePtr fromBytes(const uint8_t type,
+                                  const std::vector<uint8_t>& bytes);
+
+    /// Make Attribute a friend class.
+    friend class Attribute;
+
+public:
+
+    /// @brief Get value type.
+    ///
+    /// @return the value type.
+    virtual AttrValueType getValueType() const override {
+        return (PW_TYPE_VSA);
+    }
+
+    /// @brief Value length.
+    ///
+    /// @return Value length.
+    virtual size_t getValueLen() const override {
+        return (4 + value_.size());
+    }
+
+    /// @brief Returns text representation of the attribute.
+    ///
+    /// @param indent number of spaces before printing text.
+    /// @return string with text representation.
+    virtual std::string toText(size_t indent = 0) const override;
+
+    /// @brief To bytes.
+    ///
+    /// @return binary representation.
+    virtual std::vector<uint8_t> toBytes() const override;
+
+    /// @brief To string.
+    ///
+    /// @return the string value.
+    virtual std::string toString() const override {
+        return (value_);
+    }
+
+    /// @brief To binary.
+    ///
+    /// @return the string value as a binary.
+    virtual std::vector<uint8_t> toBinary() const override;
+
+    /// @brief To vendor id.
+    ///
+    /// @return the vendor id.
+    virtual uint32_t toVendorId() const override {
+        return (vendor_);
+    }
+
+    /// @brief Set vendor id.
+    ///
+    /// @param vendor vendor id.
+    virtual void setVendorId(const uint32_t vendor) override {
+        vendor_ = vendor;
+    }
+
+    /// @brief Unparse attribute.
+    ///
+    /// @return a pointer to unparsed attribute.
+    virtual data::ElementPtr toElement() const override;
+
+private:
+    /// @brief Vendor id.
+    uint32_t vendor_;
+
+    /// @brief Value.
+    std::string value_;
+};
+
+
 /// @brief Collection of attributes.
 class Attributes : public data::CfgToElement {
 public:
index 01f7b9b6419431cb8ae3cf664ad2f74dd3068af5..988ab2f2ff19068c5c468af9e039606588b5522c 100644 (file)
@@ -34,6 +34,8 @@ attrValueTypeToText(const AttrValueType value) {
         return ("ipv6addr");
     case PW_TYPE_IPV6PREFIX:
         return ("ipv6prefix");
+    case PW_TYPE_VSA:
+        return ("vsa");
     default:
         // Impossible case.
         return ("unknown?");
@@ -52,6 +54,8 @@ textToAttrValueType(const string& name) {
         return (PW_TYPE_IPV6ADDR);
     } else if (name == "ipv6prefix") {
         return (PW_TYPE_IPV6PREFIX);
+    } else if (name == "vsa") {
+        return (PW_TYPE_VSA);
     } else {
         isc_throw(OutOfRange, "unknown AttrValueType name " << name);
     }
@@ -230,6 +234,10 @@ AttrDefs::parseLine(const string& line, unsigned int depth) {
             isc_throw(Unexpected, "can't parse attribute type " << type_str);
         }
         AttrValueType value_type = textToAttrValueType(tokens[3]);
+        if ((value_type == PW_TYPE_VSA) && (type != PW_VENDOR_SPECIFIC)) {
+            isc_throw(BadValue, "only Vendor-Specific (26) attribute can "
+                      << "have the vsa data type");
+        }
         AttrDefPtr def(new AttrDef(type, name, value_type));
         add(def);
         return;
index 21ee11b9b1557a7958d7bb4b117b8ce349c5ffe9..4b6824faeea9edea814509af7d815dbdbd258738 100644 (file)
@@ -31,7 +31,8 @@ enum AttrValueType {
     PW_TYPE_INTEGER,
     PW_TYPE_IPADDR,
     PW_TYPE_IPV6ADDR,
-    PW_TYPE_IPV6PREFIX
+    PW_TYPE_IPV6PREFIX,
+    PW_TYPE_VSA
 };
 
 /// @brief AttrValueType value -> name function.
index 2d72b05c51491a48f69c38d7e47748ede6362e5c..1bc3e32c900727cbc9d6fcb44d23684508ce4e38 100644 (file)
@@ -175,6 +175,8 @@ TEST_F(AttributeTest, attrString) {
                      "the attribute value type must be ipv6prefix, not string");
     EXPECT_THROW_MSG(attr->toIpv6PrefixLen(), TypeError,
                      "the attribute value type must be ipv6prefix, not string");
+    EXPECT_THROW_MSG(attr->toVendorId(), TypeError,
+                     "the attribute value type must be vsa, not string");
 }
 
 // Verifies raw string attribute.
@@ -268,9 +270,9 @@ TEST_F(AttributeTest, attrInt) {
         << def_bytes->toText() << " != " << attr->toText();
 
     EXPECT_THROW_MSG(attr->toString(), TypeError,
-                     "the attribute value type must be string, not integer");
+                     "the attribute value type must be string or vsa, not integer");
     EXPECT_THROW_MSG(attr->toBinary(), TypeError,
-                     "the attribute value type must be string, not integer");
+                     "the attribute value type must be string or vsa, not integer");
     EXPECT_THROW_MSG(attr->toIpAddr(), TypeError,
                      "the attribute value type must be ipaddr, not integer");
     EXPECT_THROW_MSG(attr->toIpv6Addr(), TypeError,
@@ -278,7 +280,10 @@ TEST_F(AttributeTest, attrInt) {
     EXPECT_THROW_MSG(attr->toIpv6Prefix(), TypeError,
                      "the attribute value type must be ipv6prefix, not integer");
     EXPECT_THROW_MSG(attr->toIpv6PrefixLen(), TypeError,
-                     "the attribute value type must be ipv6prefix, not integer");
+                     "the attribute value type must be ipv6prefix, not integer"
+);
+    EXPECT_THROW_MSG(attr->toVendorId(), TypeError,
+                     "the attribute value type must be vsa, not integer");
 }
 
 // Verifies IP address attribute.
@@ -338,9 +343,9 @@ TEST_F(AttributeTest, attrIpAddr) {
         << def_bytes->toText() << " != " << attr->toText();
 
     EXPECT_THROW_MSG(attr->toString(), TypeError,
-                     "the attribute value type must be string, not ipaddr");
+                     "the attribute value type must be string or vsa, not ipaddr");
     EXPECT_THROW_MSG(attr->toBinary(), TypeError,
-                     "the attribute value type must be string, not ipaddr");
+                     "the attribute value type must be string or vsa, not ipaddr");
     EXPECT_THROW_MSG(attr->toInt(), TypeError,
                      "the attribute value type must be integer, not ipaddr");
     EXPECT_THROW_MSG(attr->toIpv6Addr(), TypeError,
@@ -349,6 +354,8 @@ TEST_F(AttributeTest, attrIpAddr) {
                      "the attribute value type must be ipv6prefix, not ipaddr");
     EXPECT_THROW_MSG(attr->toIpv6PrefixLen(), TypeError,
                      "the attribute value type must be ipv6prefix, not ipaddr");
+    EXPECT_THROW_MSG(attr->toVendorId(), TypeError,
+                     "the attribute value type must be vsa, not ipaddr");
 }
 
 // Verifies IPv6 address attribute.
@@ -415,9 +422,9 @@ TEST_F(AttributeTest, attrIpv6Addr) {
         << def_bytes->toText() << " != " << attr->toText();
 
     EXPECT_THROW_MSG(attr->toString(), TypeError,
-                     "the attribute value type must be string, not ipv6addr");
+                     "the attribute value type must be string or vsa, not ipv6addr");
     EXPECT_THROW_MSG(attr->toBinary(), TypeError,
-                     "the attribute value type must be string, not ipv6addr");
+                     "the attribute value type must be string or vsa, not ipv6addr");
     EXPECT_THROW_MSG(attr->toInt(), TypeError,
                      "the attribute value type must be integer, not ipv6addr");
     EXPECT_THROW_MSG(attr->toIpAddr(), TypeError,
@@ -426,6 +433,8 @@ TEST_F(AttributeTest, attrIpv6Addr) {
                      "the attribute value type must be ipv6prefix, not ipv6addr");
     EXPECT_THROW_MSG(attr->toIpv6PrefixLen(), TypeError,
                      "the attribute value type must be ipv6prefix, not ipv6addr");
+    EXPECT_THROW_MSG(attr->toVendorId(), TypeError,
+                     "the attribute value type must be vsa, not ipv6addr");
 }
 
 // Verifies IPv6 prefix attribute.
@@ -524,15 +533,17 @@ TEST_F(AttributeTest, attrIpv6Prefix) {
         << def_bytes->toText() << " != " << attr->toText();
 
     EXPECT_THROW_MSG(attr->toString(), TypeError,
-                     "the attribute value type must be string, not ipv6prefix");
+                     "the attribute value type must be string or vsa, not ipv6prefix");
     EXPECT_THROW_MSG(attr->toBinary(), TypeError,
-                     "the attribute value type must be string, not ipv6prefix");
+                     "the attribute value type must be string or vsa, not ipv6prefix");
     EXPECT_THROW_MSG(attr->toInt(), TypeError,
                      "the attribute value type must be integer, not ipv6prefix");
     EXPECT_THROW_MSG(attr->toIpAddr(), TypeError,
                      "the attribute value type must be ipaddr, not ipv6prefix");
     EXPECT_THROW_MSG(attr->toIpv6Addr(), TypeError,
                      "the attribute value type must be ipv6addr, not ipv6prefix");
+    EXPECT_THROW_MSG(attr->toVendorId(), TypeError,
+                     "the attribute value type must be vsa, not ipv6prefix");
 }
 
 // Verifies basic methods for attribute collection.
index 2fbd014bc907faf1434a748c0d96a12681efc144..a0589478d300d62fe819a570d6baf6b1253300d7 100644 (file)
@@ -180,6 +180,19 @@ TEST_F(DictionaryTest, parseLines) {
     expected = "Illegal attribute redefinition of 'Service-Type' ";
     expected += "type 6 value type integer by 6 string at line 2";
     EXPECT_THROW_MSG(parseLines(new_value_type), BadValue, expected);
+
+    // Only the attribute 26 (Vendor-Specific) can have the vsa data type.
+    list<string> bad_vsa = {
+        "ATTRIBUTE Attr126 126 vsa"
+    };
+    expected = "only Vendor-Specific (26) attribute can have ";
+    expected += "the vsa data type at line 1";
+    EXPECT_THROW_MSG(parseLines(bad_vsa), BadValue, expected);
+
+    list<string> vsa = {
+        "ATTRIBUTE Attr26 26 vsa"
+    };
+    EXPECT_NO_THROW_LOG(parseLines(vsa));
 }
 
 // Verifies integer constant definitions.