diff options
author | Mukund Sivaraman <muks@isc.org> | 2014-02-06 10:01:17 +0100 |
---|---|---|
committer | Mukund Sivaraman <muks@isc.org> | 2014-02-06 10:01:17 +0100 |
commit | 8b413f0b6f0325a124af6e170e9c96016d032007 (patch) | |
tree | e091a297313fbd71ab7fd76472cb9bd2d50c088e /src/bin/dhcp4 | |
parent | [2168] Fix more unittests (diff) | |
parent | Merge branch 'trac956' (diff) | |
download | kea-8b413f0b6f0325a124af6e170e9c96016d032007.tar.xz kea-8b413f0b6f0325a124af6e170e9c96016d032007.zip |
Merge branch 'master' into trac2168
Conflicts:
src/lib/dns/tests/rrset_unittest.cc
Diffstat (limited to 'src/bin/dhcp4')
-rw-r--r-- | src/bin/dhcp4/.gitignore | 1 | ||||
-rw-r--r-- | src/bin/dhcp4/config_parser.cc | 16 | ||||
-rw-r--r-- | src/bin/dhcp4/ctrl_dhcp4_srv.cc | 9 | ||||
-rw-r--r-- | src/bin/dhcp4/ctrl_dhcp4_srv.h | 3 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4.dox | 30 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4.spec | 91 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4_messages.mes | 17 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4_srv.cc | 373 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4_srv.h | 36 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/.gitignore | 3 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/Makefile.am | 3 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/config_parser_unittest.cc | 534 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 122 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_test_utils.cc | 8 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_test_utils.h | 12 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/fqdn_unittest.cc | 356 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/test_data_files_config.h.in | 17 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/wireshark.cc | 60 |
18 files changed, 1407 insertions, 284 deletions
diff --git a/src/bin/dhcp4/.gitignore b/src/bin/dhcp4/.gitignore index 86965b9f56..2ca0e8093f 100644 --- a/src/bin/dhcp4/.gitignore +++ b/src/bin/dhcp4/.gitignore @@ -4,3 +4,4 @@ /dhcp4_messages.h /spec_config.h /spec_config.h.pre +/s-messages diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc index 777cce5d2b..7774edcaa0 100644 --- a/src/bin/dhcp4/config_parser.cc +++ b/src/bin/dhcp4/config_parser.cc @@ -265,7 +265,7 @@ protected: Triplet<uint32_t> valid = getParam("valid-lifetime"); stringstream tmp; - tmp << addr.toText() << "/" << (int)len + tmp << addr << "/" << (int)len << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid; LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str()); @@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) { parser = new HooksLibrariesParser(config_id); } else if (config_id.compare("echo-client-id") == 0) { parser = new BooleanParser(config_id, globalContext()->boolean_values_); + } else if (config_id.compare("dhcp-ddns") == 0) { + parser = new D2ClientConfigParser(config_id); } else { isc_throw(NotImplemented, "Parser error: Global configuration parameter not supported: " @@ -433,6 +435,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str()); + // Before starting any subnet operations, let's reset the subnet-id counter, + // so newly recreated configuration starts with first subnet-id equal 1. + Subnet::resetSubnetID(); + // Some of the values specified in the configuration depend on // other values. Typically, the values in the subnet4 structure // depend on the global values. Also, option values configuration @@ -448,7 +454,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Some of the parsers alter the state of the system in a way that can't // easily be undone. (Or alter it in a way such that undoing the change has // the same risk of failure as doing the change.) - ParserPtr hooks_parser_; + ParserPtr hooks_parser; // The subnet parsers implement data inheritance by directly // accessing global storage. For this reason the global data @@ -489,7 +495,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // Executing commit will alter currently-loaded hooks // libraries. Check if the supplied libraries are valid, // but defer the commit until everything else has committed. - hooks_parser_ = parser; + hooks_parser = parser; parser->build(config_pair.second); } else { // Those parsers should be started before other @@ -557,8 +563,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) { // This occurs last as if it succeeds, there is no easy way // revert it. As a result, the failure to commit a subsequent // change causes problems when trying to roll back. - if (hooks_parser_) { - hooks_parser_->commit(); + if (hooks_parser) { + hooks_parser->commit(); } } catch (const isc::Exception& ex) { diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 43c08c9d74..72e0b219d9 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -226,7 +226,7 @@ void ControlledDhcpv4Srv::establishSession() { int ctrl_socket = cc_session_->getSocketDesc(); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED) .arg(ctrl_socket); - IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader); + IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader); } void ControlledDhcpv4Srv::disconnectSession() { @@ -235,13 +235,14 @@ void ControlledDhcpv4Srv::disconnectSession() { config_session_ = NULL; } if (cc_session_) { + + int ctrl_socket = cc_session_->getSocketDesc(); cc_session_->disconnect(); + + IfaceMgr::instance().deleteExternalSocket(ctrl_socket); delete cc_session_; cc_session_ = NULL; } - - // deregister session socket - IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL); } ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/) diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h index 526d98753c..59dde870ee 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.h +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h @@ -107,7 +107,8 @@ protected: /// various configuration values. Installing the dummy handler /// that guarantees to return success causes initial configuration /// to be stored for the session being created and that it can - /// be later accessed with \ref isc::ConfigData::getFullConfig. + /// be later accessed with + /// \ref isc::config::ConfigData::getFullConfig(). /// /// @param new_config new configuration. /// diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox index 0d457c6b48..aa43ee3267 100644 --- a/src/bin/dhcp4/dhcp4.dox +++ b/src/bin/dhcp4/dhcp4.dox @@ -155,6 +155,36 @@ The default behaviour is constituted by the set of constants defined in the (upper part of) dhcp4_srv.cc file. Once the configuration is implemented, these constants will be removed. +@section dhcpv4Classifier DHCPv4 Client Classification + +Kea DHCPv4 server currently supports simplified client classification. It is called +"simplified", because the incoming packets are classified based on the content +of the vendor class (60) option. More flexible classification is planned, but there +are no specific development dates agreed. + +For each incoming packet, @ref isc::dhcp::Dhcpv4Srv::classifyPacket() method is called. +It attempts to extract content of the vendor class option and interpret as a name +of the class. For now, the code has been tested with two classes used in cable modem +networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will +be interpreted as a class name. + +In principle any given packet can belong to zero or more classes. As the current +classifier is very modest, there's only one way to assign a class (based on vendor class +option), the ability to assign more than one class to a packet is not yet exercised. +Neverthless, there is such a possibility and it will be used in a near future. To +check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should +be used. + +Currently there is a short code section that alternates packet processing depending on +which class it belongs to. It is planned to move that capability to an external hook +library. See ticket #3275. The class specific behavior is: + +- docsis3.0 packets have siaddr (next server) field set +- docsis3.0 packets have file field set to the content of the boot-file-name option +- eRouter1.0 packets have siaddr (next server) field cleared + +Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. + @section dhcpv4Other Other DHCPv4 topics For hooks API support in DHCPv4, see @ref dhcpv4Hooks. diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec index 9372dfd8ad..04bcc7ac61 100644 --- a/src/bin/dhcp4/dhcp4.spec +++ b/src/bin/dhcp4/dhcp4.spec @@ -292,7 +292,96 @@ } } ] } - } + }, + + { "item_name": "dhcp-ddns", + "item_type": "map", + "item_optional": false, + "item_default": {"enable-updates": false}, + "item_description" : "Contains parameters pertaining DHCP-driven DDNS updates", + "map_item_spec": [ + { + "item_name": "enable-updates", + "item_type": "boolean", + "item_optional": false, + "item_default": false, + "item_description" : "Enables DDNS update processing" + }, + { + "item_name": "server-ip", + "item_type": "string", + "item_optional": true, + "item_default": "127.0.0.1", + "item_description" : "IP address of b10-dhcp-ddns (IPv4 or IPv6)" + }, + { + "item_name": "server-port", + "item_type": "integer", + "item_optional": true, + "item_default": 53001, + "item_description" : "port number of b10-dhcp-ddns" + }, + { + "item_name": "ncr-protocol", + "item_type": "string", + "item_optional": true, + "item_default": "UDP", + "item_description" : "Socket protocol to use with b10-dhcp-ddns" + }, + { + "item_name": "ncr-format", + "item_type": "string", + "item_optional": true, + "item_default": "JSON", + "item_description" : "Format of the update request packet" + }, + { + + "item_name": "always-include-fqdn", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Enable always including the FQDN option in its response" + }, + { + "item_name": "override-no-update", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Do update, even if client requested no updates with N flag" + }, + { + "item_name": "override-client-update", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Server performs an update even if client requested delegation" + }, + { + "item_name": "replace-client-name", + "item_type": "boolean", + "item_optional": true, + "item_default": false, + "item_description": "Should server replace the domain-name supplied by the client" + }, + { + "item_name": "generated-prefix", + "item_type": "string", + "item_optional": true, + "item_default": "myhost", + "item_description": "Prefix to use when generating the client's name" + }, + + { + "item_name": "qualifying-suffix", + "item_type": "string", + "item_optional": true, + "item_default": "example.com", + "item_description": "Fully qualified domain-name suffix if partial name provided by client" + }, + ] + }, + ], "commands": [ { diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes index f561695f90..297acfdc75 100644 --- a/src/bin/dhcp4/dhcp4_messages.mes +++ b/src/bin/dhcp4/dhcp4_messages.mes @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -32,6 +32,14 @@ This debug message is issued when the DHCP server was unable to process the FQDN or Hostname option sent by a client. This is likely because the client's name was malformed or due to internal server error. +% DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed +This debug message means that the server processing that is unique for each +client class has reported a failure. The response packet will not be sent. + +% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1 +This debug message informs that incoming packet has been assigned to specified +class or classes. This is a norma + % DHCP4_COMMAND_RECEIVED received command %1, arguments: %2 A debug message listing the command (and possible arguments) received from the BIND 10 control system by the IPv4 DHCP server. @@ -175,6 +183,13 @@ IPv4 DHCP server but it is not running. A debug message issued during startup, this indicates that the IPv4 DHCP server is about to open sockets on the specified port. +% DHCP4_PACKET_NOT_FOR_US received DHCPv4 message (transid=%1, iface=%2) dropped because it contains foreign server identifier +This debug message is issued when received DHCPv4 message is dropped because +it is addressed to a different server, i.e. a server identifier held by +this message doesn't match the identifier used by our server. The arguments +of this message hold the name of the transaction id and interface on which +the message has been received. + % DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1 A warning message issued when IfaceMgr fails to open and bind a socket. The reason for the failure is appended as an argument of the log message. diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 39dde03388..46ad0bbe2b 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -21,6 +21,7 @@ #include <dhcp/option_int.h> #include <dhcp/option_int_array.h> #include <dhcp/option_vendor.h> +#include <dhcp/option_string.h> #include <dhcp/pkt4.h> #include <dhcp/docsis3_option_defs.h> #include <dhcp4/dhcp4_log.h> @@ -79,36 +80,6 @@ Dhcp4Hooks Hooks; namespace isc { namespace dhcp { -namespace { - -// @todo The following constants describe server's behavior with respect to the -// DHCPv4 Client FQDN Option sent by a client. They will be removed -// when DDNS parameters for DHCPv4 are implemented with the ticket #3033. - -// @todo Additional configuration parameter which we may consider is the one -// that controls whether the DHCP server sends the removal NameChangeRequest -// if it discovers that the entry for the particular client exists or that -// it always updates the DNS. - -// Should server always include the FQDN option in its response, regardless -// if it has been requested in Parameter Request List Option (Disabled). -const bool FQDN_ALWAYS_INCLUDE = false; -// Enable A RR update delegation to the client (Disabled). -const bool FQDN_ALLOW_CLIENT_UPDATE = false; -// Globally enable updates (Enabled). -const bool FQDN_ENABLE_UPDATE = true; -// Do update, even if client requested no updates with N flag (Disabled). -const bool FQDN_OVERRIDE_NO_UPDATE = false; -// Server performs an update when client requested delegation (Enabled). -const bool FQDN_OVERRIDE_CLIENT_UPDATE = true; -// The fully qualified domain-name suffix if partial name provided by -// a client. -const char* FQDN_PARTIAL_SUFFIX = "example.com"; -// Should server replace the domain-name supplied by the client (Disabled). -const bool FQDN_REPLACE_CLIENT_NAME = false; - -} - Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast, const bool direct_response_desired) : shutdown_(true), alloc_engine_(), port_(port), @@ -259,6 +230,15 @@ Dhcpv4Srv::run() { } } + // Check if the DHCPv4 packet has been sent to us or to someone else. + // If it hasn't been sent to us, drop it! + if (!acceptServerId(query)) { + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US) + .arg(query->getTransid()) + .arg(query->getIface()); + continue; + } + // When receiving a packet without message type option, getType() will // throw. Let's set type to -1 as default error indicator. int type = -1; @@ -303,6 +283,9 @@ Dhcpv4Srv::run() { callout_handle->getArgument("query4", query); } + // Assign this packet to one or more classes if needed + classifyPacket(query); + try { switch (query->getType()) { case DHCPDISCOVER: @@ -359,6 +342,18 @@ Dhcpv4Srv::run() { continue; } + // Let's do class specific processing. This is done before + // pkt4_send. + // + /// @todo: decide whether we want to add a new hook point for + /// doing class specific processing. + if (!classSpecificProcessing(query, rsp)) { + /// @todo add more verbosity here + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_PROCESSING_FAILED); + + continue; + } + // Specifies if server should do the packing bool skip_pack = false; @@ -571,8 +566,8 @@ Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) { // The source address for the outbound message should have been set already. // This is the address that to the best of the server's knowledge will be // available from the client. - // @todo: perhaps we should consider some more sophisticated server id - // generation, but for the current use cases, it should be ok. + /// @todo: perhaps we should consider some more sophisticated server id + /// generation, but for the current use cases, it should be ok. response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, response->getLocalAddr())) ); @@ -641,8 +636,8 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer uint32_t vendor_id = vendor_req->getVendorId(); // Let's try to get ORO within that vendor-option - /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors - /// may have different policies. + /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other + /// vendors may have different policies. OptionUint8ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO)); @@ -748,68 +743,19 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn, // response to a client. Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn)); - // RFC4702, section 4 - set 'NOS' flags to 0. - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, 0); - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, 0); - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, 0); - - // Conditions when N flag has to be set to indicate that server will not - // perform DNS updates: - // 1. Updates are globally disabled, - // 2. Client requested no update and server respects it, - // 3. Client requested that the forward DNS update is delegated to the - // client but server neither respects requests for forward update - // delegation nor it is configured to send update on its own when - // client requested delegation. - if (!FQDN_ENABLE_UPDATE || - (fqdn->getFlag(Option4ClientFqdn::FLAG_N) && - !FQDN_OVERRIDE_NO_UPDATE) || - (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) && - !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) { - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, true); - - // Conditions when S flag is set to indicate that server will perform DNS - // update on its own: - // 1. Client requested that server performs DNS update and DNS updates are - // globally enabled. - // 2. Client requested that server delegates forward update to the client - // but server doesn't respect requests for delegation and it is - // configured to perform an update on its own when client requested the - // delegation. - } else if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) || - (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) && - !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) { - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, true); - } + // Set the server S, N, and O flags based on client's flags and + // current configuration. + D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); + d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp); - // Server MUST set the O flag if it has overriden the client's setting - // of S flag. - if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) != - fqdn_resp->getFlag(Option4ClientFqdn::FLAG_S)) { - fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, true); - } + // Carry over the client's E flag. + fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E, + fqdn->getFlag(Option4ClientFqdn::FLAG_E)); - // If client suppled partial or empty domain-name, server should generate - // one. - if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) { - std::ostringstream name; - if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) { - fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL); - } else { - name << fqdn->getDomainName(); - name << "." << FQDN_PARTIAL_SUFFIX; - fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL); - - } - - // Server may be configured to replace a name supplied by a client, even if - // client supplied fully qualified domain-name. The empty domain-name is - // is set to indicate that the name must be generated when the new lease - // is acquired. - } else if(FQDN_REPLACE_CLIENT_NAME) { - fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL); - } + // 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 @@ -829,15 +775,20 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn, void Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname, Pkt4Ptr& answer) { + // Fetch D2 configuration. + D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr(); + // Do nothing if the DNS updates are disabled. - if (!FQDN_ENABLE_UPDATE) { + if (!d2_mgr.ddnsEnabled()) { return; } std::string hostname = isc::util::str::trim(opt_hostname->readString()); unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname); // The hostname option sent by the client should be at least 1 octet long. - // If it isn't we ignore this option. + // If it isn't we ignore this option. (Per RFC 2131, section 3.14) + /// @todo It would be more liberal to accept this and let it fall into + /// the case of replace or less than two below. if (label_count == 0) { LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_EMPTY_HOSTNAME); return; @@ -855,21 +806,20 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& 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 (FQDN_REPLACE_CLIENT_NAME || (label_count < 2)) { + /// @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)) { opt_hostname_resp->writeString(""); - // If there are two labels, it means that the client has specified - // the unqualified name. We have to concatenate the unqalified name - // with the domain name. } else if (label_count == 2) { - std::ostringstream resp_hostname; - resp_hostname << hostname << "." << FQDN_PARTIAL_SUFFIX << "."; - opt_hostname_resp->writeString(resp_hostname.str()); + // If there are two labels, it means that the client has specified + // the unqualified name. We have to concatenate the unqalified name + // with the domain name. + opt_hostname_resp->writeString(d2_mgr.qualifyName(hostname)); } answer->addOption(opt_hostname_resp); @@ -902,17 +852,14 @@ Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease, // removal request for non-existent hostname. // - A server has performed reverse, forward or both updates. // - FQDN data between the new and old lease do not match. - if ((lease->hostname_ != old_lease->hostname_) || - (lease->fqdn_fwd_ != old_lease->fqdn_fwd_) || - (lease->fqdn_rev_ != old_lease->fqdn_rev_)) { + if (!lease->hasIdenticalFqdn(*old_lease)) { queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, old_lease); // If FQDN data from both leases match, there is no need to update. - } else if ((lease->hostname_ == old_lease->hostname_) && - (lease->fqdn_fwd_ == old_lease->fqdn_fwd_) && - (lease->fqdn_rev_ == old_lease->fqdn_rev_)) { + } else if (lease->hasIdenticalFqdn(*old_lease)) { return; + } } @@ -960,9 +907,9 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type, void Dhcpv4Srv::sendNameChangeRequests() { while (!name_change_reqs_.empty()) { - // @todo Once next NameChangeRequest is picked from the queue - // we should send it to the b10-dhcp_ddns module. Currently we - // just drop it. + /// @todo Once next NameChangeRequest is picked from the queue + /// we should send it to the b10-dhcp_ddns module. Currently we + /// just drop it. name_change_reqs_.pop(); } } @@ -981,8 +928,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { // thing this client can get is some global information (like DNS // servers). - // perhaps this should be logged on some higher level? This is most likely - // configuration bug. + // perhaps this should be logged on some higher level? This is most + // likely configuration bug. LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED) .arg(question->getRemoteAddr().toText()) .arg(serverReceivedPacketName(question->getType())); @@ -995,7 +942,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { // as siaddr has nothing to do with a lease, but otherwise we would have // to select subnet twice (performance hit) or update too many functions // at once. - // @todo: move subnet selection to a common code + /// @todo: move subnet selection to a common code answer->setSiaddr(subnet->getSiaddr()); LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED) @@ -1038,8 +985,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { (answer->getOption(DHO_HOST_NAME)); if (opt_hostname) { hostname = opt_hostname->readString(); - // @todo It could be configurable what sort of updates the server - // is doing when Hostname option was sent. + /// @todo It could be configurable what sort of updates the + /// server is doing when Hostname option was sent. fqdn_fwd = true; fqdn_rev = true; } @@ -1049,7 +996,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { // 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. + /// @todo pass the actual FQDN data. Lease4Ptr old_lease; Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr, hint, fqdn_fwd, fqdn_rev, @@ -1074,14 +1021,9 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { // generating the entire hostname for the client. The example of the // client's name, generated from the IP address is: host-192-0-2-3. if ((fqdn || opt_hostname) && lease->hostname_.empty()) { - hostname = lease->addr_.toText(); - // Replace dots with hyphens. - std::replace(hostname.begin(), hostname.end(), '.', '-'); - ostringstream stream; - // The partial suffix will need to be replaced with the actual - // domain-name for the client when configuration is implemented. - stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << "."; - lease->hostname_ = stream.str(); + lease->hostname_ = CfgMgr::instance() + .getD2ClientMgr().generateFqdn(lease->addr_); + // The operations below are rather safe, but we want to catch // any potential exceptions (e.g. invalid lease database backend // implementation) and log an error. @@ -1113,22 +1055,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) { // Subnet mask (type 1) answer->addOption(getNetmaskOption(subnet)); - // @todo: send renew timer option (T1, option 58) - // @todo: send rebind timer option (T2, option 59) + /// @todo: send renew timer option (T1, option 58) + /// @todo: send rebind timer option (T2, option 59) - // @todo Currently the NameChangeRequests are always generated if - // real (not fake) allocation is being performed. Should we have - // control switch to enable/disable NameChangeRequest creation? - // Perhaps we need a way to detect whether the b10-dhcp-ddns module - // is up an running? - if (!fake_allocation) { + // Create NameChangeRequests if DDNS is enabled and this is a + // real allocation. + if (!fake_allocation && CfgMgr::instance().ddnsEnabled()) { try { createNameChangeRequests(lease, old_lease); } catch (const Exception& ex) { LOG_ERROR(dhcp4_logger, DHCP4_NCR_CREATION_FAILED) .arg(ex.what()); } - } } else { @@ -1169,8 +1107,8 @@ Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) { // address for the response. Instead, we have to check what address our // socket is bound to and use it as a source address. This operation // may throw if for some reason the socket is closed. - // @todo Consider an optimization that we use local address from - // the query if this address is not broadcast. + /// @todo Consider an optimization that we use local address from + /// the query if this address is not broadcast. SocketInfo sock_info = IfaceMgr::instance().getSocket(*query); // Set local adddress, port and interface. response->setLocalAddr(sock_info.addr_); @@ -1285,7 +1223,7 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) { /// @todo Uncomment this (see ticket #3116) - // sanityCheck(request, MANDATORY); + /// sanityCheck(request, MANDATORY); Pkt4Ptr ack = Pkt4Ptr (new Pkt4(DHCPACK, request->getTransid())); @@ -1327,7 +1265,7 @@ void Dhcpv4Srv::processRelease(Pkt4Ptr& release) { /// @todo Uncomment this (see ticket #3116) - // sanityCheck(release, MANDATORY); + /// sanityCheck(release, MANDATORY); // Try to find client-id ClientIdPtr client_id; @@ -1352,7 +1290,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) { // Does the hardware address match? We don't want one client releasing // second client's leases. if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) { - // @todo: Print hwaddr from lease as part of ticket #2589 + /// @todo: Print hwaddr from lease as part of ticket #2589 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR) .arg(release->getCiaddr().toText()) .arg(client_id ? client_id->toText() : "(no client-id)") @@ -1410,9 +1348,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) { .arg(client_id ? client_id->toText() : "(no client-id)") .arg(release->getHWAddr()->toText()); - // Remove existing DNS entries for the lease, if any. - queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease); - + if (CfgMgr::instance().ddnsEnabled()) { + // Remove existing DNS entries for the lease, if any. + queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease); + } } else { // Release failed - LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL) @@ -1525,6 +1464,57 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) { return (subnet); } +bool +Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const { + // This function is meant to be called internally by the server class, so + // we rely on the caller to sanity check the pointer and we don't check + // it here. + + // Check if server identifier option is present. If it is not present + // we accept the message because it is targetted to all servers. + // Note that we don't check cases that server identifier is mandatory + // but not present. This is meant to be sanity checked in other + // functions. + OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!option) { + return (true); + } + // Server identifier is present. Let's convert it to 4-byte address + // and try to match with server identifiers used by the server. + OptionCustomPtr option_custom = + boost::dynamic_pointer_cast<OptionCustom>(option); + // Unable to convert the option to the option type which encapsulates it. + // We treat this as non-matching server id. + if (!option_custom) { + return (false); + } + // The server identifier option should carry exactly one IPv4 address. + // If the option definition for the server identifier doesn't change, + // the OptionCustom object should have exactly one IPv4 address and + // this check is somewhat redundant. On the other hand, if someone + // breaks option it may be better to check that here. + if (option_custom->getDataFieldsNum() != 1) { + return (false); + } + + // The server identifier MUST be an IPv4 address. If given address is + // v6, it is wrong. + IOAddress server_id = option_custom->readAddress(); + if (!server_id.isV4()) { + return (false); + } + + // This function iterates over all interfaces on which the + // server is listening to find the one which has a socket bound + // to the address carried in the server identifier option. + // This has some performance implications. However, given that + // typically there will be just a few active interfaces the + // performance hit should be acceptable. If it turns out to + // be significant, we will have to cache server identifiers + // when sockets are opened. + return (IfaceMgr::instance().hasOpenSocket(server_id)); +} + void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) { OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER); @@ -1602,8 +1592,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port, } // Let's reopen active sockets. openSockets4 will check internally whether // sockets are marked active or inactive. - // @todo Optimization: we should not reopen all sockets but rather select - // those that have been affected by the new configuration. + /// @todo Optimization: we should not reopen all sockets but rather select + /// those that have been affected by the new configuration. isc::dhcp::IfaceMgrErrorMsgCallback error_handler = boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1); if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) { @@ -1706,5 +1696,86 @@ Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) { LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg); } +void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) { + boost::shared_ptr<OptionString> vendor_class = + boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER)); + + string classes = ""; + + if (!vendor_class) { + return; + } + + // DOCSIS specific section + + // Let's keep this as a series of checks. So far we're supporting only + // docsis3.0, but there are also docsis2.0, docsis1.1 and docsis1.0. We + // may come up with adding several classes, e.g. for docsis2.0 we would + // add classes docsis2.0, docsis1.1 and docsis1.0. + + // Also we are using find, because we have at least one traffic capture + // where the user class was followed by a space ("docsis3.0 "). + + // For now, the code is very simple, but it is expected to get much more + // complex soon. One specific case is that the vendor class is an option + // sent by the client, so we should not trust it. To confirm that the device + // is indeed a modem, John B. suggested to check whether chaddr field + // quals subscriber-id option that was inserted by the relay (CMTS). + // This kind of logic will appear here soon. + if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) { + pkt->addClass(DOCSIS3_CLASS_MODEM); + classes += string(DOCSIS3_CLASS_MODEM) + " "; + } else + if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) { + pkt->addClass(DOCSIS3_CLASS_EROUTER); + classes += string(DOCSIS3_CLASS_EROUTER) + " "; + } else { + classes += vendor_class->getValue(); + pkt->addClass(vendor_class->getValue()); + } + + if (!classes.empty()) { + LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED) + .arg(classes); + } +} + +bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) { + + Subnet4Ptr subnet = selectSubnet(query); + if (!subnet) { + return (true); + } + + if (query->inClass(DOCSIS3_CLASS_MODEM)) { + + // Set next-server. This is TFTP server address. Cable modems will + // download their configuration from that server. + rsp->setSiaddr(subnet->getSiaddr()); + + // Now try to set up file field in DHCPv4 packet. We will just copy + // content of the boot-file option, which contains the same information. + Subnet::OptionDescriptor desc = + subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME); + + if (desc.option) { + boost::shared_ptr<OptionString> boot = + boost::dynamic_pointer_cast<OptionString>(desc.option); + if (boot) { + std::string filename = boot->getValue(); + rsp->setFile((const uint8_t*)filename.c_str(), filename.size()); + } + } + } + + if (query->inClass(DOCSIS3_CLASS_EROUTER)) { + + // Do not set TFTP server address for eRouter devices. + rsp->setSiaddr(IOAddress("0.0.0.0")); + } + + return (true); +} + } // namespace dhcp } // namespace isc diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index 324ba4fa0d..ec63e7fcef 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -167,6 +167,21 @@ public: protected: + /// @brief Verifies if the server id belongs to our server. + /// + /// This function checks if the server identifier carried in the specified + /// DHCPv4 message belongs to this server. If the server identifier option + /// is absent or the value carried by this option is equal to one of the + /// server identifiers used by the server, the true is returned. If the + /// server identifier option is present, but it doesn't match any server + /// identifier used by this server, the false value is returned. + /// + /// @param pkt DHCPv4 message which server identifier is to be checked. + /// + /// @return true, if the server identifier is absent or matches one of the + /// server identifiers that the server is using; false otherwise. + bool acceptServerId(const Pkt4Ptr& pkt) const; + /// @brief verifies if specified packet meets RFC requirements /// /// Checks if mandatory option is really there, that forbidden option @@ -247,7 +262,7 @@ protected: /// using separate options within their respective vendor-option spaces. /// /// @param question DISCOVER or REQUEST message from a client. - /// @param msg outgoing message (options will be added here) + /// @param answer outgoing message (options will be added here) void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer); /// @brief Assigns a lease and appends corresponding options @@ -544,6 +559,25 @@ protected: const std::string& option_space, isc::dhcp::OptionCollection& options); + /// @brief Assigns incoming packet to zero or more classes. + /// + /// @note For now, the client classification is very simple. It just uses + /// content of the vendor-class-identifier option as a class. The resulting + /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_ and + /// @ref isc::dhcp::Pkt4::inClass). + /// + /// @param pkt packet to be classified + void classifyPacket(const Pkt4Ptr& pkt); + + /// @brief Performs packet processing specific to a class + /// + /// This processing is a likely candidate to be pushed into hooks. + /// + /// @param query incoming client's packet + /// @param rsp server's response + /// @return true if successful, false otherwise (will prevent sending response) + bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp); + private: /// @brief Constructs netmask option based on subnet4 diff --git a/src/bin/dhcp4/tests/.gitignore b/src/bin/dhcp4/tests/.gitignore index 5d14dacb3f..5983892923 100644 --- a/src/bin/dhcp4/tests/.gitignore +++ b/src/bin/dhcp4/tests/.gitignore @@ -1 +1,4 @@ /dhcp4_unittests +/marker_file.h +/test_data_files_config.h +/test_libraries.h diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index c72b8b058d..91b5a3b0e8 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -58,7 +58,8 @@ if HAVE_GTEST # to unexpected errors. For this reason, the --enable-static-link option is # ignored for unit tests built here. -lib_LTLIBRARIES = libco1.la libco2.la +nodistdir=$(abs_top_builddir)/src/bin/dhcp4/tests +nodist_LTLIBRARIES = libco1.la libco2.la libco1_la_SOURCES = callout_library_1.cc callout_library_common.h libco1_la_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 1d9ccb7fa0..4071adc3fa 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -30,6 +30,7 @@ #include "marker_file.h" #include "test_libraries.h" +#include "test_data_files_config.h" #include <boost/foreach.hpp> #include <boost/scoped_ptr.hpp> @@ -50,6 +51,23 @@ using namespace std; namespace { +/// @brief Prepends the given name with the DHCP4 source directory +/// +/// @param name file name of the desired file +/// @return string containing the absolute path of the file in the DHCP source +/// directory. +std::string specfile(const std::string& name) { + return (std::string(DHCP4_SRC_DIR) + "/" + name); +} + +/// @brief Tests that the spec file is valid. +/// Verifies that the BIND10 DHCP-DDNS configuration specification file +// is valid. +TEST(Dhcp4SpecTest, basicSpec) { + (isc::config::moduleSpecFromFile(specfile("dhcp4.spec"))); + ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec"))); +} + class Dhcp4ParserTest : public ::testing::Test { public: Dhcp4ParserTest() @@ -119,19 +137,19 @@ public: params["name"] = param_value; params["space"] = "dhcp4"; params["code"] = "56"; - params["data"] = "AB CDEF0105"; + params["data"] = "ABCDEF0105"; params["csv-format"] = "False"; } else if (parameter == "space") { params["name"] = "dhcp-message"; params["space"] = param_value; params["code"] = "56"; - params["data"] = "AB CDEF0105"; + params["data"] = "ABCDEF0105"; params["csv-format"] = "False"; } else if (parameter == "code") { params["name"] = "dhcp-message"; params["space"] = "dhcp4"; params["code"] = param_value; - params["data"] = "AB CDEF0105"; + params["data"] = "ABCDEF0105"; params["csv-format"] = "False"; } else if (parameter == "data") { params["name"] = "dhcp-message"; @@ -143,7 +161,7 @@ public: params["name"] = "dhcp-message"; params["space"] = "dhcp4"; params["code"] = "56"; - params["data"] = "AB CDEF0105"; + params["data"] = "ABCDEF0105"; params["csv-format"] = param_value; } return (createConfigWithOption(params)); @@ -194,6 +212,62 @@ public: return (stream.str()); } + /// @brief Returns an option from the subnet. + /// + /// This function returns an option from a subnet to which the + /// specified subnet address belongs. The option is identified + /// by its code. + /// + /// @param subnet_address Address which belongs to the subnet from + /// which the option is to be returned. + /// @param option_code Code of the option to be returned. + /// @param expected_options_count Expected number of options in + /// the particular subnet. + /// + /// @return Descriptor of the option. If the descriptor holds a + /// NULL option pointer, it means that there was no such option + /// in the subnet. + Subnet::OptionDescriptor + getOptionFromSubnet(const IOAddress& subnet_address, + const uint16_t option_code, + const uint16_t expected_options_count = 1) { + Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address); + if (!subnet) { + /// @todo replace toText() with the use of operator <<. + ADD_FAILURE() << "A subnet for the specified address " + << subnet_address.toText() + << "does not exist in Config Manager"; + } + Subnet::OptionContainerPtr options = + subnet->getOptionDescriptors("dhcp4"); + if (expected_options_count != options->size()) { + ADD_FAILURE() << "The number of options in the subnet '" + << subnet_address.toText() << "' is different " + " than expected number of options '" + << expected_options_count << "'"; + } + + // Get the search index. Index #1 is to search using option code. + const Subnet::OptionContainerTypeIndex& idx = options->get<1>(); + + // Get the options for specified index. Expecting one option to be + // returned but in theory we may have multiple options with the same + // code so we get the range. + std::pair<Subnet::OptionContainerTypeIndex::const_iterator, + Subnet::OptionContainerTypeIndex::const_iterator> range = + idx.equal_range(option_code); + if (std::distance(range.first, range.second) > 1) { + ADD_FAILURE() << "There is more than one option having the" + " option code '" << option_code << "' in a subnet '" + << subnet_address.toText() << "'. Expected " + " at most one option"; + } else if (std::distance(range.first, range.second) == 0) { + return (Subnet::OptionDescriptor(OptionPtr(), false)); + } + + return (*range.first); + } + /// @brief Test invalid option parameter value. /// /// This test function constructs the simple configuration @@ -215,6 +289,24 @@ public: ASSERT_EQ(1, rcode_); } + /// @brief Test invalid option paramater value. + /// + /// This test function constructs the simple configuration + /// string and injects invalid option configuration into it. + /// It expects that parser will fail with provided option code. + /// + /// @param params Map of parameters defining an option. + void + testInvalidOptionParam(const std::map<std::string, std::string>& params) { + ConstElementPtr x; + std::string config = createConfigWithOption(params); + ElementPtr json = Element::fromJSON(config); + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(1, rcode_); + } + /// @brief Test option against given code and data. /// /// @param option_desc option descriptor that carries the option to @@ -256,6 +348,39 @@ public: expected_data_len)); } + /// @brief Test option configuration. + /// + /// This function creates a configuration for a specified option using + /// a map of parameters specified as the argument. The map holds + /// name/value pairs which identifies option's configuration parameters: + /// - name + /// - space + /// - code + /// - data + /// - csv-format. + /// This function applies a new server configuration and checks that the + /// option being configured is inserted into CfgMgr. The raw contents of + /// this option are compared with the binary data specified as expected + /// data passed to this function. + /// + /// @param params Map of parameters defining an option. + /// @param option_code Option code. + /// @param expected_data Array containing binary data expected to be stored + /// in the configured option. + /// @param expected_data_len Length of the array holding reference data. + void testConfiguration(const std::map<std::string, std::string>& params, + const uint16_t option_code, + const uint8_t* expected_data, + const size_t expected_data_len) { + std::string config = createConfigWithOption(params); + ASSERT_TRUE(executeConfiguration(config, "parse option configuration")); + // The subnet should now hold one option with the specified option code. + Subnet::OptionDescriptor desc = + getOptionFromSubnet(IOAddress("192.0.2.24"), option_code); + ASSERT_TRUE(desc.option); + testOption(desc, option_code, expected_data, expected_data_len); + } + /// @brief Parse and Execute configuration /// /// Parses a configuration and executes a configuration of the server. @@ -321,6 +446,7 @@ public: "\"renew-timer\": 1000, " "\"valid-lifetime\": 4000, " "\"subnet4\": [ ], " + "\"dhcp-ddns\": { \"enable-updates\" : false }, " "\"option-def\": [ ], " "\"option-data\": [ ] }"; static_cast<void>(executeConfiguration(config, @@ -410,8 +536,195 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) { EXPECT_EQ(1000, subnet->getT1()); EXPECT_EQ(2000, subnet->getT2()); EXPECT_EQ(4000, subnet->getValid()); + + // Check that subnet-id is 1 + EXPECT_EQ(1, subnet->getID()); +} + +// Goal of this test is to verify that multiple subnets get unique +// subnet-ids. Also, test checks that it's possible to do reconfiguration +// multiple times. +TEST_F(Dhcp4ParserTest, multipleSubnets) { + ConstElementPtr x; + string config = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ]," + " \"subnet\": \"192.0.3.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ]," + " \"subnet\": \"192.0.4.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ]," + " \"subnet\": \"192.0.5.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + ElementPtr json = Element::fromJSON(config); + + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + int cnt = 0; // Number of reconfigurations + + do { + ElementPtr json = Element::fromJSON(config); + + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(4, subnets->size()); // We expect 4 subnets + + // Check subnet-ids of each subnet (it should be monotonously increasing) + EXPECT_EQ(1, subnets->at(0)->getID()); + EXPECT_EQ(2, subnets->at(1)->getID()); + EXPECT_EQ(3, subnets->at(2)->getID()); + EXPECT_EQ(4, subnets->at(3)->getID()); + + // Repeat reconfiguration process 10 times and check that the subnet-id + // is set to the same value. Technically, just two iterations would be + // sufficient, but it's nice to have a test that exercises reconfiguration + // a bit. + } while (++cnt < 10); } +// Goal of this test is to verify that a previously configured subnet can be +// deleted in subsequent reconfiguration. +TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) { + ConstElementPtr x; + + // All four subnets + string config4 = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ]," + " \"subnet\": \"192.0.3.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ]," + " \"subnet\": \"192.0.4.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ]," + " \"subnet\": \"192.0.5.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + // Three subnets (the last one removed) + string config_first3 = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ]," + " \"subnet\": \"192.0.3.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ]," + " \"subnet\": \"192.0.4.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + // Second subnet removed + string config_second_removed = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ]," + " \"subnet\": \"192.0.4.0/24\" " + " }," + " {" + " \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ]," + " \"subnet\": \"192.0.5.0/24\" " + " } ]," + "\"valid-lifetime\": 4000 }"; + + // CASE 1: Configure 4 subnets, then reconfigure and remove the + // last one. + + ElementPtr json = Element::fromJSON(config4); + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(4, subnets->size()); // We expect 4 subnets + + // Do the reconfiguration (the last subnet is removed) + json = Element::fromJSON(config_first3); + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed) + + // Check subnet-ids of each subnet (it should be monotonously increasing) + EXPECT_EQ(1, subnets->at(0)->getID()); + EXPECT_EQ(2, subnets->at(1)->getID()); + EXPECT_EQ(3, subnets->at(2)->getID()); + + /// CASE 2: Configure 4 subnets, then reconfigure and remove one + /// from in between (not first, not last) + +#if 0 + /// @todo: Uncomment subnet removal test as part of #3281. + json = Element::fromJSON(config4); + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + // Do reconfiguration + json = Element::fromJSON(config_second_removed); + EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json)); + ASSERT_TRUE(x); + comment_ = parseAnswer(rcode_, x); + ASSERT_EQ(0, rcode_); + + subnets = CfgMgr::instance().getSubnets4(); + ASSERT_TRUE(subnets); + ASSERT_EQ(3, subnets->size()); // We expect 4 subnets + + EXPECT_EQ(1, subnets->at(0)->getID()); + // The second subnet (with subnet-id = 2) is no longer there + EXPECT_EQ(3, subnets->at(1)->getID()); + EXPECT_EQ(4, subnets->at(2)->getID()); +#endif + +} + +/// @todo: implement subnet removal test as part of #3281. + // Checks if the next-server defined as global parameter is taken into // consideration. TEST_F(Dhcp4ParserTest, nextServerGlobal) { @@ -1188,7 +1501,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) { " \"name\": \"dhcp-message\"," " \"space\": \"dhcp4\"," " \"code\": 56," - " \"data\": \"AB CDEF0105\"," + " \"data\": \"ABCDEF0105\"," " \"csv-format\": False" " }," " {" @@ -1261,7 +1574,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) { " \"name\": \"dhcp-message\"," " \"space\": \"dhcp4\"," " \"code\": 56," - " \"data\": \"AB CDEF0105\"," + " \"data\": \"ABCDEF0105\"," " \"csv-format\": False" " }," " {" @@ -1438,7 +1751,6 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) { " } ]" "}"; - json = Element::fromJSON(config); EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json)); @@ -1494,7 +1806,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) { " \"name\": \"dhcp-message\"," " \"space\": \"dhcp4\"," " \"code\": 56," - " \"data\": \"AB CDEF0105\"," + " \"data\": \"ABCDEF0105\"," " \"csv-format\": False" " }," " {" @@ -1545,6 +1857,89 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) { testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected)); } +// The goal of this test is to check that the option carrying a boolean +// value can be configured using one of the values: "true", "false", "0" +// or "1". +TEST_F(Dhcp4ParserTest, optionDataBoolean) { + // Create configuration. Use standard option 19 (ip-forwarding). + std::map<std::string, std::string> params; + params["name"] = "ip-forwarding"; + params["space"] = "dhcp4"; + params["code"] = "19"; + params["data"] = "true"; + params["csv-format"] = "true"; + + std::string config = createConfigWithOption(params); + ASSERT_TRUE(executeConfiguration(config, "parse configuration with a" + " boolean value")); + + // The subnet should now hold one option with the code 19. + Subnet::OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"), + 19); + ASSERT_TRUE(desc.option); + + // This option should be set to "true", represented as 0x1 in the option + // buffer. + uint8_t expected_option_data[] = { + 0x1 + }; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // Configure the option with the "1" value. This should have the same + // effect as if "true" was specified. + params["data"] = "1"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // The value of "1" with a few leading zeros should work too. + params["data"] = "00001"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // Configure the option with the "false" value. + params["data"] = "false"; + // The option buffer should now hold the value of 0. + expected_option_data[0] = 0; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // Specifying "0" should have the same effect as "false". + params["data"] = "0"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // The same effect should be for multiple 0 chars. + params["data"] = "00000"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // Bogus values should not be accepted. + params["data"] = "bugus"; + testInvalidOptionParam(params); + + params["data"] = "2"; + testInvalidOptionParam(params); + + // Now let's test that it is possible to use binary format. + params["data"] = "0"; + params["csv-format"] = "false"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // The binary 1 should work as well. + params["data"] = "1"; + expected_option_data[0] = 1; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + + // As well as an even number of digits. + params["data"] = "01"; + testConfiguration(params, 19, expected_option_data, + sizeof(expected_option_data)); + +} + // Goal of this test is to verify options configuration // for multiple subnets. TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { @@ -1622,6 +2017,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) { testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected)); } + + // Verify that empty option name is rejected in the configuration. TEST_F(Dhcp4ParserTest, optionNameEmpty) { // Empty option names not allowed. @@ -1672,14 +2069,6 @@ TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) { testInvalidOptionParam("0x0102", "data"); } -// Verify that option data consisting od an odd number of -// hexadecimal digits is rejected in the configuration. -TEST_F(Dhcp4ParserTest, optionDataOddLength) { - // Option code 0 is reserved and should not be accepted - // by configuration parser. - testInvalidOptionParam("123", "data"); -} - // Verify that either lower or upper case characters are allowed // to specify the option data. TEST_F(Dhcp4ParserTest, optionDataLowerCase) { @@ -1995,7 +2384,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) { " \"name\": \"option-one\"," " \"space\": \"vendor-4491\"," // VENDOR_ID_CABLE_LABS = 4491 " \"code\": 100," // just a random code - " \"data\": \"AB CDEF0105\"," + " \"data\": \"ABCDEF0105\"," " \"csv-format\": False" " }," " {" @@ -2127,7 +2516,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) { " \"name\": \"dhcp-message\"," " \"space\": \"dhcp4\"," " \"code\": 56," - " \"data\": \"AB CDEF0105\"," + " \"data\": \"ABCDEF0105\"," " \"csv-format\": False" " }," " {" @@ -2299,6 +2688,113 @@ TEST_F(Dhcp4ParserTest, allInterfaces) { EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2")); } +// This test checks the ability of the server to parse a configuration +// containing a full, valid dhcp-ddns (D2ClientConfig) entry. +TEST_F(Dhcp4ParserTest, d2ClientConfig) { + ConstElementPtr status; + + // Verify that the D2 configuraiton can be fetched and is set to disabled. + D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig(); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); + + // Verify that the convenience method agrees. + ASSERT_FALSE(CfgMgr::instance().ddnsEnabled()); + + string config_str = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" } ]," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : true, " + " \"server-ip\" : \"192.168.2.1\", " + " \"server-port\" : 777, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" }," + "\"valid-lifetime\": 4000 }"; + // Convert the JSON string to configuration elements. + ElementPtr config; + ASSERT_NO_THROW(config = Element::fromJSON(config_str)); + + // Pass the configuration in for parsing. + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config)); + + // check if returned status is OK + checkResult(status, 0); + + // Verify that DHCP-DDNS updating is enabled. + EXPECT_TRUE(CfgMgr::instance().ddnsEnabled()); + + // Verify that the D2 configuration can be retrieved. + d2_client_config = CfgMgr::instance().getD2ClientConfig(); + ASSERT_TRUE(d2_client_config); + + // Verify that the configuration values are correct. + EXPECT_TRUE(d2_client_config->getEnableUpdates()); + EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText()); + EXPECT_EQ(777, d2_client_config->getServerPort()); + EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol()); + EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat()); + EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn()); + EXPECT_TRUE(d2_client_config->getOverrideNoUpdate()); + EXPECT_TRUE(d2_client_config->getOverrideClientUpdate()); + EXPECT_TRUE(d2_client_config->getReplaceClientName()); + EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix()); + EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix()); +} + +// This test checks the ability of the server to handle a configuration +// containing an invalid dhcp-ddns (D2ClientConfig) entry. +TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) { + ConstElementPtr status; + + // Configuration string with an invalid D2 client config, + // "server-ip" is missing. + string config_str = "{ \"interfaces\": [ \"*\" ]," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"subnet4\": [ { " + " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ]," + " \"subnet\": \"192.0.2.0/24\" } ]," + " \"dhcp-ddns\" : {" + " \"enable-updates\" : true, " + " \"server-port\" : 5301, " + " \"ncr-protocol\" : \"UDP\", " + " \"ncr-format\" : \"JSON\", " + " \"always-include-fqdn\" : true, " + " \"allow-client-update\" : true, " + " \"override-no-update\" : true, " + " \"override-client-update\" : true, " + " \"replace-client-name\" : true, " + " \"generated-prefix\" : \"test.prefix\", " + " \"qualifying-suffix\" : \"test.suffix.\" }," + "\"valid-lifetime\": 4000 }"; + + // Convert the JSON string to configuration elements. + ElementPtr config; + ASSERT_NO_THROW(config = Element::fromJSON(config_str)); + + // Configuration should not throw, but should fail. + EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config)); + + // check if returned status is failed. + checkResult(status, 1); + + // Verify that the D2 configuraiton can be fetched and is set to disabled. + D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig(); + EXPECT_FALSE(d2_client_config->getEnableUpdates()); + + // Verify that the convenience method agrees. + ASSERT_FALSE(CfgMgr::instance().ddnsEnabled()); +} } diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc index 5142c70c06..e930ba0316 100644 --- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc +++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -584,7 +584,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) { // lifetime is correct, that T1 and T2 are returned properly checkAddressParams(offer, subnet_); - EXPECT_EQ(offer->getYiaddr().toText(), hint.toText()); + EXPECT_EQ(offer->getYiaddr(), hint); // Check identifiers checkServerId(offer, srv->getServerID()); @@ -624,7 +624,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) { // lifetime is correct, that T1 and T2 are returned properly checkAddressParams(offer, subnet_); - EXPECT_EQ(offer->getYiaddr().toText(), hint.toText()); + EXPECT_EQ(offer->getYiaddr(), hint); // Check identifiers checkServerId(offer, srv->getServerID()); @@ -664,7 +664,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) { // lifetime is correct, that T1 and T2 are returned properly checkAddressParams(offer, subnet_); - EXPECT_NE(offer->getYiaddr().toText(), hint.toText()); + EXPECT_NE(offer->getYiaddr(), hint); // Check identifiers checkServerId(offer, srv->getServerID()); @@ -735,12 +735,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) { checkClientId(offer3, clientid3); // Finally check that the addresses offered are different - EXPECT_NE(addr1.toText(), addr2.toText()); - EXPECT_NE(addr2.toText(), addr3.toText()); - EXPECT_NE(addr3.toText(), addr1.toText()); - cout << "Offered address to client1=" << addr1.toText() << endl; - cout << "Offered address to client2=" << addr2.toText() << endl; - cout << "Offered address to client3=" << addr3.toText() << endl; + EXPECT_NE(addr1, addr2); + EXPECT_NE(addr2, addr3); + EXPECT_NE(addr3, addr1); + cout << "Offered address to client1=" << addr1 << endl; + cout << "Offered address to client2=" << addr2 << endl; + cout << "Offered address to client3=" << addr3 << endl; } // Checks whether echoing back client-id is controllable, i.e. @@ -802,7 +802,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) { // Check if we get response at all checkResponse(ack, DHCPACK, 1234); - EXPECT_EQ(hint.toText(), ack->getYiaddr().toText()); + EXPECT_EQ(hint, ack->getYiaddr()); // Check that address was returned from proper range, that its lease // lifetime is correct, that T1 and T2 are returned properly @@ -886,9 +886,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) { IOAddress addr3 = ack3->getYiaddr(); // Check that every client received the address it requested - EXPECT_EQ(req_addr1.toText(), addr1.toText()); - EXPECT_EQ(req_addr2.toText(), addr2.toText()); - EXPECT_EQ(req_addr3.toText(), addr3.toText()); + EXPECT_EQ(req_addr1, addr1); + EXPECT_EQ(req_addr2, addr2); + EXPECT_EQ(req_addr3, addr3); // Check that the assigned address is indeed from the configured pool checkAddressParams(ack1, subnet_); @@ -910,12 +910,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) { l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3); // Finally check that the addresses offered are different - EXPECT_NE(addr1.toText(), addr2.toText()); - EXPECT_NE(addr2.toText(), addr3.toText()); - EXPECT_NE(addr3.toText(), addr1.toText()); - cout << "Offered address to client1=" << addr1.toText() << endl; - cout << "Offered address to client2=" << addr2.toText() << endl; - cout << "Offered address to client3=" << addr3.toText() << endl; + EXPECT_NE(addr1, addr2); + EXPECT_NE(addr2, addr3); + EXPECT_NE(addr3, addr1); + cout << "Offered address to client1=" << addr1 << endl; + cout << "Offered address to client2=" << addr2 << endl; + cout << "Offered address to client3=" << addr3 << endl; } // Checks whether echoing back client-id is controllable @@ -1003,7 +1003,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) { // Check if we get response at all checkResponse(ack, DHCPACK, 1234); - EXPECT_EQ(addr.toText(), ack->getYiaddr().toText()); + EXPECT_EQ(addr, ack->getYiaddr()); // Check that address was returned from proper range, that its lease // lifetime is correct, that T1 and T2 are returned properly @@ -1031,6 +1031,56 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) { EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr)); } +// This test verifies that the logic which matches server identifier in the +// received message with server identifiers used by a server works correctly: +// - a message with no server identifier is accepted, +// - a message with a server identifier which matches one of the server +// identifiers used by a server is accepted, +// - a message with a server identifier which doesn't match any server +// identifier used by a server, is not accepted. +TEST_F(Dhcpv4SrvFakeIfaceTest, acceptServerId) { + NakedDhcpv4Srv srv(0); + + Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234)); + // If no server identifier option is present, the message is always + // accepted. + EXPECT_TRUE(srv.acceptServerId(pkt)); + + // Create definition of the server identifier option. + OptionDefinition def("server-identifier", DHO_DHCP_SERVER_IDENTIFIER, + "ipv4-address", false); + + // Add a server identifier option which doesn't match server ids being + // used by the server. The accepted server ids are the IPv4 addresses + // configured on the interfaces. The 10.1.2.3 is not configured on + // any interfaces. + OptionCustomPtr other_serverid(new OptionCustom(def, Option::V6)); + other_serverid->writeAddress(IOAddress("10.1.2.3")); + pkt->addOption(other_serverid); + EXPECT_FALSE(srv.acceptServerId(pkt)); + + // Remove the server identifier. + ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER)); + + // Add a server id being an IPv4 address configured on eth0 interface. + // A DHCPv4 message holding this server identifier should be accepted. + OptionCustomPtr eth0_serverid(new OptionCustom(def, Option::V6)); + eth0_serverid->writeAddress(IOAddress("192.0.3.1")); + ASSERT_NO_THROW(pkt->addOption(eth0_serverid)); + EXPECT_TRUE(srv.acceptServerId(pkt)); + + // Remove the server identifier. + ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER)); + + // Add a server id being an IPv4 address configured on eth1 interface. + // A DHCPv4 message holding this server identifier should be accepted. + OptionCustomPtr eth1_serverid(new OptionCustom(def, Option::V6)); + eth1_serverid->writeAddress(IOAddress("10.0.0.1")); + ASSERT_NO_THROW(pkt->addOption(eth1_serverid)); + EXPECT_TRUE(srv.acceptServerId(pkt)); + +} + // @todo: Implement tests for rejecting renewals // This test verifies if the sanityCheck() really checks options presence. @@ -3123,8 +3173,32 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsisDefinitions) { ASSERT_EQ(0, rcode_); } +// Checks if client packets are classified properly +TEST_F(Dhcpv4SrvTest, clientClassification) { + + NakedDhcpv4Srv srv(0); + + // Let's create a relayed DISCOVER. This particular relayed DISCOVER has + // vendor-class set to docsis3.0 + Pkt4Ptr dis1; + ASSERT_NO_THROW(dis1 = captureRelayedDiscover()); + ASSERT_NO_THROW(dis1->unpack()); + + srv.classifyPacket(dis1); + + EXPECT_TRUE(dis1->inClass("docsis3.0")); + EXPECT_FALSE(dis1->inClass("eRouter1.0")); + + // Let's create a relayed DISCOVER. This particular relayed DISCOVER has + // vendor-class set to eRouter1.0 + Pkt4Ptr dis2; + ASSERT_NO_THROW(dis2 = captureRelayedDiscover2()); + ASSERT_NO_THROW(dis2->unpack()); + + srv.classifyPacket(dis2); + + EXPECT_TRUE(dis2->inClass("eRouter1.0")); + EXPECT_FALSE(dis2->inClass("docsis3.0")); } - /*I}; // end of isc::dhcp::test namespace -}; // end of isc::dhcp namespace -}; // end of isc namespace */ +}; // end of anonymous namespace diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc index 8f96c1e23c..e983e7b939 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc @@ -126,7 +126,7 @@ void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) { EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER)); // Check that something is offered - EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0"); + EXPECT_NE("0.0.0.0", a->getYiaddr().toText()); } ::testing::AssertionResult @@ -299,14 +299,14 @@ Lease4Ptr Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp, Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr); if (!lease) { - cout << "Lease for " << expected_addr.toText() + cout << "Lease for " << expected_addr << " not found in the database backend."; return (Lease4Ptr()); } - EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText()); + EXPECT_EQ(rsp->getYiaddr(), expected_addr); - EXPECT_EQ(expected_addr.toText(), lease->addr_.toText()); + EXPECT_EQ(expected_addr, lease->addr_); if (client_id) { EXPECT_TRUE(*lease->client_id_ == *id); } diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h index ebb6300a5d..8a2211789e 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.h +++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h @@ -222,7 +222,8 @@ public: /// @brief returns captured DISCOVER that went through a relay /// - /// See method code for a detailed explanation. + /// See method code for a detailed explanation. This is a discover from + /// docsis3.0 device (Cable Modem) /// /// @return relayed DISCOVER Pkt4Ptr captureRelayedDiscover(); @@ -253,6 +254,13 @@ public: createPacketFromBuffer(const Pkt4Ptr& src_pkt, Pkt4Ptr& dst_pkt); + /// @brief returns captured DISCOVER that went through a relay + /// + /// See method code for a detailed explanation. This is a discover from + /// eRouter1.0 device (CPE device integrated with cable modem) + /// + /// @return relayed DISCOVER + Pkt4Ptr captureRelayedDiscover2(); /// @brief generates a DHCPv4 packet based on provided hex string /// @@ -438,10 +446,12 @@ public: using Dhcpv4Srv::processClientName; using Dhcpv4Srv::computeDhcid; using Dhcpv4Srv::createNameChangeRequests; + using Dhcpv4Srv::acceptServerId; using Dhcpv4Srv::sanityCheck; using Dhcpv4Srv::srvidToString; using Dhcpv4Srv::unpackOptions; using Dhcpv4Srv::name_change_reqs_; + using Dhcpv4Srv::classifyPacket; }; }; // end of isc::dhcp::test namespace diff --git a/src/bin/dhcp4/tests/fqdn_unittest.cc b/src/bin/dhcp4/tests/fqdn_unittest.cc index d3bf9ae2c1..e17a7e3ac6 100644 --- a/src/bin/dhcp4/tests/fqdn_unittest.cc +++ b/src/bin/dhcp4/tests/fqdn_unittest.cc @@ -18,6 +18,7 @@ #include <dhcp/option_int_array.h> #include <dhcp4/tests/dhcp4_test_utils.h> #include <dhcp_ddns/ncr_msg.h> +#include <dhcpsrv/cfgmgr.h> #include <gtest/gtest.h> #include <boost/scoped_ptr.hpp> @@ -32,13 +33,53 @@ namespace { class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest { public: + + // 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() : Dhcpv4SrvFakeIfaceTest() { srv_ = new NakedDhcpv4Srv(0); + // Config DDNS to be enabled, all controls off + enableD2(); } + virtual ~NameDhcpv4SrvTest() { delete srv_; } + /// @brief Sets the server's DDNS configuration to ddns updates disabled. + void disableD2() { + // Default constructor creates a config with DHCP-DDNS updates + // disabled. + D2ClientConfigPtr cfg(new D2ClientConfig()); + CfgMgr::instance().setD2ClientConfig(cfg); + } + + /// @brief Enables DHCP-DDNS updates with the given options enabled. + /// + /// Replaces the current D2ClientConfiguration with a configuration + /// which as updates enabled and the control options set based upon + /// the bit mask of options. + /// + /// @param mask Bit mask of configuration options that should be enabled. + void enableD2(const uint16_t mask = 0) { + D2ClientConfigPtr cfg; + + ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true, + isc::asiolink::IOAddress("192.0.2.1"), 477, + dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON, + (mask & ALWAYS_INCLUDE_FQDN), + (mask & OVERRIDE_NO_UPDATE), + (mask & OVERRIDE_CLIENT_UPDATE), + (mask & REPLACE_CLIENT_NAME), + "myhost", "example.com"))); + + CfgMgr::instance().setD2ClientConfig(cfg); + } + // Create a lease to be used by various tests. Lease4Ptr createLease(const isc::asiolink::IOAddress& addr, const std::string& hostname, @@ -78,15 +119,18 @@ public: return (opt_hostname); } - // Generates partial hostname from the address. The format of the - // generated address is: host-A-B-C-D, where A.B.C.D is an IP - // address. + /// @brief Convenience method for generating an FQDN from an IP address. + /// + /// This is just a wrapper method around the D2ClientMgr's method for + /// generating domain names from the configured prefix, suffix, and a + /// given IP address. This is useful for verifying that fully generated + /// names are correct. + /// + /// @param addr IP address used in the lease. + /// + /// @return An std::string contained the generated FQDN. std::string generatedNameFromAddress(const IOAddress& addr) { - std::string gen_name = addr.toText(); - std::replace(gen_name.begin(), gen_name.end(), '.', '-'); - std::ostringstream hostname; - hostname << "host-" << gen_name; - return (hostname.str()); + return(CfgMgr::instance().getD2ClientMgr().generateFqdn(addr)); } // Get the Client FQDN Option from the given message. @@ -182,6 +226,21 @@ public: Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer); ASSERT_TRUE(fqdn); + checkFqdnFlags(answer, exp_flags); + + EXPECT_EQ(exp_domain_name, fqdn->getDomainName()); + EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType()); + + } + + /// @brief Checks the packet's FQDN option flags against a given mask + /// + /// @param pkt IPv4 packet whose FQDN flags should be checked. + /// @param exp_flags Bit mask of flags that are expected to be true. + void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) { + Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt); + ASSERT_TRUE(fqdn); + const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0; const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0; const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0; @@ -191,12 +250,9 @@ public: EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S)); EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O)); EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E)); - - EXPECT_EQ(exp_domain_name, fqdn->getDomainName()); - EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType()); - } + // Processes the Hostname option in the client's message and returns // the hostname option which would be sent to the client. It will // throw NULL pointer if the hostname option is not to be included @@ -221,8 +277,8 @@ public: } - // Test that the client message holding an FQDN is processed and the - // NameChangeRequests are generated. + // Test that the client message holding an FQDN is processed and + // that the response packet is as expected. void testProcessMessageWithFqdn(const uint8_t msg_type, const std::string& hostname) { Pkt4Ptr req = generatePktWithFqdn(msg_type, Option4ClientFqdn::FLAG_S | @@ -287,6 +343,50 @@ public: srv_->name_change_reqs_.pop(); } + + /// @brief Tests processing a request with the given client flags + /// + /// This method creates a request with its FQDN flags set to the given + /// value and submits it to the server for processing. It then checks + /// the following: + /// 1. Did the server generate an ACK with the correct FQDN flags + /// 2. If the server should have generated an NCR, did it? and If + /// so was it correct? + /// + /// @param client_flags Mask of client FQDN flags which are true + /// @param response_flags Mask of expected FQDN flags in the response + void flagVsConfigScenario(const uint8_t client_flags, + const uint8_t response_flags) { + Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags, + "myhost.example.com.", + Option4ClientFqdn::FULL, true); + + // Process the request. + Pkt4Ptr reply; + ASSERT_NO_THROW(reply = srv_->processRequest(req)); + + // Verify the response and flags. + checkResponse(reply, DHCPACK, 1234); + checkFqdnFlags(reply, response_flags); + + // There should be an NCR only if response S flag is 1. + /// @todo This logic will need to change if forward and reverse + /// updates are ever controlled independently. + if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) { + ASSERT_EQ(0, srv_->name_change_reqs_.size()); + } else { + // Verify that there is one NameChangeRequest generated as expected. + ASSERT_EQ(1, srv_->name_change_reqs_.size()); + verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, + reply->getYiaddr().toText(), + "myhost.example.com.", + "", // empty DHCID means don't check it + time(NULL) + subnet_->getValid(), + subnet_->getValid(), true); + } + } + + NakedDhcpv4Srv* srv_; }; @@ -348,21 +448,101 @@ TEST_F(NameDhcpv4SrvTest, dhcidComputeFromHWAddr) { EXPECT_EQ(dhcid_ref, dhcid.toStr()); } +// Tests the following scenario: +// - Updates are enabled +// - All overrides are off +// - Client requests forward update (N = 0, S = 1) +// +// Server should perform the update: +// - Reponse flags should N = 0, S = 1, O = 0 +// - Should queue an NCR +TEST_F(NameDhcpv4SrvTest, updatesEnabled) { + flagVsConfigScenario((Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_S), + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_S)); +} -// Test that server confirms to perform the forward and reverse DNS update, -// when client asks for it. -TEST_F(NameDhcpv4SrvTest, serverUpdateForwardFqdn) { - Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST, - Option4ClientFqdn::FLAG_E | - Option4ClientFqdn::FLAG_S, - "myhost.example.com.", - Option4ClientFqdn::FULL, - true); +// Tests the following scenario +// - Updates are disabled +// - Client requests forward update (N = 0, S = 1) +// +// Server should NOT perform updates: +// - Response flags should N = 1, S = 0, O = 1 +// - Should not queue any NCRs +TEST_F(NameDhcpv4SrvTest, updatesDisabled) { + disableD2(); + flagVsConfigScenario((Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_S), + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_N | + Option4ClientFqdn::FLAG_O)); +} - testProcessFqdn(query, - Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S, - "myhost.example.com."); +// Tests the following scenario: +// - Updates are enabled +// - All overrides are off. +// - Client requests no updates (N = 1, S = 0) +// +// Server should NOT perform updates: +// - Response flags should N = 1, S = 0, O = 0 +// - Should not queue any NCRs +TEST_F(NameDhcpv4SrvTest, respectNoUpdate) { + flagVsConfigScenario((Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_N), + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_N)); +} +// Tests the following scenario: +// - Updates are enabled +// - override-no-update is on +// - Client requests no updates (N = 1, S = 0) +// +// Server should override "no update" request and perform updates: +// - Response flags should be N = 0, S = 1, O = 1 +// - Should queue an NCR +TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) { + enableD2(OVERRIDE_NO_UPDATE); + flagVsConfigScenario((Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_N), + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_O)); +} + +// Tests the following scenario: +// - Updates are enabled +// - All overrides are off. +// - Client requests delegation (N = 0, S = 0) +// +// Server should respect client's delegation request and NOT do updates: + +// - Response flags should be N = 1, S = 0, O = 0 +// - Should not queue any NCRs +TEST_F(NameDhcpv4SrvTest, respectClientDelegation) { + + flagVsConfigScenario(Option4ClientFqdn::FLAG_E, + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_N)); +} + +// Tests the following scenario: +// - Updates are enabled +// - override-client-update is on. +// - Client requests delegation (N = 0, S = 0) +// +// Server should override client's delegation request and do updates: +// - Response flags should be N = 0, S = 1, O = 1 +// - Should queue an NCR +TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) { + // Turn on override-client-update. + enableD2(OVERRIDE_CLIENT_UPDATE); + + flagVsConfigScenario(Option4ClientFqdn::FLAG_E, + (Option4ClientFqdn::FLAG_E | + Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_O)); } // Test that server processes the Hostname option sent by a client and @@ -447,34 +627,6 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) { } -// Test server's response when client requests no DNS update. -TEST_F(NameDhcpv4SrvTest, noUpdateFqdn) { - Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST, - Option4ClientFqdn::FLAG_E | - Option4ClientFqdn::FLAG_N, - "myhost.example.com.", - Option4ClientFqdn::FULL, - true); - testProcessFqdn(query, Option4ClientFqdn::FLAG_E | - Option4ClientFqdn::FLAG_N, - "myhost.example.com."); -} - -// Test that server does not accept delegation of the forward DNS update -// to a client. -TEST_F(NameDhcpv4SrvTest, clientUpdateNotAllowedFqdn) { - Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST, - Option4ClientFqdn::FLAG_E, - "myhost.example.com.", - Option4ClientFqdn::FULL, - true); - - testProcessFqdn(query, Option4ClientFqdn::FLAG_E | - Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_O, - "myhost.example.com."); - -} - // Test that exactly one NameChangeRequest is generated when the new lease // has been acquired (old lease is NULL). TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) { @@ -600,18 +752,42 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) { // Verify that there is one NameChangeRequest generated. ASSERT_EQ(1, srv_->name_change_reqs_.size()); + // The hostname is generated from the IP address acquired (yiaddr). - std::ostringstream hostname; - hostname << generatedNameFromAddress(reply->getYiaddr()) - << ".example.com."; + std::string hostname = generatedNameFromAddress(reply->getYiaddr()); + verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, - reply->getYiaddr().toText(), hostname.str(), + reply->getYiaddr().toText(), hostname, "", // empty DHCID forces that it is not checked time(NULL) + subnet_->getValid(), subnet_->getValid(), true); } // Test that server generates client's hostname from the IP address assigned +// to it when DHCPv4 Client FQDN option specifies an empty domain-name AND +// ddns updates are disabled. +TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) { + disableD2(); + Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + "", Option4ClientFqdn::PARTIAL, true); + Pkt4Ptr reply; + ASSERT_NO_THROW(reply = srv_->processRequest(req)); + + checkResponse(reply, DHCPACK, 1234); + + Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply); + ASSERT_TRUE(fqdn); + + // The hostname is generated from the IP address acquired (yiaddr). + std::string hostname = generatedNameFromAddress(reply->getYiaddr()); + + EXPECT_EQ(hostname, fqdn->getDomainName()); + EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType()); +} + + +// Test that server generates client's hostname from the IP address assigned // to it when Hostname option carries the top level domain-name. TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) { Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, "."); @@ -626,11 +802,12 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) { // Verify that there is one NameChangeRequest generated. ASSERT_EQ(1, srv_->name_change_reqs_.size()); + // The hostname is generated from the IP address acquired (yiaddr). - std::ostringstream hostname; - hostname << generatedNameFromAddress(reply->getYiaddr()) << ".example.com."; + std::string hostname = generatedNameFromAddress(reply->getYiaddr()); + verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, - reply->getYiaddr().toText(), hostname.str(), + reply->getYiaddr().toText(), hostname, "", // empty DHCID forces that it is not checked time(NULL), subnet_->getValid(), true); } @@ -742,21 +919,23 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) { time(NULL), subnet_->getValid(), true); } -// Test that when the Release message is sent for the previously acquired -// lease, then server genenerates a NameChangeRequest to remove the entries -// corresponding to the lease being released. +// Test that when a release message is sent for a previously acquired lease, +// DDNS updates are enabled that the server genenerates a NameChangeRequest +// to remove entries corresponding to the released lease. TEST_F(NameDhcpv4SrvTest, processRequestRelease) { + // Verify the updates are enabled. + ASSERT_TRUE(CfgMgr::instance().ddnsEnabled()); + + // Create and process a lease request so we have a lease to release. Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, "myhost.example.com.", Option4ClientFqdn::FULL, true); - Pkt4Ptr reply; ASSERT_NO_THROW(reply = srv_->processRequest(req)); - checkResponse(reply, DHCPACK, 1234); - // Verify that there is one NameChangeRequest generated. + // Verify that there is one NameChangeRequest generated for lease. ASSERT_EQ(1, srv_->name_change_reqs_.size()); verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true, reply->getYiaddr().toText(), "myhost.example.com.", @@ -764,18 +943,57 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) { "965B68B6D438D98E680BF10B09F3BCF", time(NULL), subnet_->getValid(), true); - // Create a Release message. + // Create and process the Release message. Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); rel->setCiaddr(reply->getYiaddr()); rel->setRemoteAddr(IOAddress("192.0.2.3")); rel->addOption(generateClientId()); rel->addOption(srv_->getServerID()); - ASSERT_NO_THROW(srv_->processRelease(rel)); // The lease has been removed, so there should be a NameChangeRequest to // remove corresponding DNS entries. ASSERT_EQ(1, srv_->name_change_reqs_.size()); + verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true, + reply->getYiaddr().toText(), "myhost.example.com.", + "00010132E91AA355CFBB753C0F0497A5A940436" + "965B68B6D438D98E680BF10B09F3BCF", + time(NULL), subnet_->getValid(), true); +} + +// Test that when the Release message is sent for a previously acquired lease +// and DDNS updates are disabled that server does NOT generate a +// NameChangeRequest to remove entries corresponding to the released lease. +TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) { + // Disable DDNS. + disableD2(); + ASSERT_FALSE(CfgMgr::instance().ddnsEnabled()); + + // Create and process a lease request so we have a lease to release. + Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S | + Option4ClientFqdn::FLAG_E, + "myhost.example.com.", + Option4ClientFqdn::FULL, true); + Pkt4Ptr reply; + ASSERT_NO_THROW(reply = srv_->processRequest(req)); + checkResponse(reply, DHCPACK, 1234); + + // With DDNS updates disabled, there should be not be a NameChangeRequest + // for the add. + ASSERT_EQ(0, srv_->name_change_reqs_.size()); + + // Create and process the Release message. + Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234)); + rel->setCiaddr(reply->getYiaddr()); + rel->setRemoteAddr(IOAddress("192.0.2.3")); + rel->addOption(generateClientId()); + rel->addOption(srv_->getServerID()); + ASSERT_NO_THROW(srv_->processRelease(rel)); + + // With DDNS updates disabled, there should be not be a NameChangeRequest + // for the remove. + ASSERT_EQ(0, srv_->name_change_reqs_.size()); } + } // end of anonymous namespace diff --git a/src/bin/dhcp4/tests/test_data_files_config.h.in b/src/bin/dhcp4/tests/test_data_files_config.h.in new file mode 100644 index 0000000000..0b778cf8a8 --- /dev/null +++ b/src/bin/dhcp4/tests/test_data_files_config.h.in @@ -0,0 +1,17 @@ +// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file +/// can find it reliably. +#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4" diff --git a/src/bin/dhcp4/tests/wireshark.cc b/src/bin/dhcp4/tests/wireshark.cc index 80b4737083..c07bc1bc29 100644 --- a/src/bin/dhcp4/tests/wireshark.cc +++ b/src/bin/dhcp4/tests/wireshark.cc @@ -71,7 +71,10 @@ void Dhcpv4SrvTest::captureSetDefaultFields(const Pkt4Ptr& pkt) { Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover() { -/* string exported from Wireshark: +/* This is packet 1 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) Source port: bootps (67) @@ -98,7 +101,7 @@ Bootstrap Protocol Magic cookie: DHCP Option: (53) DHCP Message Type Option: (55) Parameter Request List - Option: (60) Vendor class identifier + Option: (60) Vendor class identifier (docsis3.0) Option: (125) V-I Vendor-specific Information - suboption 1 (Option Request): requesting option 2 - suboption 5 (Modem Caps): 117 bytes @@ -129,6 +132,59 @@ Bootstrap Protocol return (packetFromCapture(hex_string)); } +Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover2() { + +/* This is packet 5 from capture + dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap + +string exported from Wireshark: + +User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67) +Bootstrap Protocol + Message type: Boot Request (1) + Hardware type: Ethernet (0x01) + Hardware address length: 6 + Hops: 1 + Transaction ID: 0x5d05478f + Seconds elapsed: 5 + Bootp flags: 0x0000 (Unicast) + Client IP address: 0.0.0.0 (0.0.0.0) + Your (client) IP address: 0.0.0.0 (0.0.0.0) + Next server IP address: 0.0.0.0 (0.0.0.0) + Relay agent IP address: 10.254.226.1 (10.254.226.1) + Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15) + Client hardware address padding: 00000000000000000000 + Server host name not given + Boot file name not given + Magic cookie: DHCP + Option: (53) DHCP Message Type + Option: (55) Parameter Request List + Option: (43) Vendor-Specific Information + Option: (60) Vendor class identifier (eRouter1.0) + Option: (15) Domain Name + Option: (61) Client identifier + Option: (57) Maximum DHCP Message Size + Option: (82) Agent Information Option + Option: (255) End */ + + string hex_string = + "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000063825363350101370e" + "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55" + "544552040d324252323239553430303434430504312e3034060856312e33332e303307" + "07322e332e305232080630303039354209094347333030304443520a074e6574676561" + "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d" + "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420" + "000002020620e52ab8151409090000118b0401020300ff"; + + return (packetFromCapture(hex_string)); +} + }; // end of isc::dhcp::test namespace }; // end of isc::dhcp namespace }; // end of isc namespace |