summaryrefslogtreecommitdiffstats
path: root/src/lib/d2srv/testutils/nc_test_utils.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/d2srv/testutils/nc_test_utils.h')
-rw-r--r--src/lib/d2srv/testutils/nc_test_utils.h497
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