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