3d38c645b021b4e597edd99c227b33f805854627
[asterisk/asterisk.git] / tests / test_dns_recurring.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Mark Michelson
5  *
6  * Mark Michelson <mmichelson@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*** MODULEINFO
20         <depend>TEST_FRAMEWORK</depend>
21         <support_level>core</support_level>
22  ***/
23
24 #include "asterisk.h"
25
26 #include <arpa/nameser.h>
27 #include <arpa/inet.h>
28
29 #include "asterisk/test.h"
30 #include "asterisk/module.h"
31 #include "asterisk/dns_core.h"
32 #include "asterisk/dns_resolver.h"
33 #include "asterisk/dns_recurring.h"
34 #include "asterisk/dns_internal.h"
35
36 struct recurring_data {
37         /*! TTL to place in first returned result */
38         int ttl1;
39         /*! TTL to place in second returned result */
40         int ttl2;
41         /*! Boolean indicator if query has completed */
42         int query_complete;
43         /*! Number of times recurring resolution has completed */
44         int complete_resolutions;
45         /*! Number of times resolve() method has been called */
46         int resolves;
47         /*! Indicates that the query is expected to be canceled */
48         int cancel_expected;
49         /*! Indicates that the query is ready to be canceled */
50         int cancel_ready;
51         /*! Indicates that the query has been canceled */
52         int canceled;
53         ast_mutex_t lock;
54         ast_cond_t cond;
55 };
56
57 static void recurring_data_destructor(void *obj)
58 {
59         struct recurring_data *rdata = obj;
60
61         ast_mutex_destroy(&rdata->lock);
62         ast_cond_destroy(&rdata->cond);
63 }
64
65 static struct recurring_data *recurring_data_alloc(void)
66 {
67         struct recurring_data *rdata;
68
69         rdata = ao2_alloc(sizeof(*rdata), recurring_data_destructor);
70         if (!rdata) {
71                 return NULL;
72         }
73
74         ast_mutex_init(&rdata->lock);
75         ast_cond_init(&rdata->cond, NULL);
76
77         return rdata;
78 }
79
80 #define DNS_ANSWER "Yes sirree"
81 #define DNS_ANSWER_SIZE strlen(DNS_ANSWER)
82
83 /*!
84  * \brief Thread that performs asynchronous resolution.
85  *
86  * This thread uses the query's user data to determine how to
87  * perform the resolution. The query may either be canceled or
88  * it may be completed with records.
89  *
90  * \param dns_query The ast_dns_query that is being performed
91  * \return NULL
92  */
93 static void *resolution_thread(void *dns_query)
94 {
95         struct ast_dns_query *query = dns_query;
96
97         static const char *ADDR1 = "127.0.0.1";
98         static const size_t ADDR1_BUFSIZE = sizeof(struct in_addr);
99         char addr1_buf[ADDR1_BUFSIZE];
100
101         static const char *ADDR2 = "192.168.0.1";
102         static const size_t ADDR2_BUFSIZE = sizeof(struct in_addr);
103         char addr2_buf[ADDR2_BUFSIZE];
104
105         struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
106         struct recurring_data *rdata = recurring->user_data;
107
108         ast_assert(rdata != NULL);
109
110         /* Canceling is an interesting dance. This thread needs to signal that it is
111          * ready to be canceled. Then it needs to wait until the query is actually canceled.
112          */
113         if (rdata->cancel_expected) {
114                 ast_mutex_lock(&rdata->lock);
115                 rdata->cancel_ready = 1;
116                 ast_cond_signal(&rdata->cond);
117
118                 while (!rdata->canceled) {
119                         ast_cond_wait(&rdata->cond, &rdata->lock);
120                 }
121                 ast_mutex_unlock(&rdata->lock);
122
123                 ast_dns_resolver_completed(query);
124                 ao2_ref(query, -1);
125
126                 return NULL;
127         }
128
129         /* When the query isn't canceled, we set the TTL of the results based on what
130          * we've been told to set it to
131          */
132         ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
133
134         inet_pton(AF_INET, ADDR1, addr1_buf);
135         ast_dns_resolver_add_record(query, ns_t_a, ns_c_in, rdata->ttl1, addr1_buf, ADDR1_BUFSIZE);
136
137         inet_pton(AF_INET, ADDR2, addr2_buf);
138         ast_dns_resolver_add_record(query, ns_t_a, ns_c_in, rdata->ttl2, addr2_buf, ADDR2_BUFSIZE);
139
140         ++rdata->complete_resolutions;
141
142         ast_dns_resolver_completed(query);
143
144         ao2_ref(query, -1);
145         return NULL;
146 }
147
148 /*!
149  * \brief Resolver's resolve() method
150  *
151  * \param query The query that is to be resolved
152  * \retval 0 Successfully created thread to perform the resolution
153  * \retval non-zero Failed to create resolution thread
154  */
155 static int recurring_resolve(struct ast_dns_query *query)
156 {
157         struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
158         struct recurring_data *rdata = recurring->user_data;
159         pthread_t resolver_thread;
160
161         ast_assert(rdata != NULL);
162         ++rdata->resolves;
163         return ast_pthread_create_detached(&resolver_thread, NULL, resolution_thread, ao2_bump(query));
164 }
165
166 /*!
167  * \brief Resolver's cancel() method
168  *
169  * \param query The query to cancel
170  * \return 0
171  */
172 static int recurring_cancel(struct ast_dns_query *query)
173 {
174         struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
175         struct recurring_data *rdata = recurring->user_data;
176
177         ast_mutex_lock(&rdata->lock);
178         rdata->canceled = 1;
179         ast_cond_signal(&rdata->cond);
180         ast_mutex_unlock(&rdata->lock);
181
182         return 0;
183 }
184
185 static struct ast_dns_resolver recurring_resolver = {
186         .name = "test_recurring",
187         .priority = 0,
188         .resolve = recurring_resolve,
189         .cancel = recurring_cancel,
190 };
191
192 /*!
193  * \brief Wait for a successful resolution to complete
194  *
195  * This is called whenever a successful DNS resolution occurs. This function
196  * serves to ensure that parameters are as we expect them to be.
197  *
198  * \param test The test being executed
199  * \param rdata DNS query user data
200  * \param expected_lapse The amount of time we expect to wait for the query to complete
201  * \param num_resolves The number of DNS resolutions that have been executed
202  * \param num_completed The number of DNS resolutions we expect to have completed successfully
203  * \param canceled Whether the query is expected to have been canceled
204  */
205 static int wait_for_resolution(struct ast_test *test, struct recurring_data *rdata,
206                 int expected_lapse, int num_resolves, int num_completed, int canceled)
207 {
208         struct timespec begin;
209         struct timespec end;
210         struct timespec timeout;
211         int secdiff;
212
213         clock_gettime(CLOCK_REALTIME, &begin);
214
215         timeout.tv_sec = begin.tv_sec + 20;
216         timeout.tv_nsec = begin.tv_nsec;
217
218         ast_mutex_lock(&rdata->lock);
219         while (!rdata->query_complete) {
220                 if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
221                         break;
222                 }
223         }
224         ast_mutex_unlock(&rdata->lock);
225
226         if (!rdata->query_complete) {
227                 ast_test_status_update(test, "Query timed out\n");
228                 return -1;
229         }
230
231         rdata->query_complete = 0;
232         clock_gettime(CLOCK_REALTIME, &end);
233
234         secdiff = end.tv_sec - begin.tv_sec;
235
236         /* Give ourselves some wiggle room */
237         if (secdiff < expected_lapse - 2 || secdiff > expected_lapse + 2) {
238                 ast_test_status_update(test, "Query did not complete in expected time\n");
239                 return -1;
240         }
241
242         if (rdata->resolves != num_resolves || rdata->complete_resolutions != num_completed) {
243                 ast_test_status_update(test, "Query has not undergone expected number of resolutions\n");
244                 return -1;
245         }
246
247         if (rdata->canceled != canceled) {
248                 ast_test_status_update(test, "Query was canceled unexpectedly\n");
249                 return -1;
250         }
251
252         ast_test_status_update(test, "Query completed in expected time frame\n");
253
254         return 0;
255 }
256
257 static void async_callback(const struct ast_dns_query *query)
258 {
259         struct recurring_data *rdata = ast_dns_query_get_data(query);
260
261         ast_assert(rdata != NULL);
262
263         ast_mutex_lock(&rdata->lock);
264         rdata->query_complete = 1;
265         ast_cond_signal(&rdata->cond);
266         ast_mutex_unlock(&rdata->lock);
267 }
268
269 AST_TEST_DEFINE(recurring_query)
270 {
271         RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
272         RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
273
274         enum ast_test_result_state res = AST_TEST_PASS;
275         int expected_lapse;
276
277         switch (cmd) {
278         case TEST_INIT:
279                 info->name = "recurring_query";
280                 info->category = "/main/dns/recurring/";
281                 info->summary = "Test nominal asynchronous recurring DNS queries\n";
282                 info->description =
283                         "This tests nominal recurring queries in the following ways:\n"
284                         "\t* An asynchronous query is sent to a mock resolver\n"
285                         "\t* The mock resolver returns two records with different TTLs\n"
286                         "\t* We ensure that the query re-occurs according to the lower of the TTLs\n"
287                         "\t* The mock resolver returns two records, this time with different TTLs\n"
288                         "\t  from the first time the query was resolved\n"
289                         "\t* We ensure that the query re-occurs according to the new lower TTL\n";
290                 return AST_TEST_NOT_RUN;
291         case TEST_EXECUTE:
292                 break;
293         }
294
295         if (ast_dns_resolver_register(&recurring_resolver)) {
296                 ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
297                 return AST_TEST_FAIL;
298         }
299
300         rdata = recurring_data_alloc();
301         if (!rdata) {
302                 ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
303                 res = AST_TEST_FAIL;
304                 goto cleanup;
305         }
306
307         expected_lapse = 0;
308         rdata->ttl1 = 5;
309         rdata->ttl2 = 20;
310
311         recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
312         if (!recurring_query) {
313                 ast_test_status_update(test, "Failed to create recurring DNS query\n");
314                 res = AST_TEST_FAIL;
315                 goto cleanup;
316         }
317
318         /* This should be near instantaneous */
319         if (wait_for_resolution(test, rdata, expected_lapse, 1, 1, 0)) {
320                 res = AST_TEST_FAIL;
321                 goto cleanup;
322         }
323
324         expected_lapse = rdata->ttl1;
325         rdata->ttl1 = 45;
326         rdata->ttl2 = 10;
327
328         /* This should take approximately 5 seconds */
329         if (wait_for_resolution(test, rdata, expected_lapse, 2, 2, 0)) {
330                 res = AST_TEST_FAIL;
331                 goto cleanup;
332         }
333
334         expected_lapse = rdata->ttl2;
335
336         /* This should take approximately 10 seconds */
337         if (wait_for_resolution(test, rdata, expected_lapse, 3, 3, 0)) {
338                 res = AST_TEST_FAIL;
339                 goto cleanup;
340         }
341
342 cleanup:
343         if (recurring_query) {
344                 /* XXX I don't like calling this here since I'm not testing
345                  * canceling recurring queries, but I'm forced into it here
346                  */
347                 ast_dns_resolve_recurring_cancel(recurring_query);
348         }
349         ast_dns_resolver_unregister(&recurring_resolver);
350         return res;
351 }
352
353 static int fail_resolve(struct ast_dns_query *query)
354 {
355         return -1;
356 }
357
358 static int stub_cancel(struct ast_dns_query *query)
359 {
360         return 0;
361 }
362
363 static void stub_callback(const struct ast_dns_query *query)
364 {
365         return;
366 }
367
368 AST_TEST_DEFINE(recurring_query_off_nominal)
369 {
370         struct ast_dns_resolver terrible_resolver = {
371                 .name = "Harold P. Warren's Filmography",
372                 .priority = 0,
373                 .resolve = fail_resolve,
374                 .cancel = stub_cancel,
375         };
376
377         struct ast_dns_query_recurring *recurring;
378
379         struct dns_resolve_data {
380                 const char *name;
381                 int rr_type;
382                 int rr_class;
383                 ast_dns_resolve_callback callback;
384         } resolves [] = {
385                 { NULL,           ns_t_a,       ns_c_in,      stub_callback },
386                 { "asterisk.org", -1,           ns_c_in,      stub_callback },
387                 { "asterisk.org", ns_t_max + 1, ns_c_in,      stub_callback },
388                 { "asterisk.org", ns_t_a,       -1,           stub_callback },
389                 { "asterisk.org", ns_t_a,       ns_c_max + 1, stub_callback },
390                 { "asterisk.org", ns_t_a,       ns_c_in,      NULL },
391         };
392         int i;
393         enum ast_test_result_state res = AST_TEST_PASS;
394
395         switch (cmd) {
396         case TEST_INIT:
397                 info->name = "recurring_query_off_nominal";
398                 info->category = "/main/dns/recurring/";
399                 info->summary = "Test off-nominal recurring DNS resolution";
400                 info->description =
401                         "This test performs several off-nominal recurring DNS resolutions:\n"
402                         "\t* Attempt resolution with NULL name\n",
403                         "\t* Attempt resolution with invalid RR type\n",
404                         "\t* Attempt resolution with invalid RR class\n",
405                         "\t* Attempt resolution with NULL callback pointer\n",
406                         "\t* Attempt resolution with resolver that returns an error\n";
407
408                 return AST_TEST_NOT_RUN;
409         case TEST_EXECUTE:
410                 break;
411         }
412
413         if (ast_dns_resolver_register(&recurring_resolver)) {
414                 ast_test_status_update(test, "Failed to register test resolver\n");
415                 return AST_TEST_FAIL;
416         }
417
418         for (i = 0; i < ARRAY_LEN(resolves); ++i) {
419                 recurring = ast_dns_resolve_recurring(resolves[i].name, resolves[i].rr_type, resolves[i].rr_class,
420                                 resolves[i].callback, NULL);
421                 if (recurring) {
422                         ast_test_status_update(test, "Successfully performed recurring resolution with invalid data\n");
423                         ast_dns_resolve_recurring_cancel(recurring);
424                         ao2_ref(recurring, -1);
425                         res = AST_TEST_FAIL;
426                 }
427         }
428
429         ast_dns_resolver_unregister(&recurring_resolver);
430
431         if (ast_dns_resolver_register(&terrible_resolver)) {
432                 ast_test_status_update(test, "Failed to register the DNS resolver\n");
433                 return AST_TEST_FAIL;
434         }
435
436         recurring = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, stub_callback, NULL);
437
438         ast_dns_resolver_unregister(&terrible_resolver);
439
440         if (recurring) {
441                 ast_test_status_update(test, "Successfully performed recurring resolution with invalid data\n");
442                 ast_dns_resolve_recurring_cancel(recurring);
443                 ao2_ref(recurring, -1);
444                 return AST_TEST_FAIL;
445         }
446
447         return res;
448 }
449
450 AST_TEST_DEFINE(recurring_query_cancel_between)
451 {
452         RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
453         RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
454
455         enum ast_test_result_state res = AST_TEST_PASS;
456         struct timespec timeout;
457
458         switch (cmd) {
459         case TEST_INIT:
460                 info->name = "recurring_query_cancel_between";
461                 info->category = "/main/dns/recurring/";
462                 info->summary = "Test canceling a recurring DNS query during the downtime between queries\n";
463                 info->description = "This test does the following:\n"
464                         "\t* Issue a recurring DNS query.\n"
465                         "\t* Once results have been returned, cancel the recurring query.\n"
466                         "\t* Wait a while to ensure that no more queries are occurring.\n";
467                 return AST_TEST_NOT_RUN;
468         case TEST_EXECUTE:
469                 break;
470         }
471
472         if (ast_dns_resolver_register(&recurring_resolver)) {
473                 ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
474                 return AST_TEST_FAIL;
475         }
476
477         rdata = recurring_data_alloc();
478         if (!rdata) {
479                 ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
480                 res = AST_TEST_FAIL;
481                 goto cleanup;
482         }
483
484         rdata->ttl1 = 5;
485         rdata->ttl2 = 20;
486
487         recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
488         if (!recurring_query) {
489                 ast_test_status_update(test, "Unable to make recurring query\n");
490                 res = AST_TEST_FAIL;
491                 goto cleanup;
492         }
493
494         if (wait_for_resolution(test, rdata, 0, 1, 1, 0)) {
495                 res = AST_TEST_FAIL;
496                 goto cleanup;
497         }
498
499         if (ast_dns_resolve_recurring_cancel(recurring_query)) {
500                 ast_test_status_update(test, "Failed to cancel recurring query\n");
501                 res = AST_TEST_FAIL;
502                 goto cleanup;
503         }
504
505         /* Query has been canceled, so let's wait to make sure that we don't get
506          * told another query has occurred.
507          */
508         clock_gettime(CLOCK_REALTIME, &timeout);
509         timeout.tv_sec += 10;
510
511         ast_mutex_lock(&rdata->lock);
512         while (!rdata->query_complete) {
513                 if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
514                         break;
515                 }
516         }
517         ast_mutex_unlock(&rdata->lock);
518
519         if (rdata->query_complete) {
520                 ast_test_status_update(test, "Recurring query occurred after cancellation\n");
521                 res = AST_TEST_FAIL;
522                 goto cleanup;
523         }
524
525 cleanup:
526         ast_dns_resolver_unregister(&recurring_resolver);
527         return res;
528 }
529
530 AST_TEST_DEFINE(recurring_query_cancel_during)
531 {
532
533         RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
534         RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
535
536         enum ast_test_result_state res = AST_TEST_PASS;
537         struct timespec timeout;
538
539         switch (cmd) {
540         case TEST_INIT:
541                 info->name = "recurring_query_cancel_during";
542                 info->category = "/main/dns/recurring/";
543                 info->summary = "Cancel a recurring DNS query while a query is actually happening\n";
544                 info->description = "This test does the following:\n"
545                         "\t* Initiate a recurring DNS query.\n"
546                         "\t* Allow the initial query to complete, and a second query to start\n"
547                         "\t* Cancel the recurring query while the second query is executing\n"
548                         "\t* Ensure that the resolver's cancel() method was called\n"
549                         "\t* Wait a while to make sure that recurring queries are no longer occurring\n";
550                 return AST_TEST_NOT_RUN;
551         case TEST_EXECUTE:
552                 break;
553         }
554
555         if (ast_dns_resolver_register(&recurring_resolver)) {
556                 ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
557                 return AST_TEST_FAIL;
558         }
559
560         rdata = recurring_data_alloc();
561         if (!rdata) {
562                 ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
563                 res = AST_TEST_FAIL;
564                 goto cleanup;
565         }
566
567         rdata->ttl1 = 5;
568         rdata->ttl2 = 20;
569
570         recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
571         if (!recurring_query) {
572                 ast_test_status_update(test, "Failed to make recurring DNS query\n");
573                 res = AST_TEST_FAIL;
574                 goto cleanup;
575         }
576
577         if (wait_for_resolution(test, rdata, 0, 1, 1, 0)) {
578                 res = AST_TEST_FAIL;
579                 goto cleanup;
580         }
581
582         /* Initial query has completed. Now let's make the next query expect a cancelation */
583         rdata->cancel_expected = 1;
584
585         /* Wait to be told that the query should be canceled  */
586         ast_mutex_lock(&rdata->lock);
587         while (!rdata->cancel_ready) {
588                 ast_cond_wait(&rdata->cond, &rdata->lock);
589         }
590         rdata->cancel_expected = 0;
591         ast_mutex_unlock(&rdata->lock);
592
593         if (ast_dns_resolve_recurring_cancel(recurring_query)) {
594                 ast_test_status_update(test, "Failed to cancel recurring DNS query\n");
595                 res = AST_TEST_FAIL;
596                 goto cleanup;
597         }
598
599         /* Query has been canceled. We'll be told that the query in flight has completed. */
600         if (wait_for_resolution(test, rdata, 0, 2, 1, 1)) {
601                 res = AST_TEST_FAIL;
602                 goto cleanup;
603         }
604
605         /* Now ensure that no more queries get completed after cancellation. */
606         clock_gettime(CLOCK_REALTIME, &timeout);
607         timeout.tv_sec += 10;
608
609         ast_mutex_lock(&rdata->lock);
610         while (!rdata->query_complete) {
611                 if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
612                         break;
613                 }
614         }
615         ast_mutex_unlock(&rdata->lock);
616
617         if (rdata->query_complete) {
618                 ast_test_status_update(test, "Recurring query occurred after cancellation\n");
619                 res = AST_TEST_FAIL;
620                 goto cleanup;
621         }
622
623 cleanup:
624         ast_dns_resolver_unregister(&recurring_resolver);
625         return res;
626 }
627
628 static int unload_module(void)
629 {
630         AST_TEST_UNREGISTER(recurring_query);
631         AST_TEST_UNREGISTER(recurring_query_off_nominal);
632         AST_TEST_UNREGISTER(recurring_query_cancel_between);
633         AST_TEST_UNREGISTER(recurring_query_cancel_during);
634
635         return 0;
636 }
637
638 static int load_module(void)
639 {
640         AST_TEST_REGISTER(recurring_query);
641         AST_TEST_REGISTER(recurring_query_off_nominal);
642         AST_TEST_REGISTER(recurring_query_cancel_between);
643         AST_TEST_REGISTER(recurring_query_cancel_during);
644
645         return AST_MODULE_LOAD_SUCCESS;
646 }
647
648 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Recurring DNS query tests");