Commit abee3e73 authored by Marcin Siodelski's avatar Marcin Siodelski

[master] Merge branch 'trac4300'

parents 12320a72 7741ab8c
......@@ -75,14 +75,15 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
const std::string& hostname,
const std::string& dhcp4_client_classes,
const std::string& dhcp6_client_classes)
: hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
: identifier_type_(identifier_type),
identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
dhcp6_client_classes_(dhcp6_client_classes), host_id_(0),
cfg_option4_(), cfg_option6_() {
// Initialize HWAddr or DUID
// Initialize host identifier.
setIdentifier(identifier, identifier_len, identifier_type);
if (!ipv4_reservation.isV4Zero()) {
......@@ -97,14 +98,15 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
const std::string& hostname,
const std::string& dhcp4_client_classes,
const std::string& dhcp6_client_classes)
: hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
: identifier_type_(IDENT_HWADDR),
identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
ipv6_subnet_id_(ipv6_subnet_id),
ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
dhcp6_client_classes_(dhcp6_client_classes), host_id_(0),
cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) {
// Initialize HWAddr or DUID
// Initialize host identifier.
setIdentifier(identifier, identifier_name);
if (!ipv4_reservation.isV4Zero()) {
......@@ -115,51 +117,36 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
const std::vector<uint8_t>&
Host::getIdentifier() const {
if (hw_address_) {
return (hw_address_->hwaddr_);
} else if (duid_) {
return (duid_->getDuid());
}
static std::vector<uint8_t> empty_vector;
return (empty_vector);
return (identifier_value_);
}
Host::IdentifierType
Host::getIdentifierType() const {
if (hw_address_) {
return (IDENT_HWADDR);
}
return (IDENT_DUID);
return (identifier_type_);
}
std::string
Host::getIdentifierAsText() const {
std::string txt;
if (hw_address_) {
txt = getIdentifierAsText(IDENT_HWADDR, &hw_address_->hwaddr_[0],
hw_address_->hwaddr_.size());
} else if (duid_) {
txt = getIdentifierAsText(IDENT_DUID, &duid_->getDuid()[0],
duid_->getDuid().size());
} else {
txt = "(none)";
}
HWAddrPtr
Host::getHWAddress() const {
return ((identifier_type_ == IDENT_HWADDR) ?
HWAddrPtr(new HWAddr(identifier_value_, HTYPE_ETHER)) : HWAddrPtr());
}
DuidPtr
Host::getDuid() const {
return ((identifier_type_ == IDENT_DUID) ?
DuidPtr(new DUID(identifier_value_)) : DuidPtr());
}
return (txt);
std::string
Host::getIdentifierAsText() const {
return (getIdentifierAsText(identifier_type_, &identifier_value_[0],
identifier_value_.size()));
}
std::string
Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
const size_t length) {
// Length 0 doesn't make sense.
if (length == 0) {
isc_throw(BadValue, "invalid length 0 of the host identifier while"
" converting the identifier to a textual form");
}
// Convert identifier into <type>=<value> form.
std::ostringstream s;
switch (type) {
......@@ -169,46 +156,98 @@ Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
case IDENT_DUID:
s << "duid";
break;
case IDENT_CIRCUIT_ID:
s << "circuit-id";
break;
default:
isc_throw(BadValue, "requested conversion of the unsupported"
" identifier into textual form");
// This should never happen actually, unless we add new identifier
// and forget to add a case for it above.
s << "(invalid-type)";
}
std::vector<uint8_t> vec(value, value + length);
s << "=" << util::encode::encodeHex(vec);
s << "=" << (length > 0 ? util::encode::encodeHex(vec) : "(null)");
return (s.str());
}
std::string
Host::getIdentifierName(const IdentifierType& type) {
switch (type) {
case Host::IDENT_HWADDR:
return ("hw-address");
case Host::IDENT_DUID:
return ("duid");
case Host::IDENT_CIRCUIT_ID:
return ("circuit-id");
default:
;
}
return ("(unknown)");
}
void
Host::setIdentifier(const uint8_t* identifier, const size_t len,
const IdentifierType& type) {
switch (type) {
case IDENT_HWADDR:
hw_address_ = HWAddrPtr(new HWAddr(identifier, len, HTYPE_ETHER));
duid_.reset();
break;
case IDENT_DUID:
duid_ = DuidPtr(new DUID(identifier, len));
hw_address_.reset();
break;
default:
isc_throw(isc::BadValue, "invalid client identifier type '"
<< static_cast<int>(type) << "' when creating host "
" instance");
if (len < 1) {
isc_throw(BadValue, "invalid client identifier length 0");
}
identifier_type_ = type;
identifier_value_.assign(identifier, identifier + 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") {
hw_address_ = HWAddrPtr(new HWAddr(HWAddr::fromText(identifier)));
duid_.reset();
HWAddr hwaddr(HWAddr::fromText(identifier));
identifier_type_= IDENT_HWADDR;
identifier_value_ = hwaddr.hwaddr_;
} else if (name == "duid") {
duid_ = DuidPtr(new DUID(DUID::fromText(identifier)));
hw_address_.reset();
identifier_type_ = IDENT_DUID;
DUID duid(DUID::fromText(identifier));
identifier_value_ = duid.getDuid();
} else {
isc_throw(isc::BadValue, "invalid client identifier type '"
<< name << "' when creating host instance");
if (name == "circuit-id") {
identifier_type_ = IDENT_CIRCUIT_ID;
} else {
isc_throw(isc::BadValue, "invalid client identifier type '"
<< name << "' when creating host instance");
}
// 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.
// 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.
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 << "'");
}
}
// Successfully decoded the identifier, so let's use it.
identifier_value_.swap(binary);
}
}
......
......@@ -179,7 +179,8 @@ public:
/// DHCPv6 client's DUID are supported.
enum IdentifierType {
IDENT_HWADDR,
IDENT_DUID
IDENT_DUID,
IDENT_CIRCUIT_ID
};
/// @brief Constructor.
......@@ -222,11 +223,19 @@ public:
/// 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 is specified as: "010203040506abcd"
/// - 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.
///
/// @param identifier Identifier in the textual format. The expected formats
/// for the hardware address and DUID have been shown above.
/// @param identifier_name One of "hw-address" or "duid"
/// for the hardware address and other identifiers are provided above.
/// @param identifier_name One of "hw-address", "duid", "circuit-id".
/// @param ipv4_subnet_id Identifier of the IPv4 subnet to which the host
/// is connected.
/// @param ipv6_subnet_id Identifier of the IPv6 subnet to which the host
......@@ -251,13 +260,10 @@ public:
/// @brief Replaces currently used identifier with a new identifier.
///
/// This method initializes hardware address or DUID (@c hw_address_ or
/// @c duid_ respectively). The other (not initialized member) is
/// deallocated.
///
/// This method sets a new identifier type and value for a host.
/// This method is called by the @c Host constructor.
///
/// @param identifier Pointer to the new identifier in the textual format.
/// @param identifier Pointer to a buffer holding an identifier.
/// @param len Length of the identifier that the @c identifier points to.
/// @param type Identifier type.
///
......@@ -267,14 +273,11 @@ public:
/// @brief Replaces currently used identifier with a new identifier.
///
/// This method initializes hardware address or DUID (@c hw_address_ or
/// @c duid_ respectively). The other (not initialized member) is
/// deallocated.
///
/// This method sets a new identifier type and value for a host.
/// This method is called by the @c Host constructor.
///
/// @param identifier Pointer to the new identifier in the textual format.
/// @param name One of "hw-address" or "duid".
/// @param identifier Reference to a new identifier in the textual format.
/// @param name One of "hw-address", "duid", "circuit-id".
///
/// @throw BadValue if the identifier is invalid.
void setIdentifier(const std::string& identifier, const std::string& name);
......@@ -283,30 +286,32 @@ public:
///
/// @return Pointer to the @c HWAddr structure or null if the reservation
/// is not associated with a hardware address.
HWAddrPtr getHWAddress() const {
return (hw_address_);
}
HWAddrPtr getHWAddress() const;
/// @brief Returns DUID for which the reservations are made.
///
/// @return Pointer to the @c DUID structure or null if the reservation
/// is not associated with a DUID.
DuidPtr getDuid() const {
return (duid_);
}
DuidPtr getDuid() const;
/// @brief Returns the identifier (MAC or DUID) in binary form.
/// @return const reference to MAC or DUID in vector<uint8_t> form
/// @brief Returns the identifier in a binary form.
///
/// @return const reference to a vector<uint8_t> holding an identifier
/// value.
const std::vector<uint8_t>& getIdentifier() const;
/// @brief Returns the identifier type.
/// @return the identifier type
///
IdentifierType getIdentifierType() const;
/// @brief Returns host identifier (mac or DUID) in printer friendly form.
/// @return text form of the identifier, including (duid= or mac=).
/// @brief Returns host identifier in a textual form.
///
/// @return Identifier in the form of <type>=<value>.
std::string getIdentifierAsText() const;
/// @brief Returns name of the identifier of a specified type.
static std::string getIdentifierName(const IdentifierType& type);
/// @brief Returns host identifier in textual form.
///
/// @param type Identifier type.
......@@ -487,12 +492,10 @@ private:
void addClientClassInternal(ClientClasses& classes,
const std::string& class_name);
/// @brief Pointer to the hardware address associated with the reservations
/// for the host.
HWAddrPtr hw_address_;
/// @brief Pointer to the DUID associated with the reservations for the
/// host.
DuidPtr duid_;
/// @brief Identifier type.
IdentifierType identifier_type_;
/// @brief Vector holding identifier value.
std::vector<uint8_t> identifier_value_;
/// @brief Subnet identifier for the DHCPv4 client.
SubnetID ipv4_subnet_id_;
/// @brief Subnet identifier for the DHCPv6 client.
......
......@@ -42,6 +42,11 @@ const size_t CLIENT_CLASSES_MAX_LEN = 255;
/// in the Client FQDN %Option (see RFC4702 and RFC4704).
const size_t HOSTNAME_MAX_LEN = 255;
/// @brief Numeric value representing last supported identifier.
///
/// This value is used to validate whether the identifier type stored in
/// a database is within bounds. of supported identifiers.
const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::IDENT_CIRCUIT_ID);
/// @brief Prepared MySQL statements used by the backend to insert and
/// retrieve hosts from the database.
......@@ -299,42 +304,21 @@ public:
bind_[0].is_unsigned = MLM_TRUE;
// dhcp_identifier : VARBINARY(128) NOT NULL
// Check which of the identifiers is used and set values accordingly
if (host->getDuid()) {
dhcp_identifier_length_ = host->getDuid()->getDuid().size();
bind_[1].buffer_type = MYSQL_TYPE_BLOB;
bind_[1].buffer = reinterpret_cast<char*>
(const_cast<uint8_t*>(&(host->getDuid()->getDuid()[0])));
bind_[1].buffer_length = dhcp_identifier_length_;
bind_[1].length = &dhcp_identifier_length_;
} else if (host->getHWAddress()){
dhcp_identifier_length_ = host->getHWAddress()->hwaddr_.size();
bind_[1].buffer_type = MYSQL_TYPE_BLOB;
bind_[1].buffer = reinterpret_cast<char*>
(&(host->getHWAddress()->hwaddr_[0]));
bind_[1].buffer_length = dhcp_identifier_length_;
bind_[1].length = &dhcp_identifier_length_;
} else {
isc_throw(DbOperationError, "Host object doesn't contain any"
" identifier which can be used to retrieve information"
" from the database about its static reservations");
}
dhcp_identifier_length_ = host->getIdentifier().size();
memcpy(static_cast<void*>(dhcp_identifier_buffer_),
&(host->getIdentifier())[0],
host->getIdentifier().size());
bind_[1].buffer_type = MYSQL_TYPE_BLOB;
bind_[1].buffer = dhcp_identifier_buffer_;
bind_[1].buffer_length = dhcp_identifier_length_;
bind_[1].length = &dhcp_identifier_length_;
// dhcp_identifier_type : TINYINT NOT NULL
// Check which of the identifier types is used and set values accordingly
if (host->getHWAddress()) {
dhcp_identifier_type_ = BaseHostDataSource::ID_HWADDR; // 0
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
bind_[2].is_unsigned = MLM_TRUE;
} else if (host->getDuid()) {
dhcp_identifier_type_ = BaseHostDataSource::ID_DUID; // 1
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
bind_[2].is_unsigned = MLM_TRUE;
}
dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
bind_[2].buffer_type = MYSQL_TYPE_TINY;
bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
bind_[2].is_unsigned = MLM_TRUE;
// dhcp4_subnet_id : INT UNSIGNED NULL
// Can't take an address of intermediate object, so let's store it
......@@ -495,25 +479,14 @@ public:
/// @return Host Pointer to a @ref HostPtr object holding a pointer to the
/// @ref Host object returned.
HostPtr retrieveHost() {
// Set the dhcp identifier type in a variable of the appropriate data type,
// which has been initialized with an arbitrary (but valid) value.
Host::IdentifierType type = Host::IDENT_HWADDR;
switch (dhcp_identifier_type_) {
case 0:
type = Host::IDENT_HWADDR;
break;
case 1:
type = Host::IDENT_DUID;
break;
default:
// Check if the identifier stored in the database is correct.
if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
isc_throw(BadValue, "invalid dhcp identifier type returned: "
<< static_cast<int>(dhcp_identifier_type_)
<< ". Only 0 or 1 are supported.");
<< static_cast<int>(dhcp_identifier_type_));
}
// Set the dhcp identifier type in a variable of the appropriate data type.
Host::IdentifierType type =
static_cast<Host::IdentifierType>(dhcp_identifier_type_);
// Set DHCPv4 subnet ID to the value returned. If NULL returned, set to 0.
SubnetID ipv4_subnet_id(0);
......
......@@ -26,7 +26,7 @@ GenericHostDataSourceTest::GenericHostDataSourceTest()
GenericHostDataSourceTest::~GenericHostDataSourceTest() {
}
std::string
std::vector<uint8_t>
GenericHostDataSourceTest::generateHWAddr(const bool new_identifier) {
/// @todo: Consider moving this somewhere to lib/testutils.
......@@ -34,15 +34,6 @@ GenericHostDataSourceTest::generateHWAddr(const bool new_identifier) {
// if you need to enter MySQL queries by hand.
static uint8_t hwaddr[] = {65, 66, 67, 68, 69, 70};
stringstream tmp;
for (int i = 0; i < sizeof(hwaddr); ++i) {
if (i) {
tmp << ":";
}
tmp << std::setw(2) << std::hex << std::setfill('0')
<< static_cast<unsigned int>(hwaddr[i]);
}
if (new_identifier) {
// Increase the address for the next time we use it.
// This is primitive, but will work for 65k unique
......@@ -52,46 +43,38 @@ GenericHostDataSourceTest::generateHWAddr(const bool new_identifier) {
hwaddr[sizeof(hwaddr) - 2]++;
}
}
return (tmp.str());
return (std::vector<uint8_t>(hwaddr, hwaddr + sizeof(hwaddr)));
}
std::string
GenericHostDataSourceTest::generateDuid(const bool new_identifier) {
std::vector<uint8_t>
GenericHostDataSourceTest::generateIdentifier(const bool new_identifier) {
/// @todo: Consider moving this somewhere to lib/testutils.
// Let's use something that is easily printable. That's convenient
// if you need to enter MySQL queries by hand.
static uint8_t duid[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
static uint8_t ident[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };
stringstream tmp;
for (int i = 0; i < sizeof(duid); ++i) {
tmp << std::setw(2) << std::hex << std::setfill('0')
<< static_cast<unsigned int>(duid[i]);
}
// Increase the DUID for the next time we use it.
// Increase the identifier for the next time we use it.
// This is primitive, but will work for 65k unique
// DUIDs.
// identifiers.
if (new_identifier) {
duid[sizeof(duid) - 1]++;
if (duid[sizeof(duid) - 1] == 0) {
duid[sizeof(duid) - 2]++;
ident[sizeof(ident) - 1]++;
if (ident[sizeof(ident) - 1] == 0) {
ident[sizeof(ident) - 2]++;
}
}
return (tmp.str());
return (std::vector<uint8_t>(ident, ident + sizeof(ident)));
}
HostPtr GenericHostDataSourceTest::initializeHost4(std::string address,
bool hwaddr) {
string ident;
string ident_type;
if (hwaddr) {
HostPtr
GenericHostDataSourceTest::initializeHost4(const std::string& address,
const Host::IdentifierType& id) {
std::vector<uint8_t> ident;
if (id == Host::IDENT_HWADDR) {
ident = generateHWAddr();
ident_type = "hw-address";
} else {
ident = generateDuid();
ident_type = "duid";
ident = generateIdentifier();
}
// Let's create ever increasing subnet-ids. Let's keep those different,
......@@ -103,7 +86,7 @@ HostPtr GenericHostDataSourceTest::initializeHost4(std::string address,
subnet6++;
IOAddress addr(address);
HostPtr host(new Host(ident, ident_type, subnet4, subnet6, addr));
HostPtr host(new Host(&ident[0], ident.size(), id, subnet4, subnet6, addr));
return (host);
}
......@@ -112,17 +95,13 @@ HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
Host::IdentifierType identifier,
bool prefix,
bool new_identifier) {
string ident;
string ident_type;
std::vector<uint8_t> ident;
switch (identifier) {
case Host::IDENT_HWADDR:
ident = generateHWAddr(new_identifier);
ident_type = "hw-address";
break;
case Host::IDENT_DUID:
ident = generateDuid(new_identifier);
ident_type = "duid";
ident = generateIdentifier(new_identifier);
break;
default:
ADD_FAILURE() << "Unknown IdType: " << identifier;
......@@ -137,7 +116,8 @@ HostPtr GenericHostDataSourceTest::initializeHost6(std::string address,
subnet4++;
subnet6++;
HostPtr host(new Host(ident, ident_type, subnet4, subnet6, IOAddress("0.0.0.0")));
HostPtr host(new Host(&ident[0], ident.size(), identifier, subnet4,
subnet6, IOAddress("0.0.0.0")));
if (!prefix) {
// Create IPv6 reservation (for an address)
......@@ -333,12 +313,12 @@ GenericHostDataSourceTest::compareClientClasses(const ClientClasses& /*classes1*
/// This is part of the work for #4213.
}
void GenericHostDataSourceTest::testBasic4(bool hwaddr) {
void GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) {
// Make sure we have the pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Create a host reservation.
HostPtr host = initializeHost4("192.0.2.1", hwaddr);
HostPtr host = initializeHost4("192.0.2.1", id);
ASSERT_TRUE(host); // Make sure the host is generate properly.
SubnetID subnet = host->getIPv4SubnetID();
......@@ -358,15 +338,15 @@ void GenericHostDataSourceTest::testBasic4(bool hwaddr) {
}
void GenericHostDataSourceTest::testGetByIPv4(bool hwaddr) {
void GenericHostDataSourceTest::testGetByIPv4(const Host::IdentifierType& id) {
// Make sure we have a pointer to the host data source.
ASSERT_TRUE(hdsptr_);
// Let's create a couple of hosts...
HostPtr host1 = initializeHost4("192.0.2.1", hwaddr);
HostPtr host2 = initializeHost4("192.0.2.2", hwaddr);
HostPtr host3 = initializeHost4("192.0.2.3", hwaddr);
HostPtr host4 = initializeHost4("192.0.2.4", hwaddr);
HostPtr host1 = initializeHost4("192.0.2.1", id);
HostPtr host2 = initializeHost4("192.0.2.2", id);
HostPtr host3 = initializeHost4("192.0.2.3", id);
HostPtr host4 = initializeHost4("192.0.2.4", id);
// ... and add them to the data source.
ASSERT_NO_THROW(hdsptr_->add(host1));
......@@ -402,52 +382,16 @@ void GenericHostDataSourceTest::testGetByIPv4(bool hwaddr) {
EXPECT_FALSE(hdsptr_->get4(subnet1, IOAddress("192.0.1.5")));
}
void GenericHostDataSourceTest::testGet4ByHWAddr() {
// Make sure we have a pointer to the host data source.
ASSERT_TRUE(hdsptr_);
HostPtr host1 = initializeHost4("192.0.2.1", true);
HostPtr host2 = initializeHost4("192.0.2.2", true);
// Sanity check: make sure the hosts have different HW addresses.
ASSERT_TRUE(host1->getHWAddress());
ASSERT_TRUE(host2->getHWAddress());
compareHwaddrs(host1, host2, false);
// Try to add both of them to the host data source.
ASSERT_NO_THROW(hdsptr_->add(host1));
ASSERT_NO_THROW(hdsptr_->add(host2));
SubnetID subnet1 = host1->getIPv4SubnetID();
SubnetID subnet2 = host2->getIPv4SubnetID();