2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 13:07:50 +00:00

[master] Merge branch 'trac4552'

This commit is contained in:
Marcin Siodelski 2016-08-25 18:18:07 +02:00
commit 9b79fe005d
23 changed files with 778 additions and 47 deletions

View File

@ -2854,6 +2854,35 @@ It is merely echoed by the server
</section>
<section id="reservation4-message-fields">
<title>Reserving Next Server, Server Hostname and Boot File Name</title>
<para>BOOTP/DHCPv4 messages include "siaddr", "sname" and "file" fields.
Even though, DHCPv4 includes corresponding options, such as option 66 and
option 67, some clients may not support these options. Thus, server
administrators often use "siaddr", "sname" and "file" fields instead.</para>
<para>With Kea, it is possible to make static reservations for these DHCPv4
message fields:</para>
<screen>
{
"subnet4": [ {
"reservations": [
{
"hw-address": "aa:bb:cc:dd:ee:ff",
<userinput>"next-server": "10.1.1.2",
"server-hostname": "server-hostname.example.org",
"boot-file-name": "/tmp/bootfile.efi"</userinput>
} ]
} ]
}</screen>
<para>Note that those parameters can be specified in combination with
other parameters for a reservation, e.g. reserved IPv4 address. These
parameters are optional, i.e. a subset of them can specified, or all of
them can be omitted.</para>
</section>
<section id="reservations4-mysql-pgsql">
<title>Storing host reservations in MySQL or PostgreSQL</title>

View File

@ -513,6 +513,24 @@
"item_type": "string",
"item_optional": false,
"item_default": "0.0.0.0"
},
{
"item_name": "next-server",
"item_type": "string",
"item_optional": true,
"item_default": "0.0.0.0"
},
{
"item_name": "server-hostname",
"item_type": "string",
"item_optional": true,
"item_default": ""
},
{
"item_name": "boot-file-name",
"item_type": "string",
"item_optional": true,
"item_default": ""
} ]
}
},

View File

@ -153,6 +153,9 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
// Check for static reservations.
alloc_engine->findReservation(*context_);
// Set siaddr, sname and file.
setReservedMessageFields();
}
}
};
@ -333,6 +336,27 @@ Dhcpv4Exchange::setHostIdentifiers() {
}
}
void
Dhcpv4Exchange::setReservedMessageFields() {
ConstHostPtr host = context_->host_;
// Nothing to do if host reservations not specified for this client.
if (host) {
if (!host->getNextServer().isV4Zero()) {
resp_->setSiaddr(host->getNextServer());
}
if (!host->getServerHostname().empty()) {
resp_->setSname(reinterpret_cast<
const uint8_t*>(host->getServerHostname().c_str()));
}
if (!host->getBootFileName().empty()) {
resp_->setFile(reinterpret_cast<
const uint8_t*>(host->getBootFileName().c_str()));
}
}
}
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
@ -1515,12 +1539,6 @@ Dhcpv4Srv::assignLease(Dhcpv4Exchange& ex) {
return;
}
// Set up siaddr. Perhaps assignLease is not the best place to call this
// as siaddr has nothing to do with a lease, but otherwise we would have
// to select subnet twice (performance hit) or update too many functions
// at once.
/// @todo: move subnet selection to a common code
resp->setSiaddr(subnet->getSiaddr());
// Get the server identifier. It will be used to determine the state
// of the client.
@ -2635,9 +2653,12 @@ Dhcpv4Srv::vendorClassSpecificProcessing(const Dhcpv4Exchange& ex) {
if (query->inClass(VENDOR_CLASS_PREFIX + DOCSIS3_CLASS_MODEM)) {
// Set next-server. This is TFTP server address. Cable modems will
// download their configuration from that server.
rsp->setSiaddr(subnet->getSiaddr());
// Do not override
if (rsp->getSiaddr().isV4Zero()) {
// Set next-server. This is TFTP server address. Cable modems will
// download their configuration from that server.
rsp->setSiaddr(subnet->getSiaddr());
}
// Now try to set up file field in DHCPv4 packet. We will just copy
// content of the boot-file option, which contains the same information.
@ -2664,6 +2685,12 @@ Dhcpv4Srv::vendorClassSpecificProcessing(const Dhcpv4Exchange& ex) {
rsp->setSiaddr(IOAddress::IPV4_ZERO_ADDRESS());
}
// Set up siaddr. Do not override siaddr if host specific value or
// vendor class specific value present.
if (rsp->getSiaddr().isV4Zero()) {
rsp->setSiaddr(subnet->getSiaddr());
}
return (true);
}

View File

@ -150,6 +150,10 @@ private:
/// host-reservation-identifiers
void setHostIdentifiers();
/// @brief Sets reserved values of siaddr, sname and file in the
/// server's response.
void setReservedMessageFields();
/// @brief Pointer to the allocation engine used by the server.
AllocEnginePtr alloc_engine_;
/// @brief Pointer to the DHCPv4 message sent by the client.

View File

@ -23,7 +23,7 @@ namespace test {
Dhcp4Client::Configuration::Configuration()
: routers_(), dns_servers_(), log_servers_(), quotes_servers_(),
serverid_("0.0.0.0") {
serverid_("0.0.0.0"), siaddr_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()) {
reset();
}
@ -34,6 +34,9 @@ Dhcp4Client::Configuration::reset() {
log_servers_.clear();
quotes_servers_.clear();
serverid_ = asiolink::IOAddress("0.0.0.0");
siaddr_ = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
sname_.clear();
boot_file_name_.clear();
lease_ = Lease4();
}
@ -178,6 +181,17 @@ Dhcp4Client::applyConfiguration() {
if (opt_vendor) {
config_.vendor_suboptions_ = opt_vendor->getOptions();
}
// siaddr
config_.siaddr_ = resp->getSiaddr();
// sname
OptionBuffer buf = resp->getSname();
// sname is a fixed length field holding null terminated string. Use
// of c_str() guarantess that only a useful portion (ending with null
// character) is assigned.
config_.sname_.assign(std::string(buf.begin(), buf.end()).c_str());
// (boot)file
buf = resp->getFile();
config_.boot_file_name_.assign(std::string(buf.begin(), buf.end()).c_str());
// Server Identifier
OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));

View File

@ -81,6 +81,12 @@ public:
/// @brief Holds server id of the server which responded to the client's
/// request.
asiolink::IOAddress serverid_;
/// @brief Holds returned siaddr.
asiolink::IOAddress siaddr_;
/// @brief Holds returned sname.
std::string sname_;
/// @brief Holds returned (boot)file.
std::string boot_file_name_;
/// @brief Constructor.
Configuration();

View File

@ -71,6 +71,15 @@ namespace {
/// - The same as configuration 4, but using the following order of
/// host-reservation-identifiers: duid, circuit-id, hw-address.
///
/// - Configuration 6:
/// - This configuration provides reservations for next-server,
/// server-hostname and boot-file-name value.
/// - 1 subnet: 10.0.0.0/24
/// - 1 reservation for this subnet:
/// - Client's HW address: aa:bb:cc:dd:ee:ff
/// - next-server = 10.0.0.7
/// - server name = "some-name.example.org"
/// - boot-file-name = "bootfile.efi"
const char* DORA_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@ -233,6 +242,26 @@ const char* DORA_CONFIGS[] = {
" }"
" ]"
"} ]"
"}",
// Configuration 6
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"next-server\": \"10.0.0.1\","
"\"subnet4\": [ { "
" \"subnet\": \"10.0.0.0/24\", "
" \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
" \"reservations\": [ "
" {"
" \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
" \"next-server\": \"10.0.0.7\","
" \"server-hostname\": \"some-name.example.org\","
" \"boot-file-name\": \"bootfile.efi\""
" }"
" ]"
"} ]"
"}"
};
@ -989,6 +1018,32 @@ TEST_F(DORATest, changingHWAddress) {
EXPECT_FALSE(client.config_.lease_.client_id_);
}
// This test verifies that the server assigns reserved values for the
// siaddr, sname and file fields carried within DHCPv4 message.
TEST_F(DORATest, messageFieldsReservations) {
// Client has a reservation.
Dhcp4Client client(Dhcp4Client::SELECTING);
// Set explicit HW address so as it matches the reservation in the
// configuration used below.
client.setHWAddress("aa:bb:cc:dd:ee:ff");
// Configure DHCP server.
configure(DORA_CONFIGS[6], *client.getServer());
// Client performs 4-way exchange and should obtain a reserved
// address and fixed fields.
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<
IOAddress>(new IOAddress("0.0.0.0"))));
// Make sure that the server responded.
ASSERT_TRUE(client.getContext().response_);
Pkt4Ptr resp = client.getContext().response_;
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Check that the reserved values have been assigned.
EXPECT_EQ("10.0.0.7", client.config_.siaddr_.toText());
EXPECT_EQ("some-name.example.org", client.config_.sname_);
EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
}
// This test checks the following scenario:
// 1. Client A performs 4-way exchange and obtains a lease from the dynamic pool.
// 2. Reservation is created for the client A using an address out of the dynamic

View File

@ -39,6 +39,16 @@ namespace {
/// - Domain Name Server option present: 192.0.2.202, 192.0.2.203.
/// - Log Servers option present: 192.0.2.200 and 192.0.2.201
/// - Quotes Servers option present: 192.0.2.202, 192.0.2.203.
///
/// - Configuration 2:
/// - This configuration provides reservations for next-server,
/// server-hostname and boot-file-name value.
/// - 1 subnet: 192.0.2.0/24
/// - 1 reservation for this subnet:
/// - Client's HW address: aa:bb:cc:dd:ee:ff
/// - next-server = 10.0.0.7
/// - server name = "some-name.example.org"
/// - boot-file-name = "bootfile.efi"
const char* INFORM_CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@ -91,7 +101,26 @@ const char* INFORM_CONFIGS[] = {
" \"data\": \"10.0.0.202,10.0.0.203\""
" } ]"
" } ]"
"}"
"}",
// Configuration 2
"{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"next-server\": \"10.0.0.1\","
"\"subnet4\": [ { "
" \"subnet\": \"192.0.2.0/24\", "
" \"reservations\": [ "
" {"
" \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
" \"next-server\": \"10.0.0.7\","
" \"server-hostname\": \"some-name.example.org\","
" \"boot-file-name\": \"bootfile.efi\""
" }"
" ]"
"} ]"
"}",
};
/// @brief Test fixture class for testing DHCPINFORM.
@ -364,6 +393,32 @@ TEST_F(InformTest, relayedClientNoCiaddr) {
EXPECT_EQ("192.0.2.203", client.config_.dns_servers_[1].toText());
}
// This test verifies that the server assigns reserved values for the
// siaddr, sname and file fields carried within DHCPv4 message.
TEST_F(InformTest, messageFieldsReservations) {
// Client has a reservation.
Dhcp4Client client(Dhcp4Client::SELECTING);
// Message is relayed.
client.useRelay();
// Set explicit HW address so as it matches the reservation in the
// configuration used below.
client.setHWAddress("aa:bb:cc:dd:ee:ff");
// Configure DHCP server.
configure(INFORM_CONFIGS[2], *client.getServer());
// Client sends DHCPINFORM and should receive reserved fields.
ASSERT_NO_THROW(client.doInform());
// Make sure that the server responded.
ASSERT_TRUE(client.getContext().response_);
Pkt4Ptr resp = client.getContext().response_;
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
// Check that the reserved values have been assigned.
EXPECT_EQ("10.0.0.7", client.config_.siaddr_.toText());
EXPECT_EQ("some-name.example.org", client.config_.sname_);
EXPECT_EQ("bootfile.efi", client.config_.boot_file_name_);
}
/// This test verifies that after a client completes its INFORM exchange,
/// appropriate statistics are updated.
TEST_F(InformTest, statisticsInform) {

View File

@ -572,10 +572,13 @@ CfgHosts::add4(const HostPtr& host) {
DuidPtr duid = host->getDuid();
// There should be at least one resource reserved: hostname, IPv4
// address, IPv6 address or prefix.
// address, siaddr, sname, file or IPv6 address or prefix.
if (host->getHostname().empty() &&
(host->getIPv4Reservation().isV4Zero()) &&
(!host->hasIPv6Reservation()) &&
!host->hasIPv6Reservation() &&
host->getNextServer().isV4Zero() &&
host->getServerHostname().empty() &&
host->getBootFileName().empty() &&
host->getCfgOption4()->empty() &&
host->getCfgOption6()->empty()) {
std::ostringstream s;

View File

@ -5,6 +5,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <dhcp/pkt4.h>
#include <dhcpsrv/host.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
@ -74,14 +75,20 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
const asiolink::IOAddress& ipv4_reservation,
const std::string& hostname,
const std::string& dhcp4_client_classes,
const std::string& dhcp6_client_classes)
const std::string& dhcp6_client_classes,
const asiolink::IOAddress& next_server,
const std::string& server_host_name,
const std::string& boot_file_name)
: 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_(new CfgOption()), cfg_option6_(new CfgOption()) {
dhcp6_client_classes_(dhcp6_client_classes),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
server_host_name_(server_host_name), boot_file_name_(boot_file_name),
host_id_(0), cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) {
// Initialize host identifier.
setIdentifier(identifier, identifier_len, identifier_type);
@ -90,6 +97,11 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
// Validate and set IPv4 address reservation.
setIPv4Reservation(ipv4_reservation);
}
if (!next_server.isV4Zero()) {
// Validate and set next server address.
setNextServer(next_server);
}
}
Host::Host(const std::string& identifier, const std::string& identifier_name,
@ -97,14 +109,19 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
const asiolink::IOAddress& ipv4_reservation,
const std::string& hostname,
const std::string& dhcp4_client_classes,
const std::string& dhcp6_client_classes)
const std::string& dhcp6_client_classes,
const asiolink::IOAddress& next_server,
const std::string& server_host_name,
const std::string& boot_file_name)
: 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()) {
dhcp6_client_classes_(dhcp6_client_classes),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
server_host_name_(server_host_name), boot_file_name_(boot_file_name),
host_id_(0), cfg_option4_(new CfgOption()), cfg_option6_(new CfgOption()) {
// Initialize host identifier.
setIdentifier(identifier, identifier_name);
@ -113,6 +130,11 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
// Validate and set IPv4 address reservation.
setIPv4Reservation(ipv4_reservation);
}
if (!next_server.isV4Zero()) {
// Validate and set next server address.
setNextServer(next_server);
}
}
const std::vector<uint8_t>&
@ -339,6 +361,37 @@ Host::addClientClassInternal(ClientClasses& classes,
}
}
void
Host::setNextServer(const asiolink::IOAddress& next_server) {
if (!next_server.isV4()) {
isc_throw(isc::BadValue, "next server address '" << next_server
<< "' is not a valid IPv4 address");
} else if (next_server.isV4Bcast()) {
isc_throw(isc::BadValue, "invalid next server address '"
<< next_server << "'");
}
next_server_ = next_server;
}
void
Host::setServerHostname(const std::string& server_host_name) {
if (server_host_name.size() > Pkt4::MAX_SNAME_LEN - 1) {
isc_throw(isc::BadValue, "server hostname length must not exceed "
<< (Pkt4::MAX_SNAME_LEN - 1));
}
server_host_name_ = server_host_name;
}
void
Host::setBootFileName(const std::string& boot_file_name) {
if (boot_file_name.size() > Pkt4::MAX_FILE_LEN - 1) {
isc_throw(isc::BadValue, "boot file length must not exceed "
<< (Pkt4::MAX_FILE_LEN - 1));
}
boot_file_name_ = boot_file_name;
}
std::string
Host::toText() const {
std::ostringstream s;
@ -363,6 +416,16 @@ Host::toText() const {
s << " ipv4_reservation=" << (ipv4_reservation_.isV4Zero() ? "(no)" :
ipv4_reservation_.toText());
// Add next server.
s << " siaddr=" << (next_server_.isV4Zero() ? "(no)" :
next_server_.toText());
// Add server host name.
s << " sname=" << (server_host_name_.empty() ? "(empty)" : server_host_name_);
// Add boot file name.
s << " file=" << (boot_file_name_.empty() ? "(empty)" : boot_file_name_);
if (ipv6_reservations_.empty()) {
s << " ipv6_reservations=(none)";

View File

@ -214,6 +214,9 @@ public:
/// separated by commas. The names get trimmed by this constructor.
/// @param dhcp6_client_classes A string holding DHCPv6 client class names
/// separated by commas. The names get trimmed by this constructor.
/// @param next_server IPv4 address of next server (siaddr).
/// @param server_host_name Server host name (a.k.a. sname).
/// @param boot_file_name Boot file name (a.k.a. file).
///
/// @throw BadValue if the provided values are invalid. In particular,
/// if the identifier is invalid.
@ -223,7 +226,10 @@ public:
const asiolink::IOAddress& ipv4_reservation,
const std::string& hostname = "",
const std::string& dhcp4_client_classes = "",
const std::string& dhcp6_client_classes = "");
const std::string& dhcp6_client_classes = "",
const asiolink::IOAddress& next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS(),
const std::string& server_host_name = "",
const std::string& boot_file_name = "");
/// @brief Constructor.
///
@ -258,6 +264,9 @@ public:
/// separated by commas. The names get trimmed by this constructor.
/// @param dhcp6_client_classes A string holding DHCPv6 client class names
/// separated by commas. The names get trimmed by this constructor.
/// @param next_server IPv4 address of next server (siaddr).
/// @param server_host_name Server host name (a.k.a. sname).
/// @param boot_file_name Boot file name (a.k.a. file).
///
/// @throw BadValue if the provided values are invalid. In particular,
/// if the identifier is invalid.
@ -266,7 +275,10 @@ public:
const asiolink::IOAddress& ipv4_reservation,
const std::string& hostname = "",
const std::string& dhcp4_client_classes = "",
const std::string& dhcp6_client_classes = "");
const std::string& dhcp6_client_classes = "",
const asiolink::IOAddress& next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS(),
const std::string& server_host_name = "",
const std::string& boot_file_name = "");
/// @brief Replaces currently used identifier with a new identifier.
///
@ -449,6 +461,43 @@ public:
return (dhcp6_client_classes_);
}
/// @brief Sets new value for next server field (siaddr).
///
/// @param next_server New address of a next server.
///
/// @throw isc::BadValue if the provided address is not an IPv4 address,
/// is broadcast address.
void setNextServer(const asiolink::IOAddress& next_server);
/// @brief Returns value of next server field (siaddr).
const asiolink::IOAddress& getNextServer() const {
return (next_server_);
}
/// @brief Sets new value for server hostname (sname).
///
/// @param server_host_name New value for server hostname.
///
/// @throw BadValue if hostname is longer than 63 bytes.
void setServerHostname(const std::string& server_host_name);
/// @brief Returns value of server hostname (sname).
const std::string& getServerHostname() const {
return (server_host_name_);
}
/// @brief Sets new value for boot file name (file).
///
/// @param boot_file_name New value of boot file name.
///
/// @throw BadValue if boot file name is longer than 128 bytes.
void setBootFileName(const std::string& boot_file_name);
/// @brief Returns value of boot file name (file).
const std::string& getBootFileName() const {
return (boot_file_name_);
}
/// @brief Returns pointer to the DHCPv4 option data configuration for
/// this host.
///
@ -527,6 +576,12 @@ private:
ClientClasses dhcp4_client_classes_;
/// @brief Collection of classes associated with a DHCPv6 client.
ClientClasses dhcp6_client_classes_;
/// @brief Next server (a.k.a. siaddr, carried in DHCPv4 message).
asiolink::IOAddress next_server_;
/// @brief Server host name (a.k.a. sname, carried in DHCPv4 message).
std::string server_host_name_;
/// @brief Boot file name (a.k.a. file, carried in DHCPv4 message)
std::string boot_file_name_;
/// @brief HostID (a unique identifier assigned when the host is stored in
/// MySQL or Pgsql)

View File

@ -63,6 +63,12 @@ const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
/// @brief Maximum length of option space name.
const size_t OPTION_SPACE_MAX_LEN = 128;
/// @brief Maximum length of the server hostname.
const size_t SERVER_HOSTNAME_MAX_LEN = 64;
/// @brief Maximum length of the boot file name.
const size_t BOOT_FILE_NAME_MAX_LEN = 128;
/// @brief Numeric value representing last supported identifier.
///
/// This value is used to validate whether the identifier type stored in
@ -81,7 +87,7 @@ class MySqlHostExchange {
private:
/// @brief Number of columns returned for SELECT queries send by this class.
static const size_t HOST_COLUMNS = 9;
static const size_t HOST_COLUMNS = 12;
public:
@ -99,17 +105,25 @@ public:
dhcp4_subnet_id_(0), dhcp6_subnet_id_(0), ipv4_address_(0),
hostname_length_(0), dhcp4_client_classes_length_(0),
dhcp6_client_classes_length_(0),
dhcp4_next_server_(0),
dhcp4_server_hostname_length_(0),
dhcp4_boot_file_name_length_(0),
dhcp4_subnet_id_null_(MLM_FALSE),
dhcp6_subnet_id_null_(MLM_FALSE),
ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
dhcp4_client_classes_null_(MLM_FALSE),
dhcp6_client_classes_null_(MLM_FALSE) {
dhcp6_client_classes_null_(MLM_FALSE),
dhcp4_next_server_null_(MLM_FALSE),
dhcp4_server_hostname_null_(MLM_FALSE),
dhcp4_boot_file_name_null_(MLM_FALSE) {
// Fill arrays with 0 so as they don't include any garbage.
memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
memset(hostname_, 0, sizeof(hostname_));
memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
// Set the column names for use by this class. This only comprises
// names used by the MySqlHostExchange class. Derived classes will
@ -123,8 +137,11 @@ public:
columns_[6] = "hostname";
columns_[7] = "dhcp4_client_classes";
columns_[8] = "dhcp6_client_classes";
columns_[9] = "dhcp4_next_server";
columns_[10] = "dhcp4_server_hostname";
columns_[11] = "dhcp4_boot_file_name";
BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
BOOST_STATIC_ASSERT(11 < HOST_COLUMNS);
};
/// @brief Virtual destructor.
@ -307,6 +324,32 @@ public:
bind_[8].buffer = dhcp6_client_classes_;
bind_[8].buffer_length = classes6_txt.length();
// ipv4_address : INT UNSIGNED NULL
// The address in the Host structure is an IOAddress object. Convert
// this to an integer for storage.
dhcp4_next_server_ = static_cast<uint32_t>(host->getNextServer());
bind_[9].buffer_type = MYSQL_TYPE_LONG;
bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
bind_[9].is_unsigned = MLM_TRUE;
// bind_[9].is_null = &MLM_FALSE; // commented out for performance
// reasons, see memset() above
// dhcp4_server_hostname
bind_[10].buffer_type = MYSQL_TYPE_STRING;
std::string server_hostname = host->getServerHostname();
strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
SERVER_HOSTNAME_MAX_LEN - 1);
bind_[10].buffer = dhcp4_server_hostname_;
bind_[10].buffer_length = server_hostname.length();
// dhcp4_boot_file_name
bind_[11].buffer_type = MYSQL_TYPE_STRING;
std::string boot_file_name = host->getBootFileName();
strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
BOOT_FILE_NAME_MAX_LEN - 1);
bind_[11].buffer = dhcp4_boot_file_name_;
bind_[11].buffer_length = boot_file_name.length();
} catch (const std::exception& ex) {
isc_throw(DbOperationError,
"Could not create bind array from Host: "
@ -399,6 +442,31 @@ public:
bind_[8].length = &dhcp6_client_classes_length_;
bind_[8].is_null = &dhcp6_client_classes_null_;
// dhcp4_next_server
dhcp4_next_server_null_ = MLM_FALSE;
bind_[9].buffer_type = MYSQL_TYPE_LONG;
bind_[9].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
bind_[9].is_unsigned = MLM_TRUE;
bind_[9].is_null = &dhcp4_next_server_null_;
// dhcp4_server_hostname
dhcp4_server_hostname_null_ = MLM_FALSE;
dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
bind_[10].buffer_type = MYSQL_TYPE_STRING;
bind_[10].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
bind_[10].buffer_length = dhcp4_server_hostname_length_;
bind_[10].length = &dhcp4_server_hostname_length_;
bind_[10].is_null = &dhcp4_server_hostname_null_;
// dhcp4_boot_file_name
dhcp4_boot_file_name_null_ = MLM_FALSE;
dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
bind_[11].buffer_type = MYSQL_TYPE_STRING;
bind_[11].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
bind_[11].buffer_length = dhcp4_boot_file_name_length_;
bind_[11].length = &dhcp4_boot_file_name_length_;
bind_[11].is_null = &dhcp4_boot_file_name_null_;
// Add the error flags
setErrorIndicators(bind_, error_);
@ -468,10 +536,32 @@ public:
dhcp6_client_classes_length_);
}
// Set next server value (siaddr) if non NULL value returned.
asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
if (dhcp4_next_server_null_ == MLM_FALSE) {
next_server = asiolink::IOAddress(dhcp4_next_server_);
}
// Set server hostname (sname) if non NULL value returned.
std::string dhcp4_server_hostname;
if (dhcp4_server_hostname_null_ == MLM_FALSE) {
dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
dhcp4_server_hostname_length_);
}
// Set boot file name (file) if non NULL value returned.
std::string dhcp4_boot_file_name;
if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
dhcp4_boot_file_name_length_);
}
// Create and return Host object from the data gathered.
HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
hostname, dhcp4_client_classes, dhcp6_client_classes));
hostname, dhcp4_client_classes, dhcp6_client_classes,
next_server, dhcp4_server_hostname,
dhcp4_boot_file_name));
h->setHostId(host_id_);
return (h);
@ -580,6 +670,21 @@ private:
/// client classes.
unsigned long dhcp6_client_classes_length_;
/// Next server address (siaddr).
uint32_t dhcp4_next_server_;
/// Server hostname (sname).
char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];
/// A length of the string holding server hostname.
unsigned long dhcp4_server_hostname_length_;
/// Boot file name (file).
char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];
/// A length of the string holding boot file name.
unsigned long dhcp4_boot_file_name_length_;
/// @name Boolean values indicating if values of specific columns in
/// the database are NULL.
//@{
@ -603,6 +708,15 @@ private:
/// NULL.
my_bool dhcp6_client_classes_null_;
/// Boolean flag indicating if the value of next server is NULL.
my_bool dhcp4_next_server_null_;
/// Boolean flag indicating if the value of server hostname is NULL.
my_bool dhcp4_server_hostname_null_;
/// Boolean flag indicating if the value of boot file name is NULL.
my_bool dhcp4_boot_file_name_null_;
//@}
};
@ -1817,6 +1931,7 @@ TaggedStatementArray tagged_statements = { {
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
"h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
"o4.persistent, "
"o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
@ -1840,6 +1955,7 @@ TaggedStatementArray tagged_statements = { {
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent "
"FROM hosts AS h "
@ -1855,6 +1971,7 @@ TaggedStatementArray tagged_statements = { {
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent "
"FROM hosts AS h "
@ -1872,6 +1989,7 @@ TaggedStatementArray tagged_statements = { {
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
@ -1893,6 +2011,7 @@ TaggedStatementArray tagged_statements = { {
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
"h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent "
"FROM hosts AS h "
@ -1912,6 +2031,7 @@ TaggedStatementArray tagged_statements = { {
"h.dhcp_identifier_type, h.dhcp4_subnet_id, "
"h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
"h.dhcp4_client_classes, h.dhcp6_client_classes, "
"h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
"o.option_id, o.code, o.value, o.formatted_value, o.space, "
"o.persistent, "
"r.reservation_id, r.address, r.prefix_len, r.type, "
@ -1934,8 +2054,9 @@ TaggedStatementArray tagged_statements = { {
{MySqlHostDataSourceImpl::INSERT_HOST,
"INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
"dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
"dhcp4_client_classes, dhcp6_client_classes) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
"dhcp4_client_classes, dhcp6_client_classes, dhcp4_next_server, "
"dhcp4_server_hostname, dhcp4_boot_file_name) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
// Inserts a single IPv6 reservation into 'reservations' table.
{MySqlHostDataSourceImpl::INSERT_V6_RESRV,

View File

@ -49,6 +49,9 @@ getSupportedParams4(const bool identifiers_only = false) {
params_set.insert("hostname");
params_set.insert("ip-address");
params_set.insert("option-data");
params_set.insert("next-server");
params_set.insert("server-hostname");
params_set.insert("boot-file-name");
}
return (identifiers_only ? identifiers_set : params_set);
}
@ -120,7 +123,6 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
} else if (element.first == "hostname") {
hostname = element.second->stringValue();
}
} catch (const std::exception& ex) {
// Append line number where the error occurred.
@ -207,7 +209,16 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
if (element.first == "ip-address") {
host_->setIPv4Reservation(IOAddress(element.second->
stringValue()));
} else if (element.first == "next-server") {
host_->setNextServer(IOAddress(element.second->stringValue()));
} else if (element.first == "server-hostname") {
host_->setServerHostname(element.second->stringValue());
} else if (element.first == "boot-file-name") {
host_->setBootFileName(element.second->stringValue());
}
} catch (const std::exception& ex) {
// Append line number where the error occurred.
isc_throw(DhcpConfigError, ex.what() << " ("

View File

@ -73,8 +73,11 @@ private:
static const int HOSTNAME_COL = 6;
static const int DHCP4_CLIENT_CLASSES_COL = 7;
static const int DHCP6_CLIENT_CLASSES_COL = 8;
static const int DHCP4_NEXT_SERVER_COL = 9;
static const int DHCP4_SERVER_HOSTNAME_COL = 10;
static const int DHCP4_BOOT_FILE_NAME_COL = 11;
/// @brief Number of columns returned for SELECT queries send by this class.
static const size_t HOST_COLUMNS = 9;
static const size_t HOST_COLUMNS = 12;
public:
@ -99,8 +102,11 @@ public:
columns_[HOSTNAME_COL] = "hostname";
columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
BOOST_STATIC_ASSERT(8 < HOST_COLUMNS);
BOOST_STATIC_ASSERT(11 < HOST_COLUMNS);
};
/// @brief Virtual destructor.
@ -186,7 +192,7 @@ public:
bind_array->add(host->getIPv6SubnetID());
// ipv4_address : BIGINT NULL
bind_array->add(host->getIPv4Reservation());
bind_array->add((host->getIPv4Reservation()));
// hostname : VARCHAR(255) NULL
bind_array->add(host->getHostname());
@ -197,6 +203,16 @@ public:
// dhcp6_client_classes : VARCHAR(255) NULL
bind_array->addTempString(host->getClientClasses6().toText(","));
// dhcp4_next_server : BIGINT NULL
bind_array->add((host->getNextServer()));
// dhcp4_server_hostname : VARCHAR(64)
bind_array->add(host->getServerHostname());
// dhcp4_boot_file_name : VARCHAR(128)
bind_array->add(host->getBootFileName());
} catch (const std::exception& ex) {
host_.reset();
isc_throw(DbOperationError,
@ -221,7 +237,7 @@ public:
/// @param [out] hosts Collection of hosts to which a new host created
/// from the processed data should be inserted.
virtual void processRowData(ConstHostCollection& hosts,
const PgSqlResult& r, int row) {
const PgSqlResult& r, int row) {
// Peek at the host id , so we can skip it if we already have it
// This lets us avoid constructing a copy of host for each
// of its sub-rows (options, etc...)
@ -247,7 +263,7 @@ public:
/// @return HostPtr to the newly created Host object
/// @throw DbOperationError if the host cannot be created.
HostPtr retrieveHost(const PgSqlResult& r, int row,
const HostID& peeked_host_id = 0) {
const HostID& peeked_host_id = 0) {
// If the caller peeked ahead at the host_id use that, otherwise
// read it from the row.
@ -296,13 +312,28 @@ public:
std::string dhcp6_client_classes;
getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
// dhcp4_next_server : BIGINT NULL
uint32_t dhcp4_next_server_as_uint32;
getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
// dhcp4_server_hostname : VARCHAR(64)
std::string dhcp4_server_hostname;
getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
// dhcp4_boot_file_name : VARCHAR(128)
std::string dhcp4_boot_file_name;
getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
// Finally, attempt to create the new host.
HostPtr host;
try {
host.reset(new Host(identifier_value, identifier_len,
identifier_type, dhcp4_subnet_id,
dhcp6_subnet_id, ipv4_reservation, hostname,
dhcp4_client_classes, dhcp6_client_classes));
dhcp4_client_classes, dhcp6_client_classes,
dhcp4_next_server, dhcp4_server_hostname,
dhcp4_boot_file_name));
host->setHostId(host_id);
} catch (const isc::Exception& ex) {
@ -1302,6 +1333,7 @@ TaggedStatementArray tagged_statements = { {
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
" h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
" o4.persistent, "
" o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
@ -1323,8 +1355,9 @@ TaggedStatementArray tagged_statements = { {
{ OID_INT8 }, "get_host_addr",
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
" h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
" o.value, o.formatted_value, o.space, o.persistent "
" h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o.option_id, o.code, o.value, o.formatted_value, o.space, o.persistent "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
"WHERE ipv4_address = $1 "
@ -1340,8 +1373,9 @@ TaggedStatementArray tagged_statements = { {
"get_host_subid4_dhcpid",
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
" h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
" o.value, o.formatted_value, o.space, o.persistent "
" h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o.option_id, o.code, o.value, o.formatted_value, o.space, o.persistent "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
"WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
@ -1349,7 +1383,7 @@ TaggedStatementArray tagged_statements = { {
"ORDER BY h.host_id, o.option_id"
},
//PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
// PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
// Retrieves host information, IPv6 reservations and DHCPv6 options
// associated with a host. The number of rows returned is a multiplication
// of number of IPv6 reservations and DHCPv6 options.
@ -1360,6 +1394,7 @@ TaggedStatementArray tagged_statements = { {
" h.dhcp_identifier_type, h.dhcp4_subnet_id, "
" h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
" h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o.option_id, o.code, o.value, o.formatted_value, o.space, "
" o.persistent, "
" r.reservation_id, r.address, r.prefix_len, r.type, r.dhcp6_iaid "
@ -1381,8 +1416,9 @@ TaggedStatementArray tagged_statements = { {
"get_host_subid_addr",
"SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
" h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
" h.dhcp4_client_classes, h.dhcp6_client_classes, o.option_id, o.code, "
" o.value, o.formatted_value, o.space, o.persistent "
" h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o.option_id, o.code, o.value, o.formatted_value, o.space, o.persistent "
"FROM hosts AS h "
"LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
"WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
@ -1403,6 +1439,7 @@ TaggedStatementArray tagged_statements = { {
" h.dhcp_identifier_type, h.dhcp4_subnet_id, "
" h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
" h.dhcp4_client_classes, h.dhcp6_client_classes, "
" h.dhcp4_next_server, h.dhcp4_server_hostname, h.dhcp4_boot_file_name, "
" o.option_id, o.code, o.value, o.formatted_value, o.space, "
" o.persistent, "
" r.reservation_id, r.address, r.prefix_len, r.type, "
@ -1416,7 +1453,7 @@ TaggedStatementArray tagged_statements = { {
"ORDER BY h.host_id, o.option_id, r.reservation_id"
},
//PgSqlHostDataSourceImpl::GET_VERSION
// PgSqlHostDataSourceImpl::GET_VERSION
// Retrieves MySQL schema version.
{0,
{ OID_NONE },
@ -1426,15 +1463,16 @@ TaggedStatementArray tagged_statements = { {
// PgSqlHostDataSourceImpl::INSERT_HOST
// Inserts a host into the 'hosts' table. Returns the inserted host id.
{8,
{11,
{ OID_BYTEA, OID_INT2,
OID_INT4, OID_INT4, OID_INT8, OID_VARCHAR,
OID_VARCHAR, OID_VARCHAR },
"insert_host",
"INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
" dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
" dhcp4_client_classes, dhcp6_client_classes) "
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING host_id"
" dhcp4_client_classes, dhcp6_client_classes, "
" dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name) "
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING host_id"
},
//PgSqlHostDataSourceImpl::INSERT_V6_RESRV

View File

@ -242,6 +242,9 @@ void GenericHostDataSourceTest::compareHosts(const ConstHostPtr& host1,
EXPECT_EQ(host1->getIPv6SubnetID(), host2->getIPv6SubnetID());
EXPECT_EQ(host1->getIPv4Reservation(), host2->getIPv4Reservation());
EXPECT_EQ(host1->getHostname(), host2->getHostname());
EXPECT_EQ(host1->getNextServer(), host2->getNextServer());
EXPECT_EQ(host1->getServerHostname(), host2->getServerHostname());
EXPECT_EQ(host1->getBootFileName(), host2->getBootFileName());
// Compare IPv6 reservations
compareReservations6(host1->getIPv6Reservations(),
@ -1360,6 +1363,67 @@ GenericHostDataSourceTest::testMultipleClientClassesBoth() {
ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
}
void
GenericHostDataSourceTest::testMessageFields4() {
ASSERT_TRUE(hdsptr_);
// Create the Host object.
HostPtr host = initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
// And assign values for DHCPv4 message fields.
ASSERT_NO_THROW({
host->setNextServer(IOAddress("10.1.1.1"));
host->setServerHostname("server-name.example.org");
host->setBootFileName("bootfile.efi");
});
// Add the host.
ASSERT_NO_THROW(hdsptr_->add(host));
// Subnet id will be used in quries to the database.
SubnetID subnet_id = host->getIPv4SubnetID();
// Fetch the host via:
// getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
ConstHostCollection hosts_by_id = hdsptr_->getAll(host->getHWAddress());
ASSERT_EQ(1, hosts_by_id.size());
ASSERT_NO_FATAL_FAILURE(compareHosts(host, *hosts_by_id.begin()));
// Fetch the host via:
// getAll(const Host::IdentifierType, const uint8_t* identifier_begin,
// const size_t identifier_len) const;
hosts_by_id = hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
host->getIdentifier().size());
ASSERT_EQ(1, hosts_by_id.size());
ASSERT_NO_FATAL_FAILURE(compareHosts(host, *hosts_by_id.begin()));
// Fetch the host via
// getAll4(const asiolink::IOAddress& address) const;
hosts_by_id = hdsptr_->getAll4(IOAddress("192.0.2.5"));
ASSERT_EQ(1, hosts_by_id.size());
ASSERT_NO_FATAL_FAILURE(compareHosts(host, *hosts_by_id.begin()));
// Fetch the host via
// get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
// const DuidPtr& duid = DuidPtr()) const;
ConstHostPtr from_hds = hdsptr_->get4(subnet_id, host->getHWAddress());
ASSERT_TRUE(from_hds);
ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
// Fetch the host via
// get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
// const uint8_t* identifier_begin, const size_t identifier_len) const;
from_hds = hdsptr_->get4(subnet_id, host->getIdentifierType(), &host->getIdentifier()[0],
host->getIdentifier().size());
ASSERT_TRUE(from_hds);
ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
// Fetch the host via:
// get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
from_hds = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
ASSERT_TRUE(from_hds);
ASSERT_NO_FATAL_FAILURE(compareHosts(host, from_hds));
}
}; // namespace test
}; // namespace dhcp
}; // namespace isc

View File

@ -513,6 +513,13 @@ public:
///
void testMultipleClientClassesBoth();
/// @brief Test that siaddr, sname, file fields can be retrieved
/// from a database for a host.
///
/// Uses gtest macros to report failures.
///
void testMessageFields4();
/// @brief Returns DUID with identical content as specified HW address
///
/// This method does not have any sense in real life and is only useful

View File

@ -20,6 +20,7 @@
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
@ -299,6 +300,71 @@ TEST_F(HostReservationParserTest, dhcp4NoHostname) {
EXPECT_TRUE(hosts[0]->getHostname().empty());
}
// This test verifies that the parser can parse reservation entry
// containing next-server, server-hostname and boot-file-name values for
// DHCPv4 message fields.
TEST_F(HostReservationParserTest, dhcp4MessageFields) {
std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"next-server\": \"192.0.2.11\","
"\"server-hostname\": \"some-name.example.org\","
"\"boot-file-name\": \"/tmp/some-file.efi\" }";
ElementPtr config_element = Element::fromJSON(config);
HostReservationParser4 parser(SubnetID(10));
ASSERT_NO_THROW(parser.build(config_element));
CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
HostCollection hosts;
ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
&hwaddr_->hwaddr_[0],
hwaddr_->hwaddr_.size()));
ASSERT_EQ(1, hosts.size());
EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
EXPECT_EQ("192.0.2.11", hosts[0]->getNextServer().toText());
EXPECT_EQ("some-name.example.org", hosts[0]->getServerHostname());
EXPECT_EQ("/tmp/some-file.efi", hosts[0]->getBootFileName());
}
// This test verifies that the invalid value of the next server is rejected.
TEST_F(HostReservationParserTest, invalidNextServer) {
// Invalid IPv4 address.
std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"next-server\": \"192.0.2.foo\" }";
testInvalidConfig<HostReservationParser4>(config);
// Broadcast address.
config = "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"next-server\": \"255.255.255.255\" }";
testInvalidConfig<HostReservationParser4>(config);
// IPv6 address.
config = "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"next-server\": \"2001:db8:1::1\" }";
testInvalidConfig<HostReservationParser4>(config);
}
// This test verifies that the invalid server hostname is rejected.
TEST_F(HostReservationParserTest, invalidServerHostname) {
std::ostringstream config;
config << "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"server-hostname\": \"";
config << std::string(64, 'a');
config << "\" }";
testInvalidConfig<HostReservationParser4>(config.str());
}
// This test verifies that the invalid boot file name is rejected.
TEST_F(HostReservationParserTest, invalidBootFileName) {
std::ostringstream config;
config << "{ \"hw-address\": \"1:2:3:4:5:6\","
"\"boot-file-name\": \"";
config << std::string(128, 'a');
config << "\" }";
testInvalidConfig<HostReservationParser4>(config.str());
}
// This test verifies that the configuration parser for host reservations
// throws an exception when IPv6 address is specified for IPv4 address
@ -380,6 +446,30 @@ TEST_F(HostReservationParserTest, bcastAddress) {
testInvalidConfig<HostReservationParser4>(config);
}
// This test verifies that the configuration parser for host reservations
// throws an exception when invalid next server address is specified.
TEST_F(HostReservationParserTest, malformedNextServer) {
std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
"\"next-server\": \"192.0.2.bogus\" }";
testInvalidConfig<HostReservationParser4>(config);
}
// This test verifies that the configuration parser for host reservations
// throws an exception when zero next server address is specified.
TEST_F(HostReservationParserTest, zeroNextServer) {
std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
"\"next-server\": \"0.0.0.0\" }";
testInvalidConfig<HostReservationParser4>(config);
}
// This test verifies that the configuration parser for host reservations
// throws an exception when broadcast next server address is specified.
TEST_F(HostReservationParserTest, bcastNextServer) {
std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
"\"next-server\": \"255.255.255.255\" }";
testInvalidConfig<HostReservationParser4>(config);
}
// This test verifies that the configuration parser for host reservations
// throws an exception when unsupported parameter is specified.
TEST_F(HostReservationParserTest, invalidParameterName) {

View File

@ -192,7 +192,11 @@ TEST_F(HostTest, createFromHWAddrString) {
ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
SubnetID(1), SubnetID(2),
IOAddress("192.0.2.3"),
"somehost.example.org")));
"somehost.example.org",
std::string(), std::string(),
IOAddress("192.0.0.2"),
"server-hostname.example.org",
"bootfile.efi")));
// The HW address should be set to non-null.
HWAddrPtr hwaddr = host->getHWAddress();
ASSERT_TRUE(hwaddr);
@ -205,6 +209,9 @@ TEST_F(HostTest, createFromHWAddrString) {
EXPECT_EQ(2, host->getIPv6SubnetID());
EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
EXPECT_EQ("somehost.example.org", host->getHostname());
EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
EXPECT_EQ("bootfile.efi", host->getBootFileName());
// Use invalid identifier name
EXPECT_THROW(Host("01:02:03:04:05:06", "bogus", SubnetID(1), SubnetID(2),
@ -264,7 +271,12 @@ TEST_F(HostTest, createFromHWAddrBinary) {
Host::IDENT_HWADDR,
SubnetID(1), SubnetID(2),
IOAddress("192.0.2.3"),
"somehost.example.org")));
"somehost.example.org",
std::string(), std::string(),
IOAddress("192.0.0.2"),
"server-hostname.example.org",
"bootfile.efi")));
// Hardware address should be non-null.
HWAddrPtr hwaddr = host->getHWAddress();
ASSERT_TRUE(hwaddr);
@ -277,6 +289,9 @@ TEST_F(HostTest, createFromHWAddrBinary) {
EXPECT_EQ(2, host->getIPv6SubnetID());
EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
EXPECT_EQ("somehost.example.org", host->getHostname());
EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
EXPECT_EQ("bootfile.efi", host->getBootFileName());
}
// This test verifies that it is possible to create a Host object using
@ -644,11 +659,17 @@ TEST_F(HostTest, setValues) {
host->setIPv6SubnetID(SubnetID(234));
host->setIPv4Reservation(IOAddress("10.0.0.1"));
host->setHostname("other-host.example.org");
host->setNextServer(IOAddress("192.0.2.2"));
host->setServerHostname("server-hostname.example.org");
host->setBootFileName("bootfile.efi");
EXPECT_EQ(123, host->getIPv4SubnetID());
EXPECT_EQ(234, host->getIPv6SubnetID());
EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
EXPECT_EQ("other-host.example.org", host->getHostname());
EXPECT_EQ("192.0.2.2", host->getNextServer().toText());
EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
EXPECT_EQ("bootfile.efi", host->getBootFileName());
// Remove IPv4 reservation.
host->removeIPv4Reservation();
@ -664,6 +685,12 @@ TEST_F(HostTest, setValues) {
// Broadcast address can't be set.
EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_BCAST_ADDRESS()),
isc::BadValue);
// Broadcast and IPv6 are invalid addresses for next server.
EXPECT_THROW(host->setNextServer(asiolink::IOAddress::IPV4_BCAST_ADDRESS()),
isc::BadValue);
EXPECT_THROW(host->setNextServer(IOAddress("2001:db8:1::1")),
isc::BadValue);
}
// Test that Host constructors initialize client classes from string.
@ -918,6 +945,9 @@ TEST_F(HostTest, toText) {
EXPECT_EQ("hwaddr=010203040506 ipv4_subnet_id=1 ipv6_subnet_id=2"
" hostname=myhost.example.com"
" ipv4_reservation=192.0.2.3"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservation0=2001:db8:1::cafe"
" ipv6_reservation1=2001:db8:1::1"
" ipv6_reservation2=2001:db8:1:1::/64"
@ -931,6 +961,9 @@ TEST_F(HostTest, toText) {
EXPECT_EQ("hwaddr=010203040506 ipv6_subnet_id=2"
" hostname=(empty) ipv4_reservation=(no)"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservation0=2001:db8:1::cafe"
" ipv6_reservation1=2001:db8:1::1"
" ipv6_reservation2=2001:db8:1:1::/64"
@ -945,6 +978,9 @@ TEST_F(HostTest, toText) {
"myhost")));
EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservations=(none)", host->toText());
// Add some classes.
@ -952,6 +988,9 @@ TEST_F(HostTest, toText) {
host->addClientClass4("router");
EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservations=(none)"
" dhcp4_class0=modem dhcp4_class1=router",
host->toText());
@ -960,6 +999,9 @@ TEST_F(HostTest, toText) {
host->addClientClass6("device");
EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservations=(none)"
" dhcp4_class0=modem dhcp4_class1=router"
" dhcp6_class0=device dhcp6_class1=hub",

View File

@ -517,4 +517,10 @@ TEST_F(MySqlHostDataSourceTest, testAddRollback) {
EXPECT_FALSE(from_hds);
}
// This test checks that siaddr, sname, file fields can be retrieved
/// from a database for a host.
TEST_F(MySqlHostDataSourceTest, messageFields) {
testMessageFields4();
}
}; // Of anonymous namespace

View File

@ -475,4 +475,10 @@ TEST_F(PgSqlHostDataSourceTest, testAddRollback) {
EXPECT_FALSE(from_hds);
}
// This test checks that siaddr, sname, file fields can be retrieved
/// from a database for a host.
TEST_F(PgSqlHostDataSourceTest, messageFields) {
testMessageFields4();
}
}; // Of anonymous namespace

View File

@ -469,6 +469,11 @@ ALTER TABLE dhcp6_options
ALTER TABLE ipv6_reservations
MODIFY reservation_id INT UNSIGNED NOT NULL AUTO_INCREMENT;
# Add columns holding reservations for siaddr, sname and file fields
# carried within DHCPv4 message.
ALTER TABLE hosts ADD COLUMN dhcp4_next_server INT UNSIGNED NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) NULL;
# Update the schema version number
UPDATE schema_version

View File

@ -471,6 +471,12 @@ CREATE FUNCTION lease6DumpData() RETURNS
ORDER BY l.address;
$$ LANGUAGE SQL;
-- Add columns holding reservations for siaddr, sname and file fields
-- carried within DHCPv4 message.
ALTER TABLE hosts ADD COLUMN dhcp4_next_server BIGINT DEFAULT NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) DEFAULT NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) DEFAULT NULL;
-- Set 3.0 schema version.
UPDATE schema_version
SET version = '3', minor = '0';

View File

@ -257,6 +257,12 @@ CREATE FUNCTION lease6DumpData() RETURNS
ORDER BY l.address;
\$\$ LANGUAGE SQL;
-- Add columns holding reservations for siaddr, sname and file fields
-- carried within DHCPv4 message.
ALTER TABLE hosts ADD COLUMN dhcp4_next_server BIGINT DEFAULT NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_server_hostname VARCHAR(64) DEFAULT NULL;
ALTER TABLE hosts ADD COLUMN dhcp4_boot_file_name VARCHAR(128) DEFAULT NULL;
-- Set 3.0 schema version.
UPDATE schema_version
SET version = '3', minor = '0';