2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 22:15:23 +00:00

[3688] DHCPv4 server assignes reserved hostname to the clients.

This commit is contained in:
Marcin Siodelski
2015-03-10 16:52:21 +01:00
parent ac39054345
commit 6364a03c06
8 changed files with 549 additions and 87 deletions

View File

@@ -43,6 +43,7 @@
#include <asio.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <iomanip>
@@ -80,6 +81,152 @@ struct Dhcp4Hooks {
// 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 {
@@ -137,6 +284,11 @@ Dhcpv4Srv::shutdown() {
shutdown_ = true;
}
isc::dhcp::Subnet4Ptr
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
return (DHCPv4Exchange::selectSubnet(question));
}
Pkt4Ptr
Dhcpv4Srv::receivePacket(int timeout) {
return (IfaceMgr::instance().receive4(timeout));
@@ -150,6 +302,9 @@ Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
bool
Dhcpv4Srv::run() {
while (!shutdown_) {
// Reset pointer to the current exchange.
exchange.reset();
// client's message and server's response
Pkt4Ptr query;
Pkt4Ptr rsp;
@@ -592,7 +747,7 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
// 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
@@ -629,7 +784,7 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
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
@@ -696,7 +851,7 @@ Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
sizeof(required_options) / sizeof(required_options[0]);
// Get the subnet.
Subnet4Ptr subnet = selectSubnet(question);
Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(question);
if (!subnet) {
return;
}
@@ -764,10 +919,16 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
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
@@ -818,17 +979,23 @@ Dhcpv4Srv::processHostnameOption(const OptionStringPtr& opt_hostname,
// 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
@@ -933,7 +1100,7 @@ void
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
@@ -1072,13 +1239,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
}
}
// 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);
@@ -1342,6 +1514,9 @@ Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
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);
@@ -1390,6 +1565,9 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
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);
@@ -1437,6 +1615,9 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
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);
@@ -1548,12 +1729,20 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
}
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);
@@ -1611,56 +1800,6 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
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
@@ -1725,7 +1864,8 @@ Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
// 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
@@ -2012,7 +2152,7 @@ void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
Subnet4Ptr subnet = selectSubnet(query);
Subnet4Ptr subnet = DHCPv4Exchange::selectSubnet(query);
if (!subnet) {
return (true);
}

View File

@@ -619,7 +619,7 @@ protected:
///
/// @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.

View File

@@ -64,6 +64,7 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
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"),
@@ -150,6 +151,8 @@ Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
context_.query_ = createMsg(DHCPDISCOVER);
// Request options if any.
includePRL();
// Include FQDN or Hostname.
includeName();
if (requested_addr) {
addRequestedAddress(*requested_addr);
}
@@ -239,6 +242,8 @@ Dhcp4Client::doRequest() {
// Request options if any.
includePRL();
// Include FQDN or Hostname.
includeName();
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
@@ -249,6 +254,33 @@ Dhcp4Client::doRequest() {
}
}
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_) {

View File

@@ -217,6 +217,21 @@ public:
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
@@ -345,6 +360,13 @@ private:
/// @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)
@@ -376,6 +398,12 @@ private:
/// @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_;
@@ -406,4 +434,4 @@ private:
} // end of namespace isc::dhcp
} // end of namespace isc
#endif // DHCP4_CLIENT
#endif // DHCP4_CLIENT_H

View File

@@ -17,9 +17,12 @@
#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>
@@ -32,20 +35,91 @@ using namespace isc::dhcp_ddns;
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();
}
@@ -412,10 +486,6 @@ public:
}
}
}
NakedDhcpv4Srv* srv_;
};
// Test that the exception is thrown if lease pointer specified as the argument
@@ -1033,5 +1103,194 @@ TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
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

View File

@@ -1228,7 +1228,7 @@ AllocEngine::ClientContext4::ClientContext4()
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,

View File

@@ -682,7 +682,7 @@ public:
/// 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_;
@@ -748,7 +748,7 @@ public:
/// @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,
@@ -1079,6 +1079,9 @@ private:
ClientContext4& ctx) const;
};
/// @brief A pointer to the @c AllocEngine object.
typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
}; // namespace isc::dhcp
}; // namespace isc

View File

@@ -226,7 +226,7 @@ TEST_F(AllocEngine4Test, allocateLease4Nulls) {
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);