Commit cf56fc2a authored by Marcin Siodelski's avatar Marcin Siodelski

[master] Merge branch 'trac4301'

parents bb00d9d2 253b032f
......@@ -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\","
" \"hostname\": \"\""
" }"
......@@ -3528,16 +3528,19 @@ TEST_F(Dhcp4ParserTest, reservations) {
ASSERT_TRUE(opt_ttl);
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
// of the address from the previous test.
hwaddr->hwaddr_.assign(hwaddr_vec.rbegin(), hwaddr_vec.rend());
host = hosts_cfg->get4(542, hwaddr);
std::vector<uint8_t> circuit_id(hwaddr_vec.rbegin(), hwaddr_vec.rend());
host = hosts_cfg->get4(542, Host::IDENT_CIRCUIT_ID, &circuit_id[0],
circuit_id.size());
EXPECT_TRUE(host);
EXPECT_EQ("192.0.4.102", host->getIPv4Reservation().toText());
// This reservation must not belong to other subnets.
EXPECT_FALSE(hosts_cfg->get4(123, hwaddr));
EXPECT_FALSE(hosts_cfg->get4(234, hwaddr));
EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_CIRCUIT_ID,
&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.
duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(),
......
......@@ -6,11 +6,8 @@
#include <dhcp/duid.h>
#include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <util/io_utilities.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <util/strutil.h>
#include <iomanip>
#include <cctype>
#include <sstream>
......@@ -42,57 +39,6 @@ DUID::DUID(const uint8_t* data, size_t 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 {
return (duid_);
}
......@@ -111,8 +57,9 @@ DUID::DUIDType DUID::getType() const {
DUID
DUID::fromText(const std::string& text) {
std::vector<uint8_t> binary = decode(text);
return DUID(binary);
std::vector<uint8_t> binary;
util::str::decodeFormattedHexString(text, binary);
return (DUID(binary));
}
DuidPtr
......@@ -185,7 +132,8 @@ std::string ClientId::toText() const {
ClientIdPtr
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)));
}
......
......@@ -79,7 +79,6 @@ class DUID {
/// @brief Create DUID from 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
/// individual bytes separated by colons.
......@@ -98,22 +97,6 @@ class DUID {
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
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
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -7,10 +7,7 @@
#include <dhcp/hwaddr.h>
#include <dhcp/dhcp4.h>
#include <exceptions/exceptions.h>
#include <util/encode/hex.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/constants.hpp>
#include <boost/algorithm/string/split.hpp>
#include <util/strutil.h>
#include <iomanip>
#include <sstream>
#include <vector>
......@@ -69,37 +66,8 @@ std::string HWAddr::toText(bool include_htype) const {
HWAddr
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;
try {
util::encode::decodeHex(s.str(), binary);
} catch (const Exception& ex) {
isc_throw(isc::BadValue, "failed to create hwaddr from text '"
<< text << "': " << ex.what());
}
util::str::decodeColonSeparatedHexString(text, binary);
return (HWAddr(binary, htype));
}
......
......@@ -125,6 +125,23 @@ Host::getIdentifierType() const {
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
Host::getHWAddress() const {
return ((identifier_type_ == IDENT_HWADDR) ?
......@@ -201,53 +218,35 @@ Host::setIdentifier(const uint8_t* identifier, const size_t len,
void
Host::setIdentifier(const std::string& identifier, const std::string& name) {
// HW address and DUID are special cases because they are typically
// specified as values with colons between consecutive octets. Thus,
// we use the HWAddr and DUID classes to validate them and to
// 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;
// Empty identifier is not allowed.
if (identifier.empty()) {
isc_throw(isc::BadValue, "empty host identifier used");
}
} else {
isc_throw(isc::BadValue, "invalid client identifier type '"
<< name << "' when creating host instance");
}
// Set identifier type.
identifier_type_ = getIdentifierType(name);
// Here we're converting values other than DUID and HW address. These
// values can either be specified as strings of hexadecimal digits or
// strings in quotes. The latter are copied to a vector excluding quote
// characters.
// Idetifier value can either be specified as string of hexadecimal
// digits or a string in quotes. The latter is copied to a vector excluding
// quote characters.
// Try to convert the values in quotes into a vector of ASCII codes.
// 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
// hexadecimal digits.
// Try to convert the values in quotes into a vector of ASCII codes.
// 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
// hexadecimal digits.
try {
std::vector<uint8_t> binary = util::str::quotedStringToBinary(identifier);
if (binary.empty()) {
try {
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 << "'");
}
util::str::decodeFormattedHexString(identifier, binary);
}
// Successfully decoded the identifier, so let's use it.
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:
///
/// Creates @c Host object using an identifier in a textual format. This
/// is useful in cases when the reservation is specified in the server
/// configuration file, where:
/// - MAC address is specified as: "01:02:03:04:05:06"
/// - DUID can be specified as: "01:02:03:04:05:06:ab:cd" or "010203040506abcd".
/// - Other identifiers are specified as: "010203040506abcd" or as
/// "'some identfier'".
///
/// In case of identifiers other than HW address and DUID it is possible to use
/// textual representation, e.g. 'some identifier', which is converted to a
/// vector of ASCII codes representing characters in a given string, excluding
/// quotes. This is useful in cases when specific identifiers, e.g. circuit-id
/// are manually assigned user friendly values.
/// configuration file. Identifiers can be specified in the following
/// formats:
/// - "yy:yy:yy:yy:yy:yy"
/// - "yyyyyyyyyy",
/// - "0xyyyyyyyyyy",
/// - "'some identfier'".
/// where y is a hexadecimal digit.
///
/// Note that it is possible to use textual representation, e.g. 'some identifier',
/// which is converted to a vector of ASCII codes representing characters in a
/// 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
/// for the hardware address and other identifiers are provided above.
......@@ -304,6 +305,12 @@ public:
///
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.
///
/// @return Identifier in the form of <type>=<value>.
......
......@@ -12,6 +12,7 @@
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <sys/socket.h>
#include <sstream>
#include <string>
using namespace isc::asiolink;
......@@ -23,36 +24,62 @@ namespace {
///
/// This function returns the set of supported parameters for
/// 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;
// 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()) {
const char* params[] = {
"duid", "hw-address", "hostname", "ip-address",
"option-data", NULL
};
for (int i = 0; params[i] != NULL; ++i) {
params_set.insert(std::string(params[i]));
}
params_set = identifiers_set;
params_set.insert("hostname");
params_set.insert("ip-address");
params_set.insert("option-data");
}
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
/// 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;
// 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()) {
const char* params[] = {
"duid", "hw-address", "hostname", "ip-addresses", "prefixes",
"option-data", NULL
};
for (int i = 0; params[i] != NULL; ++i) {
params_set.insert(std::string(params[i]));
}
params_set = identifiers_set;
params_set.insert("hostname");
params_set.insert("ip-addresses");
params_set.insert("prefixes");
params_set.insert("option-data");
}
return (params_set);
return (identifiers_only ? identifiers_set : params_set);
}
}
......@@ -80,10 +107,11 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
" parameter '" << element.first << "'");
}
if (element.first == "hw-address" || element.first == "duid") {
if (!identifier_name.empty()) {
isc_throw(DhcpConfigError, "the 'hw-address' and 'duid'"
" parameters are mutually exclusive");
if (isIdentifierParameter(element.first)) {
if (!identifier.empty()) {
isc_throw(DhcpConfigError, "the '" << element.first
<< "' and '" << identifier_name
<< "' are mutually exclusive");
}
identifier = element.second->stringValue();
identifier_name = element.first;
......@@ -100,10 +128,22 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
}
try {
// hw-address or duid is a must.
// Host identifier is a must.
if (identifier_name.empty()) {
isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a required"
" parameter for host reservation");
// If there is no identifier specified, we have to display an
// 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.
......@@ -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)
: HostReservationParser(subnet_id) {
}
......@@ -167,9 +217,9 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
addHost(reservation_data);
}
bool
HostReservationParser4::isSupportedParameter(const std::string& param_name) const {
return (getSupportedParams4().count(param_name) > 0);
const std::set<std::string>&
HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
return (getSupportedParams4(identifiers_only));
}
HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
......@@ -262,9 +312,9 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
addHost(reservation_data);
}
bool
HostReservationParser6::isSupportedParameter(const std::string& param_name) const {
return (getSupportedParams6().count(param_name) > 0);
const std::set<std::string>&
HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
return (getSupportedParams6(identifiers_only));
}
} // end of namespace isc::dhcp
......
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
//
// 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
......@@ -48,12 +48,30 @@ protected:
/// @throw DhcpConfigError When operation to add a configured host fails.
void addHost(isc::data::ConstElementPtr reservation_data);
/// @brief Checks if the specified parameter is a host identifier.
///
/// @param param_name Parameter name.
///
/// @return true if the parameter specifies host identifier, false
/// otherwise.
virtual bool isIdentifierParameter(const std::string& param_name) const;
/// @brief Checks if the specified parameter is supported by the parser.
///
/// @param param_name Parameter name.
///
/// @return true if the parameter is supported, false otherwise.
virtual bool isSupportedParameter(const std::string& param_name) const = 0;
virtual bool isSupportedParameter(const std::string& param_name) const;
/// @brief Returns set of the supported parameters.
///
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
///
/// @return Set of supported parameter names.
virtual const std::set<std::string>&
getSupportedParameters(const bool identifiers_only) const = 0;
/// @brief Identifier of the subnet that the host is connected to.
SubnetID subnet_id_;
......@@ -84,12 +102,16 @@ public:
protected:
/// @brief Checks if the specified parameter is supported by the parser.
/// @brief Returns set of the supported parameters for DHCPv4.
///
/// @param param_name Parameter name.
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
///
/// @return true if the parameter is supported, false otherwise.
virtual bool isSupportedParameter(const std::string& param_name) const;
/// @return Set of supported parameter names.
virtual const std::set<std::string>&
getSupportedParameters(const bool identifiers_only) const;
};
/// @brief Parser for a single host reservation for DHCPv6.
......@@ -112,12 +134,16 @@ public:
protected:
/// @brief Checks if the specified parameter is supported by the parser.
/// @brief Returns set of the supported parameters for DHCPv6.
///
/// @param param_name Parameter name.
/// @param identifiers_only Indicates if the function should only
/// return supported host identifiers (if true) or all supported
/// parameters (if false).
///
/// @return true if the parameter is supported, false otherwise.
virtual bool isSupportedParameter(const std::string& param_name) const;
/// @return Set of supported parameter names.
virtual const std::set<std::string>&
getSupportedParameters(const bool identifiers_only) const;
};
......
......@@ -21,6 +21,7 @@
#include <gtest/gtest.h>
#include <iterator>
#include <string>
#include <vector>
using namespace isc::asiolink;
using namespace isc::data;
......@@ -28,6 +29,11 @@ using namespace isc::dhcp;
namespace {
/// @brief Holds a type of the last identifier in @c IdentifierType enum.
///
/// This value must be updated when new identifiers are added to the enum.
const Host::IdentifierType LAST_IDENTIFIER_TYPE = Host::IDENT_CIRCUIT_ID;
/// @brief Test fixture class for @c HostReservationParser.
class HostReservationParserTest : public ::testing::Test {
protected:
......@@ -92,10 +98,6 @@ protected:
return (OptionPtr());
}
void
expectFailure(const HostReservationParser& parser,
const std::string& config) const;
/// @brief This test verifies that it is possible to specify an empty list
/// of options for a host.
///
......@@ -126,6 +128,40 @@ protected:
EXPECT_TRUE(hosts[0]->getCfgOption6()->empty());