diff options
Diffstat (limited to 'src/lib/d2srv/testutils/nc_test_utils.h')
-rw-r--r-- | src/lib/d2srv/testutils/nc_test_utils.h | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/src/lib/d2srv/testutils/nc_test_utils.h b/src/lib/d2srv/testutils/nc_test_utils.h new file mode 100644 index 0000000..73b6287 --- /dev/null +++ b/src/lib/d2srv/testutils/nc_test_utils.h @@ -0,0 +1,497 @@ +// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef NC_TEST_UTILS_H +#define NC_TEST_UTILS_H + +/// @file nc_test_utils.h prototypes for functions related transaction testing. + +#include <asiolink/io_service.h> +#include <asiolink/interval_timer.h> +#include <d2srv/d2_update_message.h> +#include <d2srv/nc_trans.h> + +#include <boost/asio/ip/udp.hpp> +#include <boost/asio/socket_base.hpp> +#include <gtest/gtest.h> + +namespace isc { +namespace d2 { + +extern const char* valid_d2_config; +extern const char* TEST_DNS_SERVER_IP; +extern size_t TEST_DNS_SERVER_PORT; + +// Not extern'ed to allow use as array size +const int TEST_MSG_MAX = 1024; + +typedef boost::shared_ptr<boost::asio::ip::udp::socket> SocketPtr; + +/// @brief This class simulates a DNS server. It is capable of performing +/// an asynchronous read, governed by an IOService, and responding to received +/// requests in a given manner. +class FauxServer { +public: + /// @brief The types of response generated by the server. + enum ResponseMode { + USE_RCODE, // Generate a response with a given RCODE + CORRUPT_RESP, // Generate a corrupt response + INVALID_TSIG // Generate a response with the wrong TSIG key + }; + + /// @brief Reference to IOService to use for IO processing. + asiolink::IOService& io_service_; + + /// @brief IP address at which to listen for requests. + const asiolink::IOAddress& address_; + + /// @brief Port on which to listen for requests. + size_t port_; + + /// @brief Socket on which listening is done. + SocketPtr server_socket_; + + /// @brief Stores the end point of requesting client. + boost::asio::ip::udp::endpoint remote_; + + /// @brief Buffer in which received packets are stuffed. + uint8_t receive_buffer_[TEST_MSG_MAX]; + + /// @brief Flag which indicates if a receive has been initiated but not yet + /// completed. + bool receive_pending_; + /// @brief Flag which indicates if server is in perpetual receive mode. + /// If true once a receive has been completed, a new one will be + /// automatically initiated. + bool perpetual_receive_; + + /// @brief TSIG Key to use to verify requests and sign responses. If it is + /// NULL TSIG is not used. + D2TsigKeyPtr tsig_key_; + + /// @brief Constructor + /// + /// @param io_service IOService to be used for socket IO. + /// @param address IP address at which the server should listen. + /// @param port Port number at which the server should listen. + FauxServer(asiolink::IOService& io_service, asiolink::IOAddress& address, + size_t port); + + /// @brief Constructor + /// + /// @param io_service IOService to be used for socket IO. + /// @param server DnsServerInfo of server the DNS server. This supplies the + /// server's ip address and port. + FauxServer(asiolink::IOService& io_service, DnsServerInfo& server); + + /// @brief Destructor + virtual ~FauxServer(); + + /// @brief Initiates an asynchronous receive + /// + /// Starts the server listening for requests. Upon completion of the + /// listen, the callback method, requestHandler, is invoked. + /// + /// @param response_mode Selects how the server responds to a request + /// @param response_rcode The Rcode value set in the response. Not used + /// for all modes. + void receive (const ResponseMode& response_mode, + const dns::Rcode& response_rcode=dns::Rcode::NOERROR()); + + /// @brief Socket IO Completion callback + /// + /// This method servers as the Server's UDP socket receive callback handler. + /// When the receive completes the handler is invoked with the parameters + /// listed. + /// + /// @param error result code of the receive (determined by asio layer) + /// @param bytes_recvd number of bytes received, if any + /// @param response_mode type of response the handler should produce + /// @param response_rcode value of Rcode in the response constructed by + /// handler + void requestHandler(const boost::system::error_code& error, + std::size_t bytes_recvd, + const ResponseMode& response_mode, + const dns::Rcode& response_rcode); + + /// @brief Returns true if a receive has been started but not completed. + bool isReceivePending() { + return receive_pending_; + } + + /// @brief Sets the TSIG key to the given value. + /// + /// @param tsig_key Pointer to the TSIG key to use. If the pointer is + /// empty, TSIG will not be used. + void setTSIGKey(const D2TsigKeyPtr& tsig_key) { + tsig_key_ = tsig_key; + } +}; + +/// @brief Provides a means to process IOService IO for a finite amount of time. +/// +/// This class instantiates an IOService provides a single method, runTimedIO +/// which will run the IOService for no more than a finite amount of time, +/// at least one event is executed or the IOService is stopped. +/// It provides an virtual handler for timer expiration event. It is +/// intended to be used as a base class for test fixtures that need to process +/// IO by providing them a consistent way to do so while retaining a safety +/// valve so tests do not hang. +class TimedIO { +public: + asiolink::IOServicePtr io_service_; + asiolink::IntervalTimer timer_; + int run_time_; + + /// @brief Constructor + TimedIO(); + + /// @brief Destructor + virtual ~TimedIO(); + + /// @brief IO Timer expiration handler + /// + /// Stops the IOService and fails the current test. + virtual void timesUp(); + + /// @brief Processes IO till time expires or at least one handler executes. + /// + /// This method first polls IOService to run any ready handlers. If no + /// handlers are ready, it starts the internal time to run for the given + /// amount of time and invokes service's run_one method. This method + /// blocks until at least one handler executes or the IO Service is stopped. + /// Upon completion of this method the timer is cancelled. Should the + /// timer expires prior to run_one returning, the timesUp handler will be + /// invoked which stops the IO service and fails the test. + /// + /// Note that this method closely mimics the runIO method in D2Process. + /// + /// @param run_time maximum length of time to run in milliseconds before + /// timing out. + /// + /// @return Returns the number of handlers executed or zero. A return of + /// zero indicates that the IOService has been stopped. + int runTimedIO(int run_time); + +}; + +/// @brief Base class Test fixture for testing transactions. +class TransactionTest : public TimedIO, public ::testing::Test { +public: + dhcp_ddns::NameChangeRequestPtr ncr_; + DdnsDomainPtr forward_domain_; + DdnsDomainPtr reverse_domain_; + D2CfgMgrPtr cfg_mgr_; + + /// @brief constants used to specify change directions for a transaction. + static const unsigned int FORWARD_CHG; // Only forward change. + static const unsigned int REVERSE_CHG; // Only reverse change. + static const unsigned int FWD_AND_REV_CHG; // Both forward and reverse. + + TransactionTest(); + virtual ~TransactionTest(); + + /// @brief Creates a transaction which requests an IPv4 DNS update. + /// + /// The transaction is constructed around a predefined (i.e. "canned") + /// IPv4 NameChangeRequest. The request has both forward and reverse DNS + /// changes requested. Based upon the change mask, the transaction + /// will have either the forward, reverse, or both domains populated. + /// + /// @param change_type selects the type of change requested, CHG_ADD or + /// CHG_REMOVE. + /// @param change_mask determines which change directions are requested + /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. + /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both + /// domains in the transaction. This will cause the transaction to + /// use TSIG. If the pointer is empty, TSIG will not be used. + void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type, + int change_mask, + const TSIGKeyInfoPtr& tsig_key_info = + TSIGKeyInfoPtr()); + + /// @brief Creates a transaction which requests an IPv4 DNS update. + /// + /// Convenience wrapper around the above method which accepts a string + /// key_name from which the TSIGKeyInfo is constructed. Note the string + /// may not be blank. + /// + /// @param change_type selects the type of change requested, CHG_ADD or + /// CHG_REMOVE. + /// @param change_mask determines which change directions are requested + /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. + /// @param key_name value to use to create TSIG key. The value may not + /// be blank. + void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type, + int change_mask, const std::string& key_name); + + /// @brief Creates a transaction which requests an IPv6 DNS update. + /// + /// The transaction is constructed around a predefined (i.e. "canned") + /// IPv6 NameChangeRequest. The request has both forward and reverse DNS + /// changes requested. Based upon the change mask, the transaction + /// will have either the forward, reverse, or both domains populated. + /// + /// @param change_type selects the type of change requested, CHG_ADD or + /// CHG_REMOVE. + /// @param change_mask determines which change directions are requested + /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. + /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both + /// domains in the transaction. This will cause the transaction to + /// use TSIG. If the pointer is empty, TSIG will not be used. + void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type, + int change_mask, + const TSIGKeyInfoPtr& tsig_key_info = + TSIGKeyInfoPtr()); + + /// @brief Creates a transaction which requests an IPv6 DNS update. + /// + /// Convenience wrapper around the above method which accepts a string + /// key_name from which the TSIGKeyInfo is constructed. Note the string + /// may not be blank. + /// + /// @param change_type selects the type of change requested, CHG_ADD or + /// CHG_REMOVE. + /// @param change_mask determines which change directions are requested + /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. + /// @param key_name value to use to create TSIG key, if blank TSIG will not + /// be used. + void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type, + int change_mask, const std::string& key_name); +}; + + +/// @brief Tests the number of RRs in a request section against a given count. +/// +/// This function actually returns the number of RRsetPtrs in a section. Since +/// D2 only uses RRsets with a single RData in each (i.e. 1 RR), it is used +/// as the number of RRs. The dns::Message::getRRCount() cannot be used for +/// this as it returns the number of RDatas in an RRSet which does NOT equate +/// to the number of RRs. RRs with no RData, those with class or type of ANY, +/// are not counted. +/// +/// @param request DNS update request to test +/// @param section enum value of the section to count +/// @param count the expected number of RRs +extern void checkRRCount(const D2UpdateMessagePtr& request, + D2UpdateMessage::UpdateMsgSection section, int count); + +/// @brief Tests the zone content of a given request. +/// +/// @param request DNS update request to validate +/// @param exp_zone_name expected value of the zone name in the zone section +extern void checkZone(const D2UpdateMessagePtr& request, + const std::string& exp_zone_name); + +/// @brief Tests the contents of an RRset +/// +/// @param rrset Pointer the RRset to test +/// @param exp_name expected value of RRset name (FQDN or reverse ip) +/// @param exp_class expected RRClass value of RRset +/// @param exp_typ expected RRType value of RRset +/// @param exp_ttl expected TTL value of RRset +/// @param ncr NameChangeRequest on which the RRset is based +/// @param has_rdata if true, RRset's rdata will be checked based on it's +/// RRType. Set this to false if the RRset's type supports Rdata but it does +/// not contain it. For instance, prerequisites of type NONE have no Rdata +/// where updates of type NONE may. +extern void checkRR(dns::RRsetPtr rrset, const std::string& exp_name, + const dns::RRClass& exp_class, const dns::RRType& exp_type, + unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr, + bool has_rdata=true); + +/// @brief Fetches an RR(set) from a given section of a request +/// +/// @param request DNS update request from which the RR should come +/// @param section enum value of the section from which the RR should come +/// @param index zero-based index of the RR of interest. +/// +/// @return Pointer to the RR of interest, empty pointer if the index is out +/// of range. +extern dns::RRsetPtr getRRFromSection(const D2UpdateMessagePtr& request, + D2UpdateMessage::UpdateMsgSection section, + int index); +/// @brief Creates a NameChangeRequest from a JSON string +/// +/// @param ncr_str JSON string form of a NameChangeRequest. Example: +/// @code +/// const char* msg_str = +/// "{" +/// " \"change-type\" : 0 , " +/// " \"forward-change\" : true , " +/// " \"reverse-change\" : true , " +/// " \"fqdn\" : \"my.example.com.\" , " +/// " \"ip-address\" : \"192.168.2.1\" , " +/// " \"dhcid\" : \"0102030405060708\" , " +/// " \"lease-expires-on\" : \"20130121132405\" , " +/// " \"lease-length\" : 1300 " +/// "}"; +/// +/// @endcode + +/// @brief Verifies a forward mapping addition DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// adding a forward DNS mapping. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkAddFwdAddressRequest(NameChangeTransaction& tran); + +/// @brief Verifies a forward mapping replacement DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// replacing a forward DNS mapping. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkReplaceFwdAddressRequest(NameChangeTransaction& tran); + +/// @brief Verifies a reverse mapping replacement DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// replacing a reverse DNS mapping. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkReplaceRevPtrsRequest(NameChangeTransaction& tran); + +/// @brief Verifies a forward address removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing the forward address DNS entry. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkRemoveFwdAddressRequest(NameChangeTransaction& tran); + +/// @brief Verifies a forward RR removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing forward RR DNS entries. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran); + +/// @brief Verifies a reverse mapping removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing a reverse DNS mapping. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran); + +/// @brief Verifies a simple forward mapping replacement DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// replacing a forward DNS mapping when not using conflict resolution. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran); + +/// @brief Verifies a simple forward RR removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing forward RR DNS entries when not using conflict resolution. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran); + +/// @brief Verifies a simple reverse RR removal DNS update request +/// +/// Tests that the DNS Update request for a given transaction, is correct for +/// removing reverse RR DNS entries when not using conflict resolution. +/// +/// @param tran Transaction containing the request to be verified. +extern void checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran); + +/// @brief Creates a NameChangeRequest from JSON string. +/// +/// @param ncr_str string of JSON text from which to make the request. +/// +/// @return Pointer to newly created request. +/// +/// @throw Underlying methods may throw. +extern +dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str); + +/// @brief Creates a DdnsDomain with the one server. +/// +/// @param zone_name zone name of the domain +/// @param key_name TSIG key name of the TSIG key for this domain. It will +/// create a TSIGKeyInfo based on the key_name and assign it to the domain. +/// +/// @throw Underlying methods may throw. +extern DdnsDomainPtr makeDomain(const std::string& zone_name, + const std::string& key_name); + +/// @brief Creates a DdnsDomain with the one server. +/// +/// @param zone_name zone name of the domain +/// @param tsig_key_info pointer to the TSIGInfog key for this domain. +/// Defaults to an empty pointer, meaning this domain has no key. +/// +/// @throw Underlying methods may throw. +extern DdnsDomainPtr makeDomain(const std::string& zone_name, + const TSIGKeyInfoPtr& + tsig_key_info = TSIGKeyInfoPtr()); + +/// @brief Creates a TSIGKeyInfo +/// +/// @param key_name name of the key +/// @param secret key secret data as a base64 encoded string. If blank, +/// then the secret value will be generated from key_name. +/// @param algorithm algorithm to use. Defaults to MD5. +/// @return a TSIGKeyInfoPtr for the newly created key. If key_name is blank +/// the pointer will be empty. +/// @throw Underlying methods may throw. +extern +TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name, + const std::string& secret = "", + const std::string& algorithm + = TSIGKeyInfo::HMAC_MD5_STR); + +/// @brief Creates a DnsServerInfo and adds it to the given DdnsDomain. +/// +/// The server is created and added to the domain, without duplicate entry +/// checking. +/// +/// @param domain DdnsDomain to which to add the server +/// @param name new server's host name of the server +/// @param ip new server's ip address +/// @param port new server's port +/// @param tsig_key_info pointer to the TSIGInfog key for this server. +/// Defaults to an empty pointer, meaning this server has no key. +/// +/// @throw Underlying methods may throw. +extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name, + const std::string& ip = TEST_DNS_SERVER_IP, + const size_t port = TEST_DNS_SERVER_PORT, + const TSIGKeyInfoPtr& + tsig_key_info = TSIGKeyInfoPtr()); + +/// @brief Creates a hex text dump of the given data buffer. +/// +/// This method is not used for testing but is handy for debugging. It creates +/// a pleasantly formatted string of 2-digits per byte separated by spaces with +/// 16 bytes per line. +/// +/// @param data pointer to the data to dump +/// @param len size (in bytes) of data +extern std::string toHexText(const uint8_t* data, size_t len); + +/// @brief Verifies the current state and next event in a transaction +/// @param trans NameChangeTransaction to check +/// @param exp_state expected current state of the transaction +/// @param exp_event expected next event of the transaction +/// @param file source file name +/// @param line source line number +extern void checkContext(NameChangeTransactionPtr trans, const int exp_state, + const int exp_evt, const std::string& file, int line); + +/// @brief Macro for calling checkContext() that supplies invocation location +#define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__) + +} // namespace isc::d2 +} // namespace isc + +#endif |