1 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, v. 2.0. If a copy of the MPL was not distributed with this 5 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 #ifndef NC_TEST_UTILS_H 8 #define NC_TEST_UTILS_H 9 10 /// @file nc_test_utils.h prototypes for functions related transaction testing. 11 12 #include <asiolink/io_service.h> 13 #include <asiolink/interval_timer.h> 14 #include <d2srv/d2_update_message.h> 15 #include <d2srv/nc_trans.h> 16 17 #include <boost/asio/ip/udp.hpp> 18 #include <boost/asio/socket_base.hpp> 19 #include <gtest/gtest.h> 20 21 namespace isc { 22 namespace d2 { 23 24 extern const char* valid_d2_config; 25 extern const char* TEST_DNS_SERVER_IP; 26 extern size_t TEST_DNS_SERVER_PORT; 27 28 // Not extern'ed to allow use as array size 29 const int TEST_MSG_MAX = 1024; 30 31 typedef boost::shared_ptr<boost::asio::ip::udp::socket> SocketPtr; 32 33 /// @brief This class simulates a DNS server. It is capable of performing 34 /// an asynchronous read, governed by an IOService, and responding to received 35 /// requests in a given manner. 36 class FauxServer { 37 public: 38 /// @brief The types of response generated by the server. 39 enum ResponseMode { 40 USE_RCODE, // Generate a response with a given RCODE 41 CORRUPT_RESP, // Generate a corrupt response 42 INVALID_TSIG // Generate a response with the wrong TSIG key 43 }; 44 45 /// @brief Reference to IOService to use for IO processing. 46 asiolink::IOService& io_service_; 47 48 /// @brief IP address at which to listen for requests. 49 const asiolink::IOAddress& address_; 50 51 /// @brief Port on which to listen for requests. 52 size_t port_; 53 54 /// @brief Socket on which listening is done. 55 SocketPtr server_socket_; 56 57 /// @brief Stores the end point of requesting client. 58 boost::asio::ip::udp::endpoint remote_; 59 60 /// @brief Buffer in which received packets are stuffed. 61 uint8_t receive_buffer_[TEST_MSG_MAX]; 62 63 /// @brief Flag which indicates if a receive has been initiated but not yet 64 /// completed. 65 bool receive_pending_; 66 /// @brief Flag which indicates if server is in perpetual receive mode. 67 /// If true once a receive has been completed, a new one will be 68 /// automatically initiated. 69 bool perpetual_receive_; 70 71 /// @brief TSIG Key to use to verify requests and sign responses. If it is 72 /// NULL TSIG is not used. 73 D2TsigKeyPtr tsig_key_; 74 75 /// @brief Constructor 76 /// 77 /// @param io_service IOService to be used for socket IO. 78 /// @param address IP address at which the server should listen. 79 /// @param port Port number at which the server should listen. 80 FauxServer(asiolink::IOService& io_service, asiolink::IOAddress& address, 81 size_t port); 82 83 /// @brief Constructor 84 /// 85 /// @param io_service IOService to be used for socket IO. 86 /// @param server DnsServerInfo of server the DNS server. This supplies the 87 /// server's ip address and port. 88 FauxServer(asiolink::IOService& io_service, DnsServerInfo& server); 89 90 /// @brief Destructor 91 virtual ~FauxServer(); 92 93 /// @brief Initiates an asynchronous receive 94 /// 95 /// Starts the server listening for requests. Upon completion of the 96 /// listen, the callback method, requestHandler, is invoked. 97 /// 98 /// @param response_mode Selects how the server responds to a request 99 /// @param response_rcode The Rcode value set in the response. Not used 100 /// for all modes. 101 void receive (const ResponseMode& response_mode, 102 const dns::Rcode& response_rcode=dns::Rcode::NOERROR()); 103 104 /// @brief Socket IO Completion callback 105 /// 106 /// This method servers as the Server's UDP socket receive callback handler. 107 /// When the receive completes the handler is invoked with the parameters 108 /// listed. 109 /// 110 /// @param error result code of the receive (determined by asio layer) 111 /// @param bytes_recvd number of bytes received, if any 112 /// @param response_mode type of response the handler should produce 113 /// @param response_rcode value of Rcode in the response constructed by 114 /// handler 115 void requestHandler(const boost::system::error_code& error, 116 std::size_t bytes_recvd, 117 const ResponseMode& response_mode, 118 const dns::Rcode& response_rcode); 119 120 /// @brief Returns true if a receive has been started but not completed. isReceivePending()121 bool isReceivePending() { 122 return receive_pending_; 123 } 124 125 /// @brief Sets the TSIG key to the given value. 126 /// 127 /// @param tsig_key Pointer to the TSIG key to use. If the pointer is 128 /// empty, TSIG will not be used. setTSIGKey(const D2TsigKeyPtr & tsig_key)129 void setTSIGKey(const D2TsigKeyPtr& tsig_key) { 130 tsig_key_ = tsig_key; 131 } 132 }; 133 134 /// @brief Provides a means to process IOService IO for a finite amount of time. 135 /// 136 /// This class instantiates an IOService provides a single method, runTimedIO 137 /// which will run the IOService for no more than a finite amount of time, 138 /// at least one event is executed or the IOService is stopped. 139 /// It provides an virtual handler for timer expiration event. It is 140 /// intended to be used as a base class for test fixtures that need to process 141 /// IO by providing them a consistent way to do so while retaining a safety 142 /// valve so tests do not hang. 143 class TimedIO { 144 public: 145 asiolink::IOServicePtr io_service_; 146 asiolink::IntervalTimer timer_; 147 int run_time_; 148 149 /// @brief Constructor 150 TimedIO(); 151 152 /// @brief Destructor 153 virtual ~TimedIO(); 154 155 /// @brief IO Timer expiration handler 156 /// 157 /// Stops the IOService and fails the current test. 158 virtual void timesUp(); 159 160 /// @brief Processes IO till time expires or at least one handler executes. 161 /// 162 /// This method first polls IOService to run any ready handlers. If no 163 /// handlers are ready, it starts the internal time to run for the given 164 /// amount of time and invokes service's run_one method. This method 165 /// blocks until at least one handler executes or the IO Service is stopped. 166 /// Upon completion of this method the timer is cancelled. Should the 167 /// timer expires prior to run_one returning, the timesUp handler will be 168 /// invoked which stops the IO service and fails the test. 169 /// 170 /// Note that this method closely mimics the runIO method in D2Process. 171 /// 172 /// @param run_time maximum length of time to run in milliseconds before 173 /// timing out. 174 /// 175 /// @return Returns the number of handlers executed or zero. A return of 176 /// zero indicates that the IOService has been stopped. 177 int runTimedIO(int run_time); 178 179 }; 180 181 /// @brief Base class Test fixture for testing transactions. 182 class TransactionTest : public TimedIO, public ::testing::Test { 183 public: 184 dhcp_ddns::NameChangeRequestPtr ncr_; 185 DdnsDomainPtr forward_domain_; 186 DdnsDomainPtr reverse_domain_; 187 D2CfgMgrPtr cfg_mgr_; 188 189 /// @brief constants used to specify change directions for a transaction. 190 static const unsigned int FORWARD_CHG; // Only forward change. 191 static const unsigned int REVERSE_CHG; // Only reverse change. 192 static const unsigned int FWD_AND_REV_CHG; // Both forward and reverse. 193 194 TransactionTest(); 195 virtual ~TransactionTest(); 196 197 /// @brief Creates a transaction which requests an IPv4 DNS update. 198 /// 199 /// The transaction is constructed around a predefined (i.e. "canned") 200 /// IPv4 NameChangeRequest. The request has both forward and reverse DNS 201 /// changes requested. Based upon the change mask, the transaction 202 /// will have either the forward, reverse, or both domains populated. 203 /// 204 /// @param change_type selects the type of change requested, CHG_ADD or 205 /// CHG_REMOVE. 206 /// @param change_mask determines which change directions are requested 207 /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. 208 /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both 209 /// domains in the transaction. This will cause the transaction to 210 /// use TSIG. If the pointer is empty, TSIG will not be used. 211 void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type, 212 int change_mask, 213 const TSIGKeyInfoPtr& tsig_key_info = 214 TSIGKeyInfoPtr()); 215 216 /// @brief Creates a transaction which requests an IPv4 DNS update. 217 /// 218 /// Convenience wrapper around the above method which accepts a string 219 /// key_name from which the TSIGKeyInfo is constructed. Note the string 220 /// may not be blank. 221 /// 222 /// @param change_type selects the type of change requested, CHG_ADD or 223 /// CHG_REMOVE. 224 /// @param change_mask determines which change directions are requested 225 /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. 226 /// @param key_name value to use to create TSIG key. The value may not 227 /// be blank. 228 void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type, 229 int change_mask, const std::string& key_name); 230 231 /// @brief Creates a transaction which requests an IPv6 DNS update. 232 /// 233 /// The transaction is constructed around a predefined (i.e. "canned") 234 /// IPv6 NameChangeRequest. The request has both forward and reverse DNS 235 /// changes requested. Based upon the change mask, the transaction 236 /// will have either the forward, reverse, or both domains populated. 237 /// 238 /// @param change_type selects the type of change requested, CHG_ADD or 239 /// CHG_REMOVE. 240 /// @param change_mask determines which change directions are requested 241 /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. 242 /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both 243 /// domains in the transaction. This will cause the transaction to 244 /// use TSIG. If the pointer is empty, TSIG will not be used. 245 void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type, 246 int change_mask, 247 const TSIGKeyInfoPtr& tsig_key_info = 248 TSIGKeyInfoPtr()); 249 250 /// @brief Creates a transaction which requests an IPv6 DNS update. 251 /// 252 /// Convenience wrapper around the above method which accepts a string 253 /// key_name from which the TSIGKeyInfo is constructed. Note the string 254 /// may not be blank. 255 /// 256 /// @param change_type selects the type of change requested, CHG_ADD or 257 /// CHG_REMOVE. 258 /// @param change_mask determines which change directions are requested 259 /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG. 260 /// @param key_name value to use to create TSIG key, if blank TSIG will not 261 /// be used. 262 void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type, 263 int change_mask, const std::string& key_name); 264 }; 265 266 267 /// @brief Tests the number of RRs in a request section against a given count. 268 /// 269 /// This function actually returns the number of RRsetPtrs in a section. Since 270 /// D2 only uses RRsets with a single RData in each (i.e. 1 RR), it is used 271 /// as the number of RRs. The dns::Message::getRRCount() cannot be used for 272 /// this as it returns the number of RDatas in an RRSet which does NOT equate 273 /// to the number of RRs. RRs with no RData, those with class or type of ANY, 274 /// are not counted. 275 /// 276 /// @param request DNS update request to test 277 /// @param section enum value of the section to count 278 /// @param count the expected number of RRs 279 extern void checkRRCount(const D2UpdateMessagePtr& request, 280 D2UpdateMessage::UpdateMsgSection section, int count); 281 282 /// @brief Tests the zone content of a given request. 283 /// 284 /// @param request DNS update request to validate 285 /// @param exp_zone_name expected value of the zone name in the zone section 286 extern void checkZone(const D2UpdateMessagePtr& request, 287 const std::string& exp_zone_name); 288 289 /// @brief Tests the contents of an RRset 290 /// 291 /// @param rrset Pointer the RRset to test 292 /// @param exp_name expected value of RRset name (FQDN or reverse ip) 293 /// @param exp_class expected RRClass value of RRset 294 /// @param exp_typ expected RRType value of RRset 295 /// @param exp_ttl expected TTL value of RRset 296 /// @param ncr NameChangeRequest on which the RRset is based 297 /// @param has_rdata if true, RRset's rdata will be checked based on it's 298 /// RRType. Set this to false if the RRset's type supports Rdata but it does 299 /// not contain it. For instance, prerequisites of type NONE have no Rdata 300 /// where updates of type NONE may. 301 extern void checkRR(dns::RRsetPtr rrset, const std::string& exp_name, 302 const dns::RRClass& exp_class, const dns::RRType& exp_type, 303 unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr, 304 bool has_rdata=true); 305 306 /// @brief Fetches an RR(set) from a given section of a request 307 /// 308 /// @param request DNS update request from which the RR should come 309 /// @param section enum value of the section from which the RR should come 310 /// @param index zero-based index of the RR of interest. 311 /// 312 /// @return Pointer to the RR of interest, empty pointer if the index is out 313 /// of range. 314 extern dns::RRsetPtr getRRFromSection(const D2UpdateMessagePtr& request, 315 D2UpdateMessage::UpdateMsgSection section, 316 int index); 317 /// @brief Creates a NameChangeRequest from a JSON string 318 /// 319 /// @param ncr_str JSON string form of a NameChangeRequest. Example: 320 /// @code 321 /// const char* msg_str = 322 /// "{" 323 /// " \"change-type\" : 0 , " 324 /// " \"forward-change\" : true , " 325 /// " \"reverse-change\" : true , " 326 /// " \"fqdn\" : \"my.example.com.\" , " 327 /// " \"ip-address\" : \"192.168.2.1\" , " 328 /// " \"dhcid\" : \"0102030405060708\" , " 329 /// " \"lease-expires-on\" : \"20130121132405\" , " 330 /// " \"lease-length\" : 1300 " 331 /// "}"; 332 /// 333 /// @endcode 334 335 /// @brief Verifies a forward mapping addition DNS update request 336 /// 337 /// Tests that the DNS Update request for a given transaction, is correct for 338 /// adding a forward DNS mapping. 339 /// 340 /// @param tran Transaction containing the request to be verified. 341 extern void checkAddFwdAddressRequest(NameChangeTransaction& tran); 342 343 /// @brief Verifies a forward mapping replacement DNS update request 344 /// 345 /// Tests that the DNS Update request for a given transaction, is correct for 346 /// replacing a forward DNS mapping. 347 /// 348 /// @param tran Transaction containing the request to be verified. 349 extern void checkReplaceFwdAddressRequest(NameChangeTransaction& tran); 350 351 /// @brief Verifies a reverse mapping replacement DNS update request 352 /// 353 /// Tests that the DNS Update request for a given transaction, is correct for 354 /// replacing a reverse DNS mapping. 355 /// 356 /// @param tran Transaction containing the request to be verified. 357 extern void checkReplaceRevPtrsRequest(NameChangeTransaction& tran); 358 359 /// @brief Verifies a forward address removal DNS update request 360 /// 361 /// Tests that the DNS Update request for a given transaction, is correct for 362 /// removing the forward address DNS entry. 363 /// 364 /// @param tran Transaction containing the request to be verified. 365 extern void checkRemoveFwdAddressRequest(NameChangeTransaction& tran); 366 367 /// @brief Verifies a forward RR removal DNS update request 368 /// 369 /// Tests that the DNS Update request for a given transaction, is correct for 370 /// removing forward RR DNS entries. 371 /// 372 /// @param tran Transaction containing the request to be verified. 373 extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran); 374 375 /// @brief Verifies a reverse mapping removal DNS update request 376 /// 377 /// Tests that the DNS Update request for a given transaction, is correct for 378 /// removing a reverse DNS mapping. 379 /// 380 /// @param tran Transaction containing the request to be verified. 381 extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran); 382 383 /// @brief Verifies a simple forward mapping replacement DNS update request 384 /// 385 /// Tests that the DNS Update request for a given transaction, is correct for 386 /// replacing a forward DNS mapping when not using conflict resolution. 387 /// 388 /// @param tran Transaction containing the request to be verified. 389 extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran); 390 391 /// @brief Verifies a simple forward RR removal DNS update request 392 /// 393 /// Tests that the DNS Update request for a given transaction, is correct for 394 /// removing forward RR DNS entries when not using conflict resolution. 395 /// 396 /// @param tran Transaction containing the request to be verified. 397 extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran); 398 399 /// @brief Verifies a simple reverse RR removal DNS update request 400 /// 401 /// Tests that the DNS Update request for a given transaction, is correct for 402 /// removing reverse RR DNS entries when not using conflict resolution. 403 /// 404 /// @param tran Transaction containing the request to be verified. 405 extern void checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran); 406 407 /// @brief Creates a NameChangeRequest from JSON string. 408 /// 409 /// @param ncr_str string of JSON text from which to make the request. 410 /// 411 /// @return Pointer to newly created request. 412 /// 413 /// @throw Underlying methods may throw. 414 extern 415 dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str); 416 417 /// @brief Creates a DdnsDomain with the one server. 418 /// 419 /// @param zone_name zone name of the domain 420 /// @param key_name TSIG key name of the TSIG key for this domain. It will 421 /// create a TSIGKeyInfo based on the key_name and assign it to the domain. 422 /// 423 /// @throw Underlying methods may throw. 424 extern DdnsDomainPtr makeDomain(const std::string& zone_name, 425 const std::string& key_name); 426 427 /// @brief Creates a DdnsDomain with the one server. 428 /// 429 /// @param zone_name zone name of the domain 430 /// @param tsig_key_info pointer to the TSIGInfog key for this domain. 431 /// Defaults to an empty pointer, meaning this domain has no key. 432 /// 433 /// @throw Underlying methods may throw. 434 extern DdnsDomainPtr makeDomain(const std::string& zone_name, 435 const TSIGKeyInfoPtr& 436 tsig_key_info = TSIGKeyInfoPtr()); 437 438 /// @brief Creates a TSIGKeyInfo 439 /// 440 /// @param key_name name of the key 441 /// @param secret key secret data as a base64 encoded string. If blank, 442 /// then the secret value will be generated from key_name. 443 /// @param algorithm algorithm to use. Defaults to MD5. 444 /// @return a TSIGKeyInfoPtr for the newly created key. If key_name is blank 445 /// the pointer will be empty. 446 /// @throw Underlying methods may throw. 447 extern 448 TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name, 449 const std::string& secret = "", 450 const std::string& algorithm 451 = TSIGKeyInfo::HMAC_MD5_STR); 452 453 /// @brief Creates a DnsServerInfo and adds it to the given DdnsDomain. 454 /// 455 /// The server is created and added to the domain, without duplicate entry 456 /// checking. 457 /// 458 /// @param domain DdnsDomain to which to add the server 459 /// @param name new server's host name of the server 460 /// @param ip new server's ip address 461 /// @param port new server's port 462 /// @param tsig_key_info pointer to the TSIGInfog key for this server. 463 /// Defaults to an empty pointer, meaning this server has no key. 464 /// 465 /// @throw Underlying methods may throw. 466 extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name, 467 const std::string& ip = TEST_DNS_SERVER_IP, 468 const size_t port = TEST_DNS_SERVER_PORT, 469 const TSIGKeyInfoPtr& 470 tsig_key_info = TSIGKeyInfoPtr()); 471 472 /// @brief Creates a hex text dump of the given data buffer. 473 /// 474 /// This method is not used for testing but is handy for debugging. It creates 475 /// a pleasantly formatted string of 2-digits per byte separated by spaces with 476 /// 16 bytes per line. 477 /// 478 /// @param data pointer to the data to dump 479 /// @param len size (in bytes) of data 480 extern std::string toHexText(const uint8_t* data, size_t len); 481 482 /// @brief Verifies the current state and next event in a transaction 483 /// @param trans NameChangeTransaction to check 484 /// @param exp_state expected current state of the transaction 485 /// @param exp_event expected next event of the transaction 486 /// @param file source file name 487 /// @param line source line number 488 extern void checkContext(NameChangeTransactionPtr trans, const int exp_state, 489 const int exp_evt, const std::string& file, int line); 490 491 /// @brief Macro for calling checkContext() that supplies invocation location 492 #define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__) 493 494 } // namespace isc::d2 495 } // namespace isc 496 497 #endif 498