Commit cf56fc2a authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac4301'

parents bb00d9d2 253b032f
...@@ -3454,7 +3454,7 @@ TEST_F(Dhcp4ParserTest, reservations) { ...@@ -3454,7 +3454,7 @@ TEST_F(Dhcp4ParserTest, reservations) {
" ]" " ]"
" }," " },"
" {" " {"
" \"hw-address\": \"06:05:04:03:02:01\"," " \"circuit-id\": \"060504030201\","
" \"ip-address\": \"192.0.4.102\"," " \"ip-address\": \"192.0.4.102\","
" \"hostname\": \"\"" " \"hostname\": \"\""
" }" " }"
...@@ -3528,16 +3528,19 @@ TEST_F(Dhcp4ParserTest, reservations) { ...@@ -3528,16 +3528,19 @@ TEST_F(Dhcp4ParserTest, reservations) {
ASSERT_TRUE(opt_ttl); ASSERT_TRUE(opt_ttl);
EXPECT_EQ(32, static_cast<int>(opt_ttl->getValue())); EXPECT_EQ(32, static_cast<int>(opt_ttl->getValue()));
// The HW address used for one of the reservations in the subnet 542 // The circuit-id used for one of the reservations in the subnet 542
// consists of numbers from 6 to 1. So, let's just reverse the order // consists of numbers from 6 to 1. So, let's just reverse the order
// of the address from the previous test. // of the address from the previous test.
hwaddr->hwaddr_.assign(hwaddr_vec.rbegin(), hwaddr_vec.rend()); std::vector<uint8_t> circuit_id(hwaddr_vec.rbegin(), hwaddr_vec.rend());
host = hosts_cfg->get4(542, hwaddr); host = hosts_cfg->get4(542, Host::IDENT_CIRCUIT_ID, &circuit_id[0],
circuit_id.size());
EXPECT_TRUE(host); EXPECT_TRUE(host);
EXPECT_EQ("192.0.4.102", host->getIPv4Reservation().toText()); EXPECT_EQ("192.0.4.102", host->getIPv4Reservation().toText());
// This reservation must not belong to other subnets. // This reservation must not belong to other subnets.
EXPECT_FALSE(hosts_cfg->get4(123, hwaddr)); EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_CIRCUIT_ID,
EXPECT_FALSE(hosts_cfg->get4(234, hwaddr)); &circuit_id[0], circuit_id.size()));
EXPECT_FALSE(hosts_cfg->get4(234, Host::IDENT_CIRCUIT_ID,
&circuit_id[0], circuit_id.size()));
// Repeat the test for the DUID based reservation in this subnet. // Repeat the test for the DUID based reservation in this subnet.
duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(), duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(),
......
...@@ -6,11 +6,8 @@ ...@@ -6,11 +6,8 @@
#include <dhcp/duid.h> #include <dhcp/duid.h>
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <util/io_utilities.h> #include <util/io_utilities.h>
#include <boost/algorithm/string/classification.hpp> #include <util/strutil.h>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iomanip> #include <iomanip>
#include <cctype> #include <cctype>
#include <sstream> #include <sstream>
...@@ -42,57 +39,6 @@ DUID::DUID(const uint8_t* data, size_t len) { ...@@ -42,57 +39,6 @@ DUID::DUID(const uint8_t* data, size_t len) {
duid_ = std::vector<uint8_t>(data, data + len); duid_ = std::vector<uint8_t>(data, data + len);
} }
std::vector<uint8_t>
DUID::decode(const std::string& text) {
/// @todo optimize stream operations here.
std::vector<std::string> split_text;
boost::split(split_text, text, boost::is_any_of(":"),
boost::algorithm::token_compress_off);
std::ostringstream s;
for (size_t i = 0; i < split_text.size(); ++i) {
// Check that only hexadecimal digits are used.
size_t ch_index = 0;
while (ch_index < split_text[i].length()) {
if (!isxdigit(split_text[i][ch_index])) {
isc_throw(isc::BadValue, "invalid value '"
<< split_text[i][ch_index] << "' in"
<< " DUID '" << text << "'");
}
++ch_index;
}
if (split_text.size() > 1) {
// If there are multiple tokens and the current one is empty, it
// means that two consecutive colons were specified. This is not
// allowed for client identifier.
if (split_text[i].empty()) {
isc_throw(isc::BadValue, "invalid identifier '"
<< text << "': tokens must be"
" separated with a single colon");
} else if (split_text[i].size() > 2) {
isc_throw(isc::BadValue, "invalid identifier '"
<< text << "'");
}
}
if (split_text[i].size() % 2) {
s << "0";
}
s << split_text[i];
}
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create identifier from text '"
<< text << "': " << ex.what());
}
return (binary);
}
const std::vector<uint8_t>& DUID::getDuid() const { const std::vector<uint8_t>& DUID::getDuid() const {
return (duid_); return (duid_);
} }
...@@ -111,8 +57,9 @@ DUID::DUIDType DUID::getType() const { ...@@ -111,8 +57,9 @@ DUID::DUIDType DUID::getType() const {
DUID DUID
DUID::fromText(const std::string& text) { DUID::fromText(const std::string& text) {
std::vector<uint8_t> binary = decode(text); std::vector<uint8_t> binary;
return DUID(binary); util::str::decodeFormattedHexString(text, binary);
return (DUID(binary));
} }
DuidPtr DuidPtr
...@@ -185,7 +132,8 @@ std::string ClientId::toText() const { ...@@ -185,7 +132,8 @@ std::string ClientId::toText() const {
ClientIdPtr ClientIdPtr
ClientId::fromText(const std::string& text) { ClientId::fromText(const std::string& text) {
std::vector<uint8_t> binary = decode(text); std::vector<uint8_t> binary;
util::str::decodeFormattedHexString(text, binary);
return (ClientIdPtr(new ClientId(binary))); return (ClientIdPtr(new ClientId(binary)));
} }
......
...@@ -79,7 +79,6 @@ class DUID { ...@@ -79,7 +79,6 @@ class DUID {
/// @brief Create DUID from the textual format. /// @brief Create DUID from the textual format.
/// ///
/// This static function parses a DUID specified in the textual format. /// This static function parses a DUID specified in the textual format.
/// Internally it uses @c DUID::decode to parse the DUID.
/// ///
/// @param text DUID in the hexadecimal format with digits representing /// @param text DUID in the hexadecimal format with digits representing
/// individual bytes separated by colons. /// individual bytes separated by colons.
...@@ -98,22 +97,6 @@ class DUID { ...@@ -98,22 +97,6 @@ class DUID {
protected: protected:
/// @brief Decodes the textual format of the DUID.
///
/// The format being parsed should match the DUID representation returned
/// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
/// representing bytes of DUID must be separated by colons. Usually the
/// single byte is represented by two hexadecimal digits. However, this
/// function allows one digit per byte. In this case, a zero is prepended
/// before the conversion. For example, a DUID 0:1:2:3:4:5 equals to
/// 00:01:02:03:04:05.
///
/// @param text DUID in the hexadecimal format with digits representing
/// individual bytes separated by colons.
///
/// @throw isc::BadValue if parsing the DUID failed.
static std::vector<uint8_t> decode(const std::string& text);
/// The actual content of the DUID /// The actual content of the DUID
std::vector<uint8_t> duid_; std::vector<uint8_t> duid_;
}; };
......
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
...@@ -7,10 +7,7 @@ ...@@ -7,10 +7,7 @@
#include <dhcp/hwaddr.h> #include <dhcp/hwaddr.h>
#include <dhcp/dhcp4.h> #include <dhcp/dhcp4.h>
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <util/encode/hex.h> #include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <iomanip> #include <iomanip>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
...@@ -69,37 +66,8 @@ std::string HWAddr::toText(bool include_htype) const { ...@@ -69,37 +66,8 @@ std::string HWAddr::toText(bool include_htype) const {
HWAddr HWAddr
HWAddr::fromText(const std::string& text, const uint16_t htype) { HWAddr::fromText(const std::string& text, const uint16_t htype) {
/// @todo optimize stream operations here.
std::vector<std::string> split_text;
boost::split(split_text, text, boost::is_any_of(":"),
boost::algorithm::token_compress_off);
std::ostringstream s;
for (size_t i = 0; i < split_text.size(); ++i) {
// If there are multiple tokens and the current one is empty, it
// means that two consecutive colons were specified. This is not
// allowed for hardware address.
if ((split_text.size() > 1) && split_text[i].empty()) {
isc_throw(isc::BadValue, "failed to create hardware address"
" from text '" << text << "': tokens of the hardware"
" address must be separated with a single colon");
} else if (split_text[i].size() == 1) {
s << "0";
} else if (split_text[i].size() > 2) {
isc_throw(isc::BadValue, "invalid hwaddr '" << text << "'");
}
s << split_text[i];
}
std::vector<uint8_t> binary; std::vector<uint8_t> binary;
try { util::str::decodeColonSeparatedHexString(text, binary);
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create hwaddr from text '"
<< text << "': " << ex.what());
}
return (HWAddr(binary, htype)); return (HWAddr(binary, htype));
} }
......
...@@ -125,6 +125,23 @@ Host::getIdentifierType() const { ...@@ -125,6 +125,23 @@ Host::getIdentifierType() const {
return (identifier_type_); return (identifier_type_);
} }
Host::IdentifierType
Host::getIdentifierType(const std::string& identifier_name) {
if (identifier_name == "hw-address") {
return (IDENT_HWADDR);
} else if (identifier_name == "duid") {
return (IDENT_DUID);
} else if (identifier_name == "circuit-id") {
return (IDENT_CIRCUIT_ID);
} else {
isc_throw(isc::BadValue, "invalid client identifier type '"
<< identifier_name << "'");
}
}
HWAddrPtr HWAddrPtr
Host::getHWAddress() const { Host::getHWAddress() const {
return ((identifier_type_ == IDENT_HWADDR) ? return ((identifier_type_ == IDENT_HWADDR) ?
...@@ -201,53 +218,35 @@ Host::setIdentifier(const uint8_t* identifier, const size_t len, ...@@ -201,53 +218,35 @@ Host::setIdentifier(const uint8_t* identifier, const size_t len,
void void
Host::setIdentifier(const std::string& identifier, const std::string& name) { Host::setIdentifier(const std::string& identifier, const std::string& name) {
// HW address and DUID are special cases because they are typically // Empty identifier is not allowed.
// specified as values with colons between consecutive octets. Thus, if (identifier.empty()) {
// we use the HWAddr and DUID classes to validate them and to isc_throw(isc::BadValue, "empty host identifier used");
// convert them into binary format. }
if (name == "hw-address") {
HWAddr hwaddr(HWAddr::fromText(identifier));
identifier_type_= IDENT_HWADDR;
identifier_value_ = hwaddr.hwaddr_;
} else if (name == "duid") {
identifier_type_ = IDENT_DUID;
DUID duid(DUID::fromText(identifier));
identifier_value_ = duid.getDuid();
} else {
if (name == "circuit-id") {
identifier_type_ = IDENT_CIRCUIT_ID;
} else { // Set identifier type.
isc_throw(isc::BadValue, "invalid client identifier type '" identifier_type_ = getIdentifierType(name);
<< name << "' when creating host instance");
}
// Here we're converting values other than DUID and HW address. These // Idetifier value can either be specified as string of hexadecimal
// values can either be specified as strings of hexadecimal digits or // digits or a string in quotes. The latter is copied to a vector excluding
// strings in quotes. The latter are copied to a vector excluding quote // quote characters.
// characters.
// Try to convert the values in quotes into a vector of ASCII codes. // Try to convert the values in quotes into a vector of ASCII codes.
// If the identifier lacks opening and closing quote, this will return // If the identifier lacks opening and closing quote, this will return
// an empty value, in which case we'll try to decode it as a string of // an empty value, in which case we'll try to decode it as a string of
// hexadecimal digits. // hexadecimal digits.
try {
std::vector<uint8_t> binary = util::str::quotedStringToBinary(identifier); std::vector<uint8_t> binary = util::str::quotedStringToBinary(identifier);
if (binary.empty()) { if (binary.empty()) {
try { util::str::decodeFormattedHexString(identifier, binary);
util::encode::decodeHex(identifier, binary);
} catch (...) {
// The string doesn't match any known pattern, so we have to
// report an error at this point.
isc_throw(isc::BadValue, "invalid host identifier value '"
<< identifier << "'");
}
} }
// Successfully decoded the identifier, so let's use it. // Successfully decoded the identifier, so let's use it.
identifier_value_.swap(binary); identifier_value_.swap(binary);
} catch (...) {
// The string doesn't match any known pattern, so we have to
// report an error at this point.
isc_throw(isc::BadValue, "invalid host identifier value '"
<< identifier << "'");
} }
} }
......
...@@ -221,17 +221,18 @@ public: ...@@ -221,17 +221,18 @@ public:
/// ///
/// Creates @c Host object using an identifier in a textual format. This /// Creates @c Host object using an identifier in a textual format. This
/// is useful in cases when the reservation is specified in the server /// is useful in cases when the reservation is specified in the server
/// configuration file, where: /// configuration file. Identifiers can be specified in the following
/// - MAC address is specified as: "01:02:03:04:05:06" /// formats:
/// - DUID can be specified as: "01:02:03:04:05:06:ab:cd" or "010203040506abcd". /// - "yy:yy:yy:yy:yy:yy"
/// - Other identifiers are specified as: "010203040506abcd" or as /// - "yyyyyyyyyy",
/// "'some identfier'". /// - "0xyyyyyyyyyy",
/// /// - "'some identfier'".
/// In case of identifiers other than HW address and DUID it is possible to use /// where y is a hexadecimal digit.
/// textual representation, e.g. 'some identifier', which is converted to a ///
/// vector of ASCII codes representing characters in a given string, excluding /// Note that it is possible to use textual representation, e.g. 'some identifier',
/// quotes. This is useful in cases when specific identifiers, e.g. circuit-id /// which is converted to a vector of ASCII codes representing characters in a
/// are manually assigned user friendly values. /// given string, excluding quotes. This is useful in cases when specific
/// identifiers, e.g. circuit-id are manually assigned user friendly values.
/// ///
/// @param identifier Identifier in the textual format. The expected formats /// @param identifier Identifier in the textual format. The expected formats
/// for the hardware address and other identifiers are provided above. /// for the hardware address and other identifiers are provided above.
...@@ -304,6 +305,12 @@ public: ...@@ -304,6 +305,12 @@ public:
/// ///
IdentifierType getIdentifierType() const; IdentifierType getIdentifierType() const;
/// @brief Converts identifier name to identifier type.
///
/// @param identifier_name Identifier name.
/// @return Identifier type.
static IdentifierType getIdentifierType(const std::string& identifier_name);
/// @brief Returns host identifier in a textual form. /// @brief Returns host identifier in a textual form.
/// ///
/// @return Identifier in the form of <type>=<value>. /// @return Identifier in the form of <type>=<value>.
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <sys/socket.h> #include <sys/socket.h>
#include <sstream>
#include <string> #include <string>
using namespace isc::asiolink; using namespace isc::asiolink;
...@@ -23,36 +24,62 @@ namespace { ...@@ -23,36 +24,62 @@ namespace {
/// ///
/// This function returns the set of supported parameters for /// This function returns the set of supported parameters for
/// host reservation in DHCPv4. /// host reservation in DHCPv4.
const std::set<std::string>& getSupportedParams4() { ///
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
const std::set<std::string>&
getSupportedParams4(const bool identifiers_only = false) {
// Holds set of host identifiers.
static std::set<std::string> identifiers_set;
// Holds set of all supported parameters, including identifiers.
static std::set<std::string> params_set; static std::set<std::string> params_set;
// If this is first execution of this function, we need
// to initialize the set.
if (identifiers_set.empty()) {
identifiers_set.insert("duid");
identifiers_set.insert("hw-address");
identifiers_set.insert("circuit-id");
}
// Copy identifiers and add all other parameters.
if (params_set.empty()) { if (params_set.empty()) {
const char* params[] = { params_set = identifiers_set;
"duid", "hw-address", "hostname", "ip-address", params_set.insert("hostname");
"option-data", NULL params_set.insert("ip-address");
}; params_set.insert("option-data");
for (int i = 0; params[i] != NULL; ++i) {
params_set.insert(std::string(params[i]));
}
} }
return (params_set); return (identifiers_only ? identifiers_set : params_set);
} }
/// @brief Returns set of the supported parameters for DHCPv4. /// @brief Returns set of the supported parameters for DHCPv6.
/// ///
/// This function returns the set of supported parameters for /// This function returns the set of supported parameters for
/// host reservation in DHCPv6. /// host reservation in DHCPv6.
const std::set<std::string>& getSupportedParams6() { ///
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
const std::set<std::string>&
getSupportedParams6(const bool identifiers_only = false) {
// Holds set of host identifiers.
static std::set<std::string> identifiers_set;
// Holds set of all supported parameters, including identifiers.
static std::set<std::string> params_set; static std::set<std::string> params_set;
// If this is first execution of this function, we need
// to initialize the set.
if (identifiers_set.empty()) {
identifiers_set.insert("duid");
identifiers_set.insert("hw-address");
}
// Copy identifiers and add all other parameters.
if (params_set.empty()) { if (params_set.empty()) {
const char* params[] = { params_set = identifiers_set;
"duid", "hw-address", "hostname", "ip-addresses", "prefixes", params_set.insert("hostname");
"option-data", NULL params_set.insert("ip-addresses");
}; params_set.insert("prefixes");
for (int i = 0; params[i] != NULL; ++i) { params_set.insert("option-data");
params_set.insert(std::string(params[i]));
}
} }
return (params_set); return (identifiers_only ? identifiers_set : params_set);
} }
} }
...@@ -80,10 +107,11 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) { ...@@ -80,10 +107,11 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
" parameter '" << element.first << "'"); " parameter '" << element.first << "'");
} }
if (element.first == "hw-address" || element.first == "duid") { if (isIdentifierParameter(element.first)) {
if (!identifier_name.empty()) { if (!identifier.empty()) {
isc_throw(DhcpConfigError, "the 'hw-address' and 'duid'" isc_throw(DhcpConfigError, "the '" << element.first
" parameters are mutually exclusive"); << "' and '" << identifier_name
<< "' are mutually exclusive");
} }
identifier = element.second->stringValue(); identifier = element.second->stringValue();
identifier_name = element.first; identifier_name = element.first;
...@@ -100,10 +128,22 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) { ...@@ -100,10 +128,22 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
} }
try { try {
// hw-address or duid is a must. // Host identifier is a must.
if (identifier_name.empty()) { if (identifier_name.empty()) {
isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a required" // If there is no identifier specified, we have to display an
" parameter for host reservation"); // error message and include the information what identifiers
// are supported.
std::ostringstream s;
BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
if (s.tellp() != std::streampos(0)) {
s << ", ";
}
s << param_name;
}
isc_throw(DhcpConfigError, "one of the supported identifiers must"
" be specified for host reservation: "
<< s.str());
} }
// Create a host object from the basic parameters we already parsed. // Create a host object from the basic parameters we already parsed.
...@@ -129,6 +169,16 @@ HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) { ...@@ -129,6 +169,16 @@ HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
} }
} }
bool
HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
return (getSupportedParameters(true).count(param_name) > 0);
}
bool
HostReservationParser::isSupportedParameter(const std::string& param_name) const {
return (getSupportedParameters(false).count(param_name) > 0);
}
HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id) HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
: HostReservationParser(subnet_id) { : HostReservationParser(subnet_id) {
} }
...@@ -167,9 +217,9 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) { ...@@ -167,9 +217,9 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
addHost(reservation_data); addHost(reservation_data);
} }