2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 13:37:55 +00:00

[#3074] introduce new OPT_CUSTOM_TYPE

This commit is contained in:
Piotrek Zadroga
2023-11-21 22:09:26 +01:00
parent e936f2d376
commit 475a349a16
7 changed files with 108 additions and 86 deletions

View File

@@ -18,8 +18,10 @@ namespace isc {
namespace dhcp {
OptionClasslessStaticRoute::OptionClasslessStaticRoute(OptionBufferConstIter begin,
OptionBufferConstIter end)
: Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0) {
OptionBufferConstIter end,
bool convenient_notation)
: Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0),
convenient_notation_(convenient_notation) {
unpack(begin, end);
}
@@ -51,30 +53,17 @@ OptionClasslessStaticRoute::unpack(OptionBufferConstIter begin, OptionBufferCons
<< ", must be at least 5.");
}
// As an alternative to the binary format,
// we provide convenience option definition as a string in format:
// subnet1 - router1 IP addr, subnet2 - router2 IP addr, ...
// e.g.:
// 10.0.0.0/8 - 10.2.3.1, 10.229.0.128/25 - 10.1.0.3, ...
// where destination descriptors will be encoded as per RFC3442.
// We need to determine if OptionBuffer contains dash `-` separator (0x2d).
// If not, we assume this is binary format.
auto begin_copy = begin;
while (begin_copy != end) {
if (*begin_copy == '-') {
break;
}
++begin_copy;
}
if (begin_copy == end) {
// no separator found, assuming this is a hex on-wire data
parseWireData(begin, end);
} else {
// separator was found, assuming this is option data string from config
if (convenient_notation_) {
// As an alternative to the binary format,
// we provide convenience option definition as a string in format:
// subnet1 - router1 IP addr, subnet2 - router2 IP addr, ...
// e.g.:
// 10.0.0.0/8 - 10.2.3.1, 10.229.0.128/25 - 10.1.0.3, ...
// where destination descriptors will be encoded as per RFC3442.
std::string config_txt = std::string(begin, end);
parseConfigData(config_txt);
} else {
parseWireData(begin, end);
}
calcDataLen();

View File

@@ -22,7 +22,8 @@ class OptionClasslessStaticRoute : public Option {
public:
/// @brief Empty Constructor
OptionClasslessStaticRoute()
: Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0) {
: Option(V4, DHO_CLASSLESS_STATIC_ROUTE), static_routes_(), data_len_(0),
convenient_notation_(false) {
}
/// @brief Constructor of the %Option from on-wire data.
@@ -33,7 +34,10 @@ public:
/// @param begin Iterator pointing to the beginning of the buffer holding an
/// option.
/// @param end Iterator pointing to the end of the buffer holding an option.
OptionClasslessStaticRoute(OptionBufferConstIter begin, OptionBufferConstIter end);
/// @param convenient_notation
OptionClasslessStaticRoute(OptionBufferConstIter begin,
OptionBufferConstIter end,
bool convenient_notation = false);
/// @brief Copies this option and returns a pointer to the copy.
///
@@ -83,6 +87,9 @@ private:
/// @brief Length in octets of all encoded static routes.
uint16_t data_len_;
/// @brief
bool convenient_notation_;
/// @brief Encodes destination descriptor as per RFC3442.
/// @param route static route tuple
/// @return Contents of the destination descriptor as a vector

View File

@@ -59,6 +59,7 @@ enum OptionDataType {
OPT_STRING_TYPE,
OPT_TUPLE_TYPE,
OPT_FQDN_TYPE,
OPT_CUSTOM_TYPE,
OPT_RECORD_TYPE,
OPT_UNKNOWN_TYPE
};

View File

@@ -33,6 +33,7 @@
#include <dns/name.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/make_shared.hpp>
@@ -182,9 +183,11 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
}
OptionPtr
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionDefinition::optionFactory(Option::Universe u,
uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
OptionBufferConstIter end,
bool custom_data) const {
try {
// Some of the options are represented by the specialized classes derived
@@ -193,7 +196,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
// type to be returned. Therefore, we first check that if we are dealing
// with such an option. If the instance is returned we just exit at this
// point. If not, we will search for a generic option type to return.
OptionPtr option = factorySpecialFormatOption(u, begin, end);
OptionPtr option = factorySpecialFormatOption(u, begin, end, custom_data);
if (option) {
return (option);
}
@@ -207,6 +210,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
case OPT_BINARY_TYPE:
case OPT_CUSTOM_TYPE:
return (factoryGeneric(u, type, begin, end));
case OPT_UINT8_TYPE:
@@ -306,6 +310,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
if (type_ != OPT_EMPTY_TYPE) {
isc_throw(InvalidOptionValue, "no option value specified");
}
} else if (type_ == OPT_CUSTOM_TYPE) {
writeToBuffer(u, boost::algorithm::join(values, ","), OPT_STRING_TYPE, buf);
} else {
writeToBuffer(u, util::str::trim(values[0]), type_, buf);
}
@@ -330,7 +336,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
return (optionFactory(u, type, buf.begin(), buf.end(), (type_ == OPT_CUSTOM_TYPE)));
}
void
@@ -824,7 +830,8 @@ OptionDefinition::factoryFqdnList(Option::Universe u,
OptionPtr
OptionDefinition::factorySpecialFormatOption(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
OptionBufferConstIter end,
bool custom_data) const {
if ((u == Option::V6) && haveSpace(DHCP6_OPTION_SPACE)) {
switch (getCode()) {
case D6O_IA_NA:
@@ -886,7 +893,7 @@ OptionDefinition::factorySpecialFormatOption(Option::Universe u,
return (OptionPtr(new Option4ClientFqdn(begin, end)));
case DHO_CLASSLESS_STATIC_ROUTE:
return (OptionPtr(new OptionClasslessStaticRoute(begin, end)));
return (OptionPtr(new OptionClasslessStaticRoute(begin, end, custom_data)));
case DHO_VIVCO_SUBOPTIONS:
// Record of uint32 followed by binary.

View File

@@ -433,12 +433,15 @@ public:
/// @param type option type.
/// @param begin beginning of the option buffer.
/// @param end end of the option buffer.
/// @param custom_data
///
/// @return instance of the DHCP option.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
OptionPtr optionFactory(Option::Universe u,
uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const;
OptionBufferConstIter end,
bool custom_data = false) const;
/// @brief Option factory.
///
@@ -670,13 +673,15 @@ private:
/// @param u A universe (V4 or V6).
/// @param begin beginning of the option buffer.
/// @param end end of the option buffer.
/// @param custom_data
///
/// @return An instance of the option having special format or NULL if
/// such an option can't be created because an option with the given
/// option code hasn't got the special format.
OptionPtr factorySpecialFormatOption(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end) const;
OptionBufferConstIter end,
bool custom_data = false) const;
/// @brief Check if specified type matches option definition type.
///

View File

@@ -332,7 +332,7 @@ const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "domain-search", DHO_DOMAIN_SEARCH, DHCP4_OPTION_SPACE, OPT_FQDN_TYPE,
true, NO_RECORD_DEF, "" },
{ "classless-static-route", DHO_CLASSLESS_STATIC_ROUTE, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE,
{ "classless-static-route", DHO_CLASSLESS_STATIC_ROUTE, DHCP4_OPTION_SPACE, OPT_CUSTOM_TYPE,
false, NO_RECORD_DEF, "" },
{ "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, DHCP4_OPTION_SPACE,
OPT_RECORD_TYPE, false, RECORD_DEF(VIVCO_RECORDS), "" },

View File

@@ -79,12 +79,13 @@ TEST(OptionClasslessStaticRouteTest, emptyCtorAddMoreRoutes) {
// Only one static route is defined.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithOneRoute) {
// Prepare data to decode - one route with mask width = 8.
const std::string config = "'10.0.0.0/8 - 10.198.122.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "10.0.0.0/8 - 10.198.122.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
OptionClasslessStaticRoutePtr option;
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)));
ASSERT_TRUE(option);
// Expected len: 2 (option code + option len headers) + 6 (2 dest descriptor + 4 router addr).
@@ -100,13 +101,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithOneRoute) {
// 3 static routes are defined.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithMoreRoutes) {
// Prepare data to decode - 3 static routes
const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
OptionClasslessStaticRoutePtr option;
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)));
ASSERT_TRUE(option);
// Expected len: 2 (option code + option len headers) + 5 (1 dest descriptor + 4 router addr)
@@ -125,13 +127,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorWithMoreRoutes) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingDash) {
// Prepare data to decode - second route has missing dash separator
const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,10.229.0.128/25 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -140,13 +143,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingDash) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingPrefixLen) {
// Prepare data to decode - second route has missing "/prefix len"
const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,10.229.0.128 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -155,13 +159,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorMissingPrefixLen) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestIpV6Given) {
// Prepare data to decode - second route has IPv6 prefix
const std::string config = "'0.0.0.0/0 - 10.17.0.1,3001::5/64 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,3001::5/64 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -170,13 +175,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestIpV6Given) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestInvalidAddr) {
// Prepare data to decode - second route has invalid IP address
const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.a/32 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,1.2.3.a/32 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -185,13 +191,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDestInvalidAddr) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorInvalidPrefixLen) {
// Prepare data to decode - second route has invalid prefix len
const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/a - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,1.2.3.4/a - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -200,13 +207,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorInvalidPrefixLen) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorPrefixLenTooBig) {
// Prepare data to decode - second route has prefix len too big for IPv4
const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/64 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,1.2.3.4/64 - 10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -215,13 +223,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorPrefixLenTooBig) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterInvalidAddr) {
// Prepare data to decode - second route has invalid router IP address
const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 10.229.0.a, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 10.229.0.a, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -230,13 +239,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterInvalidAddr) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterIpV6Given) {
// Prepare data to decode - second route has IPv6 router address
const std::string config = "'0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 3001::5, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,1.2.3.4/31 - 3001::5, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -245,13 +255,14 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorRouterIpV6Given) {
// when data in the buffer has wrong format.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorCommaMissing) {
// Prepare data to decode - comma separators are missing
const std::string config = "'0.0.0.0/0 - 10.17.0.1 1.2.3.4/31 - 1.2.3.4 "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1 1.2.3.4/31 - 1.2.3.4 "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws BadValue during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::BadValue);
ASSERT_FALSE(option);
}
@@ -260,12 +271,13 @@ TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorCommaMissing) {
// when data in the buffer is truncated.
TEST(OptionClasslessStaticRouteTest, bufferFromStrCtorDataTruncated) {
// Prepare data to decode - truncated data
const std::string config = "'0.0.'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Create option instance. Check that constructor throws OutOfRange during Unpack.
OptionClasslessStaticRoutePtr option;
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())),
EXPECT_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)),
isc::OutOfRange);
ASSERT_FALSE(option);
}
@@ -420,9 +432,10 @@ TEST(OptionClasslessStaticRouteTest, toText) {
// This test verifies pack() method
TEST(OptionClasslessStaticRouteTest, pack) {
// Prepare data to decode - 3 static routes
const std::string config = "'0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1'";
OptionBuffer buf = isc::util::str::quotedStringToBinary(config);
const std::string config = "0.0.0.0/0 - 10.17.0.1,10.229.0.128/25-10.229.0.1, "
"10.27.129.0/24 - 10.27.129.1";
OptionBuffer buf;
buf.assign(config.begin(), config.end());
// Prepare expected packed data
const uint8_t ref_data[] = {
@@ -442,7 +455,7 @@ TEST(OptionClasslessStaticRouteTest, pack) {
// Create option instance. Check that constructor doesn't throw. Unpack is also tested here.
OptionClasslessStaticRoutePtr option;
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end())));
EXPECT_NO_THROW(option.reset(new OptionClasslessStaticRoute(buf.begin(), buf.end(), true)));
ASSERT_TRUE(option);
// Prepare on-wire format of the option.