#include <asio.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
#include <iomanip>
// module is called.
Dhcp4Hooks Hooks;
+namespace {
+
+/// @brief DHCPv4 message exchange.
+///
+/// This class represents the DHCPv4 message exchange. The message exchange
+/// consists of the single client message, server response to this message
+/// and the mechanisms to generate the server's response. The server creates
+/// the instance of the @c DHCPv4Exchange for each inbound message that it
+/// accepts for processing.
+///
+/// The use of the @c DHCPv4Exchange object as a central repository of
+/// information about the message exchange simplifies the API of the
+/// @c Dhcpv4Srv class.
+///
+/// Another benefit of using this class is that different methods of the
+/// @c Dhcpv4Srv may share information. For example, the constructor of this
+/// class selects the subnet and multiple methods of @c Dhcpv4Srv use this
+/// subnet, without the need to select it again.
+///
+/// @todo This is the initial version of this class. In the future a lot of
+/// code from the @c Dhcpv4Srv class will be migrated here.
+class DHCPv4Exchange {
+public:
+ /// @brief Constructor.
+ ///
+ /// The constructor selects the subnet for the query and checks for the
+ /// static host reservations for the client which has sent the message.
+ /// The information about the reservations is stored in the
+ /// @c AllocEngine::ClientContext4 object, which can be obtained by
+ /// calling the @c getContext.
+ ///
+ /// @param alloc_engine Pointer to the instance of the Allocation Engine
+ /// used by the server.
+ /// @param query Pointer to the client message.
+ DHCPv4Exchange(const AllocEnginePtr& alloc_engine, const Pkt4Ptr& query);
+
+ /// @brief Selects the subnet for the message processing.
+ ///
+ /// The pointer to the selected subnet is stored in the @c ClientContext4
+ /// structure.
+ void selectSubnet();
+
+ /// @brief Selects the subnet for the message processing.
+ ///
+ /// @todo This variant of the @c selectSubnet method is static and public so
+ /// as it may be invoked by the @c Dhcpv4Srv object. This is temporary solution
+ /// and the function will go away once the server code fully supports the use
+ /// of this class and it obtains the subnet from the context returned by the
+ /// @c getContext method.
+ ///
+ /// @param query Pointer to the client's message.
+ /// @return Pointer to the selected subnet or NULL if no suitable subnet
+ /// has been found.
+ static Subnet4Ptr selectSubnet(const Pkt4Ptr& query);
+
+ /// @brief Returns the copy of the context for the Allocation engine.
+ AllocEngine::ClientContext4 getContext() const {
+ return (context_);
+ }
+
+private:
+ /// @brief Pointer to the allocation engine used by the server.
+ AllocEnginePtr alloc_engine_;
+ /// @brief Pointer to the DHCPv4 message sent by the client.
+ Pkt4Ptr query_;
+ /// @brief Context for use with allocation engine.
+ AllocEngine::ClientContext4 context_;
+};
+
+/// @brief Type representing the pointer to the @c DHCPv4Exchange.
+typedef boost::shared_ptr<DHCPv4Exchange> DHCPv4ExchangePtr;
+
+DHCPv4Exchange::DHCPv4Exchange(const AllocEnginePtr& alloc_engine,
+ const Pkt4Ptr& query)
+ : alloc_engine_(alloc_engine), query_(query), context_() {
+ selectSubnet();
+ // Hardware address.
+ context_.hwaddr_ = query->getHWAddr();
+ // Client Identifier
+ OptionPtr opt_clientid = query->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt_clientid) {
+ context_.clientid_.reset(new ClientId(opt_clientid->getData()));
+ }
+ // Check for static reservations.
+ alloc_engine->findReservation(context_);
+};
+
+void
+DHCPv4Exchange::selectSubnet() {
+ context_.subnet_ = selectSubnet(query_);
+}
+
+Subnet4Ptr
+DHCPv4Exchange::selectSubnet(const Pkt4Ptr& query) {
+
+ Subnet4Ptr subnet;
+
+ SubnetSelector selector;
+ selector.ciaddr_ = query->getCiaddr();
+ selector.giaddr_ = query->getGiaddr();
+ selector.local_address_ = query->getLocalAddr();
+ selector.remote_address_ = query->getRemoteAddr();
+ selector.client_classes_ = query->classes_;
+ selector.iface_name_ = query->getIface();
+
+ CfgMgr& cfgmgr = CfgMgr::instance();
+ subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
+
+ // Let's execute all callouts registered for subnet4_select
+ if (HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // We're reusing callout_handle from previous calls
+ callout_handle->deleteAllArguments();
+
+ // Set new arguments
+ callout_handle->setArgument("query4", query);
+ callout_handle->setArgument("subnet4", subnet);
+ callout_handle->setArgument("subnet4collection",
+ cfgmgr.getCurrentCfg()->
+ getCfgSubnets4()->getAll());
+
+ // Call user (and server-side) callouts
+ HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
+ *callout_handle);
+
+ // Callouts decided to skip this step. This means that no subnet
+ // will be selected. Packet processing will continue, but it will
+ // be severely limited (i.e. only global options will be assigned)
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_SKIP);
+ return (Subnet4Ptr());
+ }
+
+ // Use whatever subnet was specified by the callout
+ callout_handle->getArgument("subnet4", subnet);
+ }
+
+ return (subnet);
+}
+
+DHCPv4ExchangePtr exchange;
+
+};
+
namespace isc {
namespace dhcp {
shutdown_ = true;
}
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+ return (DHCPv4Exchange::selectSubnet(question));
+}
+
Pkt4Ptr
Dhcpv4Srv::receivePacket(int timeout) {
return (IfaceMgr::instance().receive4(timeout));
bool
Dhcpv4Srv::run() {
while (!shutdown_) {
+ // Reset pointer to the current exchange.
+ exchange.reset();
+
// client's message and server's response
Pkt4Ptr query;
Pkt4Ptr rsp;
// Get the subnet relevant for the client. We will need it
// to get the options associated with it.
- Subnet4Ptr subnet = selectSubnet(question);
+ Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
// If we can't find the subnet for the client there is no way
// to get the options to be sent to a client. We don't log an
// error because it will be logged by the assignLease method
void
Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// Get the configured subnet suitable for the incoming packet.
- Subnet4Ptr subnet = selectSubnet(question);
+ Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
// Leave if there is no subnet matching the incoming packet.
// There is no need to log the error message here because
// it will be logged in the assignLease() when it fails to
sizeof(required_options) / sizeof(required_options[0]);
// Get the subnet.
- Subnet4Ptr subnet = selectSubnet(question);
+ Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
if (!subnet) {
return;
}
fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
fqdn->getFlag(Option4ClientFqdn::FLAG_E));
+ if (exchange && exchange->getContext().host_ &&
+ !exchange->getContext().host_->getHostname().empty()) {
+ fqdn_resp->setDomainName(exchange->getContext().host_->getHostname(),
+ Option4ClientFqdn::FULL);
- // Adjust the domain name based on domain name value and type sent by the
- // client and current configuration.
- d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
+ } else {
+ // Adjust the domain name based on domain name value and type sent by the
+ // client and current configuration.
+ d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
+ }
// Add FQDN option to the response message. Note that, there may be some
// cases when server may choose not to include the FQDN option in a
// By checking the number of labels present in the hostname we may infer
// whether client has sent the fully qualified or unqualified hostname.
- /// @todo We may want to reconsider whether it is appropriate for the
- /// client to send a root domain name as a Hostname. There are
- /// also extensions to the auto generation of the client's name,
- /// e.g. conversion to the puny code which may be considered at some point.
- /// For now, we just remain liberal and expect that the DNS will handle
- /// conversion if needed and possible.
- if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
- (label_count < 2)) {
+ // If there is a hostname reservation for this client, use it.
+ if (exchange && exchange->getContext().host_ &&
+ !exchange->getContext().host_->getHostname().empty()) {
+ opt_hostname_resp->setValue(exchange->getContext().host_->getHostname());
+
+ } else if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
+ (label_count < 2)) {
// Set to root domain to signal later on that we should replace it.
// DHO_HOST_NAME is a string option which cannot be empty.
+ /// @todo We may want to reconsider whether it is appropriate for the
+ /// client to send a root domain name as a Hostname. There are
+ /// also extensions to the auto generation of the client's name,
+ /// e.g. conversion to the puny code which may be considered at some point.
+ /// For now, we just remain liberal and expect that the DNS will handle
+ /// conversion if needed and possible.
opt_hostname_resp->setValue(".");
+
} else if (label_count == 2) {
// If there are two labels, it means that the client has specified
// the unqualified name. We have to concatenate the unqualified name
Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// We need to select a subnet the client is connected in.
- Subnet4Ptr subnet = selectSubnet(question);
+ Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
if (!subnet) {
// This particular client is out of luck today. We do not have
// information about the subnet he is connected to. This likely means
}
}
- // Use allocation engine to pick a lease for this client. Allocation engine
- // will try to honour the hint, but it is just a hint - some other address
- // may be used instead. If fake_allocation is set to false, the lease will
- // be inserted into the LeaseMgr as well.
- /// @todo pass the actual FQDN data.
- AllocEngine::ClientContext4 ctx(subnet, client_id, hwaddr, hint, fqdn_fwd,
- fqdn_rev, hostname, fake_allocation);
+ AllocEngine::ClientContext4 ctx;
+ if (exchange) {
+ ctx = exchange->getContext();
+ }
+ ctx.subnet_ = subnet;
+ ctx.clientid_ = client_id;
+ ctx.hwaddr_ = hwaddr;
+ ctx.requested_address_ = hint;
+ ctx.fwd_dns_update_ = fqdn_fwd;
+ ctx.rev_dns_update_ = fqdn_rev;
+ ctx.hostname_ = hostname;
+ ctx.fake_allocation_ = fake_allocation;
ctx.callout_handle_ = callout_handle;
Lease4Ptr lease = alloc_engine_->allocateLease4(ctx);
Pkt4Ptr
Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+ /// @todo Move this call to run() once we reorgnize some unit tests
+ /// which directly call this method.
+ exchange.reset(new DHCPv4Exchange(alloc_engine_, discover));
sanityCheck(discover, FORBIDDEN);
Pkt4Ptr
Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
+ /// @todo Move this call to run() once we reorgnize some unit tests
+ /// which directly call this method.
+ exchange.reset(new DHCPv4Exchange(alloc_engine_, request));
/// @todo Uncomment this (see ticket #3116)
/// sanityCheck(request, MANDATORY);
void
Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
+ /// @todo Move this call to run() once we reorgnize some unit tests
+ /// which directly call this method.
+ exchange.reset(new DHCPv4Exchange(alloc_engine_, release));
/// @todo Uncomment this (see ticket #3116)
/// sanityCheck(release, MANDATORY);
}
void
-Dhcpv4Srv::processDecline(Pkt4Ptr& /* decline */) {
+Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
+ /// @todo Move this call to run() once we reorgnize some unit tests
+ /// which directly call this method.
+ exchange.reset(new DHCPv4Exchange(alloc_engine_, decline));
+
/// @todo Implement this (also see ticket #3116)
}
Pkt4Ptr
Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
+ /// @todo Move this call to run() once we reorgnize some unit tests
+ /// which directly call this method.
+ exchange.reset(new DHCPv4Exchange(alloc_engine_, inform));
+
// DHCPINFORM MUST not include server identifier.
sanityCheck(inform, FORBIDDEN);
return (UNKNOWN);
}
-Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
-
- Subnet4Ptr subnet;
-
- SubnetSelector selector;
- selector.ciaddr_ = question->getCiaddr();
- selector.giaddr_ = question->getGiaddr();
- selector.local_address_ = question->getLocalAddr();
- selector.remote_address_ = question->getRemoteAddr();
- selector.client_classes_ = question->classes_;
- selector.iface_name_ = question->getIface();
-
- CfgMgr& cfgmgr = CfgMgr::instance();
- subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
-
- // Let's execute all callouts registered for subnet4_select
- if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
- CalloutHandlePtr callout_handle = getCalloutHandle(question);
-
- // We're reusing callout_handle from previous calls
- callout_handle->deleteAllArguments();
-
- // Set new arguments
- callout_handle->setArgument("query4", question);
- callout_handle->setArgument("subnet4", subnet);
- callout_handle->setArgument("subnet4collection",
- cfgmgr.getCurrentCfg()->
- getCfgSubnets4()->getAll());
-
- // Call user (and server-side) callouts
- HooksManager::callCallouts(hook_index_subnet4_select_,
- *callout_handle);
-
- // Callouts decided to skip this step. This means that no subnet
- // will be selected. Packet processing will continue, but it will
- // be severely limited (i.e. only global options will be assigned)
- if (callout_handle->getSkip()) {
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
- DHCP4_HOOK_SUBNET4_SELECT_SKIP);
- return (Subnet4Ptr());
- }
-
- // Use whatever subnet was specified by the callout
- callout_handle->getArgument("subnet4", subnet);
- }
-
- return (subnet);
-}
-
bool
Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
// Check that the message type is accepted by the server. We rely on the
// we validate the message type prior to calling this function.
return (false);
}
- return ((pkt->getLocalAddr() != IOAddress::IPV4_BCAST_ADDRESS() || selectSubnet(pkt)));
+ return ((pkt->getLocalAddr() != IOAddress::IPV4_BCAST_ADDRESS()
+ || DHCPv4Exchange::selectSubnet(pkt)));
}
bool
bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
- Subnet4Ptr subnet = selectSubnet(query);
+ Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(query);
if (!subnet) {
return (true);
}
///
/// @param question client's message
/// @return selected subnet (or NULL if no suitable subnet was found)
- isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
+ static isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
ciaddr_(IOAddress("0.0.0.0")),
curr_transid_(0),
dest_addr_("255.255.255.255"),
+ fqdn_(),
hwaddr_(generateHWAddr()),
iface_name_("eth0"),
relay_addr_("192.0.2.2"),
context_.query_ = createMsg(DHCPDISCOVER);
// Request options if any.
includePRL();
+ // Include FQDN or Hostname.
+ includeName();
if (requested_addr) {
addRequestedAddress(*requested_addr);
}
// Request options if any.
includePRL();
+ // Include FQDN or Hostname.
+ includeName();
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
}
}
+void
+Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option4ClientFqdn::DomainNameType fqdn_type) {
+ fqdn_.reset(new Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ fqdn_name, fqdn_type));
+}
+
+void
+Dhcp4Client::includeHostname(const std::string& name) {
+ hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
+}
+
+void
+Dhcp4Client::includeName() {
+ if (!context_.query_) {
+ isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
+ " when adding FQDN or Hostname option");
+ }
+
+ if (fqdn_) {
+ context_.query_->addOption(fqdn_);
+
+ } else if (hostname_) {
+ context_.query_->addOption(hostname_);
+ }
+}
+
void
Dhcp4Client::includePRL() {
if (!context_.query_) {
return (srv_);
}
+ /// @brief Creates an instance of the Client FQDN option to be included
+ /// in the client's message.
+ ///
+ /// @param flags Flags.
+ /// @param fqdn_name Name in the textual format.
+ /// @param fqdn_type Type of the name (fully qualified or partial).
+ void includeFQDN(const uint8_t flags, const std::string& fqdn_name,
+ Option4ClientFqdn::DomainNameType fqdn_type);
+
+ /// @brief Creates an instance of the Hostname option to be included
+ /// in the client's message.
+ ///
+ /// @param name Name to be stored in the option.
+ void includeHostname(const std::string& name);
+
/// @brief Modifies the client's HW address (adds one to it).
///
/// The HW address should be modified to test negative scenarios when the
/// @return An instance of the message created.
Pkt4Ptr createMsg(const uint8_t msg_type);
+ /// @brief Includes FQDN or Hostname option in the client's message.
+ ///
+ /// This method checks if @c fqdn_ or @c hostname_ is specified and
+ /// includes it in the client's message. If both are specified, the
+ /// @c fqdn_ will be used.
+ void includeName();
+
/// @brief Include PRL Option in the query message.
///
/// This function creates the instance of the PRL (Parameter Request List)
/// @brief Currently used destination address.
asiolink::IOAddress dest_addr_;
+ /// @brief FQDN requested by the client.
+ Option4ClientFqdnPtr fqdn_;
+
+ /// @brief Hostname requested by the client.
+ OptionStringPtr hostname_;
+
/// @brief Current hardware address of the client.
HWAddrPtr hwaddr_;
} // end of namespace isc::dhcp
} // end of namespace isc
-#endif // DHCP4_CLIENT
+#endif // DHCP4_CLIENT_H
#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option_int_array.h>
#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/tests/dhcp4_client.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
namespace {
+/// @brief Set of JSON configurations used by the FQDN tests.
+const char* CONFIGS[] = {
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"code\": 3,"
+ " \"data\": \"10.0.0.200,10.0.0.201\","
+ " \"csv-format\": true,"
+ " \"space\": \"dhcp4\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.5\","
+ " \"hostname\": \"unique-host.example.org\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}",
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 3000,"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"routers\","
+ " \"code\": 3,"
+ " \"data\": \"10.0.0.200,10.0.0.201\","
+ " \"csv-format\": true,"
+ " \"space\": \"dhcp4\""
+ " } ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"10.0.0.5\","
+ " \"hostname\": \"foobar.org\""
+ " }"
+ " ]"
+ " }],"
+ "\"dhcp-ddns\": {"
+ "\"enable-updates\": true,"
+ "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
+ "}"
+ "}"
+};
+
class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
public:
// Reference to D2ClientMgr singleton
D2ClientMgr& d2_mgr_;
+ /// @brief Pointer to the DHCP server instance.
+ NakedDhcpv4Srv* srv_;
+
+ /// @brief Interface Manager's fake configuration control.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
// Bit Constants for turning on and off DDNS configuration options.
static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
static const uint16_t OVERRIDE_NO_UPDATE = 2;
static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
static const uint16_t REPLACE_CLIENT_NAME = 8;
- NameDhcpv4SrvTest() : Dhcpv4SrvTest(),
- d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
+ NameDhcpv4SrvTest()
+ : Dhcpv4SrvTest(),
+ d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
+ srv_(NULL),
+ iface_mgr_test_config_(true)
+ {
srv_ = new NakedDhcpv4Srv(0);
+ IfaceMgr::instance().openSockets4();
// Config DDNS to be enabled, all controls off
enableD2();
}
}
}
}
-
-
- NakedDhcpv4Srv* srv_;
-
};
// Test that the exception is thrown if lease pointer specified as the argument
ASSERT_NO_THROW(srv_->processRelease(rel));
}
+// This test verifies that the server sends the FQDN option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, fqdnReservation) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Include the Client FQDN option.
+ ASSERT_NO_THROW(client.includeFQDN(Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E,
+ "client-name", Option4ClientFqdn::PARTIAL));
+ // Send the DHCPDISCOVER.
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the FQDN option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ Option4ClientFqdnPtr fqdn;
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("unique-host.example.org.", fqdn->getDomainName());
+
+ // When receiving DHCPDISCOVER, no NCRs should be generated.
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+
+ // Now send the DHCPREQUEST with including the FQDN option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the FQDN is as expected.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("unique-host.example.org.", fqdn->getDomainName());
+
+ // Because this is a new lease, there should be one NCR which adds the
+ // new DNS entry.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.org.",
+ "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+ "0D280858B1ED7696E174C4479E3372",
+ time(NULL), subnet_->getValid(), true);
+
+ // And that this FQDN has been stored in the lease database.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("unique-host.example.org.", lease->hostname_);
+
+ // Reconfigure DHCP server to use a different hostname for the client.
+ configure(CONFIGS[1], *client.getServer());
+
+ // Client is in the renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+ client.doRequest();
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // The new FQDN should contain a different name this time.
+ fqdn = boost::dynamic_pointer_cast<Option4ClientFqdn>(resp->getOption(DHO_FQDN));
+ ASSERT_TRUE(fqdn);
+ EXPECT_EQ("foobar.org.", fqdn->getDomainName());
+
+ // And the lease in the lease database should also contain this new FQDN.
+ lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("foobar.org.", lease->hostname_);
+
+ // Now there should be two name NCRs. One that removes the previous entry
+ // and the one that adds a new entry for the new hostname.
+ ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.org.",
+ "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+ "0D280858B1ED7696E174C4479E3372",
+ time(NULL), subnet_->getValid(), true);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "foobar.org.",
+ "000001B722C2FB5FAFE25B99178A0BFEC05127B9"
+ "5DC843E00941D444D53B24C2365337",
+ time(NULL), subnet_->getValid(), true);
+}
+
+// This test verifies that the server sends the Hostname option to the client
+// with the reserved hostname.
+TEST_F(NameDhcpv4SrvTest, hostnameReservation) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ // Use HW address that matches the reservation entry in the configuration.
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Configure DHCP server.
+ configure(CONFIGS[0], *client.getServer());
+ // Make sure that DDNS is enabled.
+ ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(client.getServer()->startD2());
+ // Include the Hostname option.
+ ASSERT_NO_THROW(client.includeHostname("client-name"));
+
+ // Send the DHCPDISCOVER
+ ASSERT_NO_THROW(client.doDiscover());
+
+ // Make sure that the server responded.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPOFFER, static_cast<int>(resp->getType()));
+
+ // Obtain the Hostname option sent in the response and make sure that the server
+ // has used the hostname reserved for this client.
+ OptionStringPtr hostname;
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("unique-host.example.org", hostname->getValue());
+
+ // Now send the DHCPREQUEST with including the Hostname option.
+ ASSERT_NO_THROW(client.doRequest());
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // Once again check that the Hostname is as expected.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("unique-host.example.org", hostname->getValue());
+
+ // And that this hostname has been stored in the lease database.
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("unique-host.example.org", lease->hostname_);
+
+ // Because this is a new lease, there should be one NCR which adds the
+ // new DNS entry.
+ ASSERT_EQ(1, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.org.",
+ "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+ "0D280858B1ED7696E174C4479E3372",
+ time(NULL), subnet_->getValid(), true);
+
+ // Reconfigure DHCP server to use a different hostname for the client.
+ configure(CONFIGS[1], *client.getServer());
+
+ // Client is in the renewing state.
+ client.setState(Dhcp4Client::RENEWING);
+ client.doRequest();
+ resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+
+ // The new hostname should be different than previously.
+ hostname = boost::dynamic_pointer_cast<OptionString>(resp->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(hostname);
+ EXPECT_EQ("foobar.org", hostname->getValue());
+
+ // And the lease in the lease database should also contain this new FQDN.
+ lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("foobar.org", lease->hostname_);
+
+ // Now there should be two name NCRs. One that removes the previous entry
+ // and the one that adds a new entry for the new hostname.
+ ASSERT_EQ(2, CfgMgr::instance().getD2ClientMgr().getQueueSize());
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ resp->getYiaddr().toText(),
+ "unique-host.example.org.",
+ "000001ACB52196C8F3BCC1DF3BA1F40BAC39BF23"
+ "0D280858B1ED7696E174C4479E3372",
+ time(NULL), subnet_->getValid(), true);
+
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+ resp->getYiaddr().toText(),
+ "foobar.org.",
+ "000001B722C2FB5FAFE25B99178A0BFEC05127B9"
+ "5DC843E00941D444D53B24C2365337",
+ time(NULL), subnet_->getValid(), true);
+}
+
} // end of anonymous namespace
old_lease_(), host_(), conflicting_lease_() {
}
-AllocEngine::ClientContext4::ClientContext4(const SubnetPtr& subnet,
+AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
const asiolink::IOAddress& requested_addr,
/// new information doesn't modify the API of the allocation engine.
struct ClientContext4 {
/// @brief Subnet selected for the client by the server.
- SubnetPtr subnet_;
+ Subnet4Ptr subnet_;
/// @brief Client identifier from the DHCP message.
ClientIdPtr clientid_;
/// @param fake_allocation Is this real i.e. REQUEST (false)
/// or just picking an address for DISCOVER that is not really
/// allocated (true)
- ClientContext4(const SubnetPtr& subnet, const ClientIdPtr& clientid,
+ ClientContext4(const Subnet4Ptr& subnet, const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
const asiolink::IOAddress& requested_addr,
const bool fwd_dns_update, const bool rev_dns_update,
ClientContext4& ctx) const;
};
+/// @brief A pointer to the @c AllocEngine object.
+typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
+
}; // namespace isc::dhcp
}; // namespace isc
ASSERT_TRUE(engine);
// Allocations without subnet are not allowed
- AllocEngine::ClientContext4 ctx1(SubnetPtr(), clientid_, hwaddr_,
+ AllocEngine::ClientContext4 ctx1(Subnet4Ptr(), clientid_, hwaddr_,
IOAddress("0.0.0.0"), false, false,
"", false);
Lease4Ptr lease = engine->allocateLease4(ctx1);