summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcin Siodelski <marcin@isc.org>2016-08-25 18:18:07 +0200
committerMarcin Siodelski <marcin@isc.org>2016-08-25 18:18:07 +0200
commit9b79fe005dd77328ea7c596fc6886f8fb838d1cf (patch)
tree06262dd383223a4f00eb95722b2cb9cf01989dd6
parent[master] Added ChangeLog entry 1156 for #4294. (diff)
parent[4552] Addressed review comments. (diff)
downloadkea-9b79fe005dd77328ea7c596fc6886f8fb838d1cf.tar.xz
kea-9b79fe005dd77328ea7c596fc6886f8fb838d1cf.zip
[master] Merge branch 'trac4552'
-rw-r--r--doc/guide/dhcp4-srv.xml29
-rw-r--r--src/bin/dhcp4/dhcp4.spec18
-rw-r--r--src/bin/dhcp4/dhcp4_srv.cc45
-rw-r--r--src/bin/dhcp4/dhcp4_srv.h4
-rw-r--r--src/bin/dhcp4/tests/dhcp4_client.cc16
-rw-r--r--src/bin/dhcp4/tests/dhcp4_client.h6
-rw-r--r--src/bin/dhcp4/tests/dora_unittest.cc55
-rw-r--r--src/bin/dhcp4/tests/inform_unittest.cc57
-rw-r--r--src/lib/dhcpsrv/cfg_hosts.cc7
-rw-r--r--src/lib/dhcpsrv/host.cc75
-rw-r--r--src/lib/dhcpsrv/host.h59
-rw-r--r--src/lib/dhcpsrv/mysql_host_data_source.cc133
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservation_parser.cc13
-rw-r--r--src/lib/dhcpsrv/pgsql_host_data_source.cc72
-rw-r--r--src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc64
-rw-r--r--src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h7
-rw-r--r--src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc90
-rw-r--r--src/lib/dhcpsrv/tests/host_unittest.cc46
-rw-r--r--src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc6
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc6
-rw-r--r--src/share/database/scripts/mysql/dhcpdb_create.mysql5
-rw-r--r--src/share/database/scripts/pgsql/dhcpdb_create.pgsql6
-rw-r--r--src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh.in6
23 files changed, 778 insertions, 47 deletions
diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml
index d995f53e90..23b67ec48b 100644
--- a/doc/guide/dhcp4-srv.xml
+++ b/doc/guide/dhcp4-srv.xml
@@ -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>
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 43940ef68b..dbec910df6 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -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": ""
} ]
}
},
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 1ef9d7f0f8..5d4b3d328d 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -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);
}
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 516ec9921f..2877220d34 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -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.
diff --git a/src/bin/dhcp4/tests/dhcp4_client.cc b/src/bin/dhcp4/tests/dhcp4_client.cc
index 28b6cc08bf..6773db4da6 100644
--- a/src/bin/dhcp4/tests/dhcp4_client.cc
+++ b/src/bin/dhcp4/tests/dhcp4_client.cc
@@ -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));
diff --git a/src/bin/dhcp4/tests/dhcp4_client.h b/src/bin/dhcp4/tests/dhcp4_client.h
index 480a8a426d..a30b7d821f 100644
--- a/src/bin/dhcp4/tests/dhcp4_client.h
+++ b/src/bin/dhcp4/tests/dhcp4_client.h
@@ -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();
diff --git a/src/bin/dhcp4/tests/dora_unittest.cc b/src/bin/dhcp4/tests/dora_unittest.cc
index edfdba556f..4c6f8f50a7 100644
--- a/src/bin/dhcp4/tests/dora_unittest.cc
+++ b/src/bin/dhcp4/tests/dora_unittest.cc
@@ -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
diff --git a/src/bin/dhcp4/tests/inform_unittest.cc b/src/bin/dhcp4/tests/inform_unittest.cc
index 5ed038b4dc..7a45b7e0a1 100644
--- a/src/bin/dhcp4/tests/inform_unittest.cc
+++ b/src/bin/dhcp4/tests/inform_unittest.cc
@@ -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) {
diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc
index 2f4a0d1785..748fd4d273 100644
--- a/src/lib/dhcpsrv/cfg_hosts.cc
+++ b/src/lib/dhcpsrv/cfg_hosts.cc
@@ -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;
diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc
index 3c54e188b2..8b3bbb3e1e 100644
--- a/src/lib/dhcpsrv/host.cc
+++ b/src/lib/dhcpsrv/host.cc
@@ -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)";
diff --git a/src/lib/dhcpsrv/host.h b/src/lib/dhcpsrv/host.h
index 8b38185a5e..fa080e67ce 100644
--- a/src/lib/dhcpsrv/host.h
+++ b/src/lib/dhcpsrv/host.h
@@ -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)
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc
index 622b0641bb..3dde4c4c04 100644
--- a/src/lib/dhcpsrv/mysql_host_data_source.cc
+++ b/src/lib/dhcpsrv/mysql_host_data_source.cc
@@ -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,
diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
index 491abfa495..f7e7a24680 100644
--- a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
+++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
@@ -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() << " ("
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc
index 2ab5280923..732db7cf1d 100644
--- a/src/lib/dhcpsrv/pgsql_host_data_source.cc
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc
@@ -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
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
index 42387388ad..515b6757b3 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.cc
@@ -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
diff --git a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
index 30701e2621..807ca21490 100644
--- a/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
+++ b/src/lib/dhcpsrv/tests/generic_host_data_source_unittest.h
@@ -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
diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
index 11ed6afe23..f083299927 100644
--- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
@@ -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
@@ -381,6 +447,30 @@ TEST_F(HostReservationParserTest, bcastAddress) {
}
// 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) {
// The "ip-addresses" parameter name is incorrect for the DHCPv4
diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc
index 535d861ef4..e51733bbb1 100644
--- a/src/lib/dhcpsrv/tests/host_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_unittest.cc
@@ -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",
diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
index f827f45e33..60359afd89 100644
--- a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
@@ -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
diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
index 29dcf29864..b5456bfb8e 100644
--- a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
@@ -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
diff --git a/src/share/database/scripts/mysql/dhcpdb_create.mysql b/src/share/database/scripts/mysql/dhcpdb_create.mysql
index 80aaf082c2..fefebb53e5 100644
--- a/src/share/database/scripts/mysql/dhcpdb_create.mysql
+++ b/src/share/database/scripts/mysql/dhcpdb_create.mysql
@@ -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
diff --git a/src/share/database/scripts/pgsql/dhcpdb_create.pgsql b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
index 2d5241a6c4..8052ca53ef 100644
--- a/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
+++ b/src/share/database/scripts/pgsql/dhcpdb_create.pgsql
@@ -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';
diff --git a/src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh.in b/src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh.in
index 660142cecc..7163d3eea6 100644
--- a/src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh.in
+++ b/src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh.in
@@ -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';