From: Marcin Siodelski Date: Tue, 10 Mar 2015 15:52:21 +0000 (+0100) Subject: [3688] DHCPv4 server assignes reserved hostname to the clients. X-Git-Tag: trac3764_base~12^2~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6364a03c067142c47d50c3179037bb7ebff6c16d;p=thirdparty%2Fkea.git [3688] DHCPv4 server assignes reserved hostname to the clients. --- diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index beadeda68e..8ad4781c07 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -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 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(*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(*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); } diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 67da636913..cd5d25b7ec 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -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. diff --git a/src/bin/dhcp4/tests/dhcp4_client.cc b/src/bin/dhcp4/tests/dhcp4_client.cc index 155b4b6f9e..18f59ac27c 100644 --- a/src/bin/dhcp4/tests/dhcp4_client.cc +++ b/src/bin/dhcp4/tests/dhcp4_client.cc @@ -64,6 +64,7 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr 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& 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_) { diff --git a/src/bin/dhcp4/tests/dhcp4_client.h b/src/bin/dhcp4/tests/dhcp4_client.h index 8ba097ad9a..b2b324cbfb 100644 --- a/src/bin/dhcp4/tests/dhcp4_client.h +++ b/src/bin/dhcp4/tests/dhcp4_client.h @@ -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 diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc index 3f3d78e571..8b32169562 100644 --- a/src/bin/dhcp4/tests/fqdn_unittest.cc +++ b/src/bin/dhcp4/tests/fqdn_unittest.cc @@ -17,9 +17,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -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(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(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(resp->getType())); + + // Once again check that the FQDN is as expected. + fqdn = boost::dynamic_pointer_cast(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(resp->getType())); + + // The new FQDN should contain a different name this time. + fqdn = boost::dynamic_pointer_cast(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(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(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(resp->getType())); + + // Once again check that the Hostname is as expected. + hostname = boost::dynamic_pointer_cast(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(resp->getType())); + + // The new hostname should be different than previously. + hostname = boost::dynamic_pointer_cast(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 diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 9a2516d363..0e75f19980 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -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, diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 1b4e6f31a4..d12d70f8c3 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -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 AllocEnginePtr; + }; // namespace isc::dhcp }; // namespace isc diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index 3ea9042bfa..fbe49699a2 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -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);