Skip to content
Prev Previous commit
Next Next commit
[#3860] Checkpoint: doing vsa
  • Loading branch information
fxdupont committed Aug 21, 2025
commit ab8bffed18ced2556d7e9d629d819239daf72799
114 changes: 112 additions & 2 deletions src/hooks/dhcp/radius/client_attribute.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down Expand Up @@ -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 "
Expand Down Expand Up @@ -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()));
}

Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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) {
Expand Down
157 changes: 155 additions & 2 deletions src/hooks/dhcp/radius/client_attribute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -159,6 +162,28 @@ class Attribute : public data::CfgToElement {
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.
Expand All @@ -184,13 +209,13 @@ class Attribute : public data::CfgToElement {
/// @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.
Expand Down Expand Up @@ -223,6 +248,20 @@ class Attribute : public data::CfgToElement {
/// @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_;
};
Expand All @@ -233,6 +272,7 @@ class Attribute : public data::CfgToElement {
/// @brief RADIUS attribute holding strings.
class AttrString : public Attribute {
protected:

/// @brief Constructor.
///
/// @param type attribute type.
Expand Down Expand Up @@ -648,6 +688,119 @@ class AttrIpv6Prefix : public Attribute {
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:
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/dhcp/radius/client_dictionary.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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?");
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/dhcp/radius/client_dictionary.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading