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:
@@ -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);
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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_) {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user