89a934c8f277e45a65b956584feb3f14da93ed95
[asterisk/asterisk.git] / res / res_resolver_unbound.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@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>unbound</depend>
21         <support_level>core</support_level>
22  ***/
23
24 #include "asterisk.h"
25
26 #include <signal.h>
27 #include <unbound.h>
28 #include <arpa/nameser.h>
29
30 #include "asterisk/module.h"
31 #include "asterisk/linkedlists.h"
32 #include "asterisk/dns_core.h"
33 #include "asterisk/dns_resolver.h"
34 #include "asterisk/config.h"
35 #include "asterisk/config_options.h"
36 #include "asterisk/test.h"
37
38 #ifdef TEST_FRAMEWORK
39 #include "asterisk/dns_srv.h"
40 #endif
41
42 /*** DOCUMENTATION
43         <configInfo name="res_resolver_unbound" language="en_US">
44                 <configFile name="resolver_unbound.conf">
45                         <configObject name="general">
46                                 <synopsis>General options for res_resolver_unbound</synopsis>
47                                 <configOption name="hosts">
48                                         <synopsis>Full path to an optional hosts file</synopsis>
49                                         <description><para>Hosts specified in a hosts file will be resolved within the resolver itself. If a value
50                                         of system is provided the system-specific file will be used.</para></description>
51                                 </configOption>
52                                 <configOption name="resolv">
53                                         <synopsis>Full path to an optional resolv.conf file</synopsis>
54                                         <description><para>The resolv.conf file specifies the nameservers to contact when resolving queries. If a
55                                         value of system is provided the system-specific file will be used. If provided alongside explicit nameservers the
56                                         nameservers contained within the resolv.conf file will be used after all others.</para></description>
57                                 </configOption>
58                                 <configOption name="nameserver">
59                                         <synopsis>Nameserver to use for queries</synopsis>
60                                         <description><para>An explicit nameserver can be specified which is used for resolving queries. If multiple
61                                         nameserver lines are specified the first will be the primary with failover occurring, in order, to the other
62                                         nameservers as backups. If provided alongside a resolv.conf file the nameservers explicitly specified will be
63                                         used before all others.</para></description>
64                                 </configOption>
65                                 <configOption name="debug">
66                                         <synopsis>Unbound debug level</synopsis>
67                                         <description><para>The debugging level for the unbound resolver. While there is no explicit range generally
68                                         the higher the number the more debug is output.</para></description>
69                                 </configOption>
70                                 <configOption name="ta_file">
71                                         <synopsis>Trust anchor file</synopsis>
72                                         <description><para>Full path to a file with DS and DNSKEY records in zone file format. This file is provided
73                                         to unbound and is used as a source for trust anchors.</para></description>
74                                 </configOption>
75                         </configObject>
76                 </configFile>
77         </configInfo>
78  ***/
79
80 /*!
81  * Unbound versions <= 1.4.20 declare string function parameters as 'char *'
82  * but versions >= 1.4.21 declare them as 'const char *'.  Since CentOS6 is still
83  * at 1.4.20, we need to cast away the 'const' if we detect the earlier version.
84  */
85 #ifdef HAVE_UNBOUND_CONST_PARAMS
86 #define UNBOUND_CHAR const char
87 #else
88 #define UNBOUND_CHAR char
89 #endif
90
91 /*! \brief Structure for an unbound resolver */
92 struct unbound_resolver {
93         /*! \brief Resolver context itself */
94         struct ub_ctx *context;
95         /*! \brief Thread handling the resolver */
96         pthread_t thread;
97 };
98
99 /*! \brief Structure for query resolver data */
100 struct unbound_resolver_data {
101         /*! \brief ID for the specific query */
102         int id;
103         /*! \brief The resolver in use for the query */
104         struct unbound_resolver *resolver;
105 };
106
107 /*! \brief Unbound configuration state information */
108 struct unbound_config_state {
109         /*! \brief The configured resolver */
110         struct unbound_resolver *resolver;
111 };
112
113 /*! \brief A structure to hold global configuration-related options */
114 struct unbound_global_config {
115         AST_DECLARE_STRING_FIELDS(
116                 AST_STRING_FIELD(hosts);   /*!< Optional hosts file */
117                 AST_STRING_FIELD(resolv);  /*!< Optional resolv.conf file */
118                 AST_STRING_FIELD(ta_file); /*!< Optional trust anchor file */
119         );
120         /*! \brief List of nameservers (in order) to use for queries */
121         struct ao2_container *nameservers;
122         /*! \brief Debug level for the resolver */
123         unsigned int debug;
124         /*! \brief State information */
125         struct unbound_config_state *state;
126 };
127
128 /*! \brief A container for config related information */
129 struct unbound_config {
130         struct unbound_global_config *global;
131 };
132
133 /*!
134  * \brief Allocate a unbound_config to hold a snapshot of the complete results of parsing a config
135  * \internal
136  * \returns A void pointer to a newly allocated unbound_config
137  */
138 static void *unbound_config_alloc(void);
139
140 /*! \brief An aco_type structure to link the "general" category to the unbound_global_config type */
141 static struct aco_type global_option = {
142         .type = ACO_GLOBAL,
143         .name = "general",
144         .item_offset = offsetof(struct unbound_config, global),
145         .category_match = ACO_WHITELIST_EXACT,
146         .category = "general",
147 };
148
149 static struct aco_type *global_options[] = ACO_TYPES(&global_option);
150
151 static struct aco_file resolver_unbound_conf = {
152         .filename = "resolver_unbound.conf",
153         .types = ACO_TYPES(&global_option),
154 };
155
156 /*! \brief A global object container that will contain the global_config that gets swapped out on reloads */
157 static AO2_GLOBAL_OBJ_STATIC(globals);
158
159 /*!
160  * \brief Finish initializing new configuration
161  * \internal
162  */
163 static int unbound_config_preapply_callback(void);
164
165 /*! \brief Register information about the configs being processed by this module */
166 CONFIG_INFO_STANDARD(cfg_info, globals, unbound_config_alloc,
167         .files = ACO_FILES(&resolver_unbound_conf),
168         .pre_apply_config = unbound_config_preapply_callback,
169 );
170
171 /*! \brief Destructor for unbound resolver */
172 static void unbound_resolver_destroy(void *obj)
173 {
174         struct unbound_resolver *resolver = obj;
175
176         if (resolver->context) {
177                 ub_ctx_delete(resolver->context);
178         }
179 }
180
181 /*! \brief Allocator for unbound resolver */
182 static struct unbound_resolver *unbound_resolver_alloc(void)
183 {
184         struct unbound_resolver *resolver;
185
186         resolver = ao2_alloc_options(sizeof(*resolver), unbound_resolver_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
187         if (!resolver) {
188                 return NULL;
189         }
190
191         resolver->thread = AST_PTHREADT_NULL;
192
193         resolver->context = ub_ctx_create();
194         if (!resolver->context) {
195                 ao2_ref(resolver, -1);
196                 return NULL;
197         }
198
199         /* Each async result should be invoked in a separate thread so others are not blocked */
200         ub_ctx_async(resolver->context, 1);
201
202         return resolver;
203 }
204
205 /*! \brief Resolver thread which waits and handles results */
206 static void *unbound_resolver_thread(void *data)
207 {
208         struct unbound_resolver *resolver = data;
209
210         ast_debug(1, "Starting processing for unbound resolver\n");
211
212         while (resolver->thread != AST_PTHREADT_STOP) {
213                 /* Wait for any results to come in */
214                 ast_wait_for_input(ub_fd(resolver->context), -1);
215
216                 /* Finally process any results */
217                 ub_process(resolver->context);
218         }
219
220         ast_debug(1, "Terminating processing for unbound resolver\n");
221
222         ao2_ref(resolver, -1);
223
224         return NULL;
225 }
226
227 /*! \brief Start function for the unbound resolver */
228 static int unbound_resolver_start(struct unbound_resolver *resolver)
229 {
230         int res;
231
232         if (resolver->thread != AST_PTHREADT_NULL) {
233                 return 0;
234         }
235
236         ast_debug(1, "Starting thread for unbound resolver\n");
237
238         res = ast_pthread_create(&resolver->thread, NULL, unbound_resolver_thread, ao2_bump(resolver));
239         if (res) {
240                 ast_debug(1, "Could not start thread for unbound resolver\n");
241                 ao2_ref(resolver, -1);
242         }
243
244         return res;
245 }
246
247 /*! \brief Stop function for the unbound resolver */
248 static void unbound_resolver_stop(struct unbound_resolver *resolver)
249 {
250         pthread_t thread;
251
252         if (resolver->thread == AST_PTHREADT_NULL) {
253                 return;
254         }
255
256         ast_debug(1, "Stopping processing thread for unbound resolver\n");
257
258         thread = resolver->thread;
259         resolver->thread = AST_PTHREADT_STOP;
260         pthread_kill(thread, SIGURG);
261         pthread_join(thread, NULL);
262
263         ast_debug(1, "Stopped processing thread for unbound resolver\n");
264 }
265
266 /*! \brief Callback invoked when resolution completes on a query */
267 static void unbound_resolver_callback(void *data, int err, struct ub_result *ub_result)
268 {
269         struct ast_dns_query *query = data;
270
271         if (!ast_dns_resolver_set_result(query, ub_result->secure, ub_result->bogus, ub_result->rcode,
272                 S_OR(ub_result->canonname, ast_dns_query_get_name(query)), ub_result->answer_packet, ub_result->answer_len)) {
273                 int i;
274                 char *data;
275
276                 for (i = 0; (data = ub_result->data[i]); i++) {
277                         if (ast_dns_resolver_add_record(query, ub_result->qtype, ub_result->qclass, ub_result->ttl,
278                                 data, ub_result->len[i])) {
279                                 break;
280                         }
281                 }
282         }
283
284         ast_dns_resolver_completed(query);
285         ao2_ref(query, -1);
286         ub_resolve_free(ub_result);
287 }
288
289 static void unbound_resolver_data_dtor(void *vdoomed)
290 {
291         struct unbound_resolver_data *doomed = vdoomed;
292
293         ao2_cleanup(doomed->resolver);
294 }
295
296 static int unbound_resolver_resolve(struct ast_dns_query *query)
297 {
298         struct unbound_config *cfg = ao2_global_obj_ref(globals);
299         struct unbound_resolver_data *data;
300         int res;
301
302         data = ao2_alloc_options(sizeof(*data), unbound_resolver_data_dtor,
303                 AO2_ALLOC_OPT_LOCK_NOLOCK);
304         if (!data) {
305                 ast_log(LOG_ERROR, "Failed to allocate resolver data for resolution of '%s'\n",
306                         ast_dns_query_get_name(query));
307                 return -1;
308         }
309         data->resolver = ao2_bump(cfg->global->state->resolver);
310         ast_dns_resolver_set_data(query, data);
311
312         res = ub_resolve_async(data->resolver->context, (UNBOUND_CHAR *)ast_dns_query_get_name(query),
313                 ast_dns_query_get_rr_type(query), ast_dns_query_get_rr_class(query),
314                 ao2_bump(query), unbound_resolver_callback, &data->id);
315
316         if (res) {
317                 ast_log(LOG_ERROR, "Failed to perform async DNS resolution of '%s'\n",
318                         ast_dns_query_get_name(query));
319                 ao2_ref(query, -1);
320         }
321
322         ao2_ref(data, -1);
323         ao2_ref(cfg, -1);
324         return res;
325 }
326
327 static int unbound_resolver_cancel(struct ast_dns_query *query)
328 {
329         struct unbound_resolver_data *data = ast_dns_resolver_get_data(query);
330         int res;
331
332         res = ub_cancel(data->resolver->context, data->id);
333         if (!res) {
334                 /* When this query was started we bumped the ref, now that it has been cancelled we have ownership and
335                  * need to drop it
336                  */
337                 ao2_ref(query, -1);
338         }
339
340         return res;
341 }
342
343 struct ast_dns_resolver unbound_resolver = {
344         .name = "unbound",
345         .priority = 100,
346         .resolve = unbound_resolver_resolve,
347         .cancel = unbound_resolver_cancel,
348 };
349
350 static void unbound_config_destructor(void *obj)
351 {
352         struct unbound_config *cfg = obj;
353
354         ao2_cleanup(cfg->global);
355 }
356
357 static void unbound_global_config_destructor(void *obj)
358 {
359         struct unbound_global_config *global = obj;
360
361         ast_string_field_free_memory(global);
362         ao2_cleanup(global->nameservers);
363         ao2_cleanup(global->state);
364 }
365
366 static void unbound_config_state_destructor(void *obj)
367 {
368         struct unbound_config_state *state = obj;
369
370         if (state->resolver) {
371                 unbound_resolver_stop(state->resolver);
372                 ao2_ref(state->resolver, -1);
373         }
374 }
375
376 static void *unbound_config_alloc(void)
377 {
378         struct unbound_config *cfg;
379
380         cfg = ao2_alloc_options(sizeof(*cfg), unbound_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
381         if (!cfg) {
382                 return NULL;
383         }
384
385         /* Allocate/initialize memory */
386         cfg->global = ao2_alloc_options(sizeof(*cfg->global), unbound_global_config_destructor,
387                 AO2_ALLOC_OPT_LOCK_NOLOCK);
388         if (!cfg->global) {
389                 goto error;
390         }
391
392         if (ast_string_field_init(cfg->global, 128)) {
393                 goto error;
394         }
395
396         return cfg;
397 error:
398         ao2_ref(cfg, -1);
399         return NULL;
400 }
401
402 static int unbound_config_preapply(struct unbound_config *cfg)
403 {
404         int res = 0;
405
406         cfg->global->state = ao2_alloc_options(sizeof(*cfg->global->state), unbound_config_state_destructor,
407                 AO2_ALLOC_OPT_LOCK_NOLOCK);
408         if (!cfg->global->state) {
409                 ast_log(LOG_ERROR, "Could not allocate unbound resolver state structure\n");
410                 return -1;
411         }
412
413         cfg->global->state->resolver = unbound_resolver_alloc();
414         if (!cfg->global->state->resolver) {
415                 ast_log(LOG_ERROR, "Could not create an unbound resolver\n");
416                 return -1;
417         }
418
419         ub_ctx_debuglevel(cfg->global->state->resolver->context, cfg->global->debug);
420
421         if (!strcmp(cfg->global->hosts, "system")) {
422                 res = ub_ctx_hosts(cfg->global->state->resolver->context, NULL);
423         } else if (!ast_strlen_zero(cfg->global->hosts)) {
424                 res = ub_ctx_hosts(cfg->global->state->resolver->context, (UNBOUND_CHAR *)cfg->global->hosts);
425         }
426
427         if (res) {
428                 ast_log(LOG_ERROR, "Failed to set hosts file to '%s' in unbound resolver: %s\n",
429                         cfg->global->hosts, ub_strerror(res));
430                 return -1;
431         }
432
433         if (cfg->global->nameservers) {
434                 struct ao2_iterator it_nameservers;
435                 char *nameserver;
436
437                 it_nameservers = ao2_iterator_init(cfg->global->nameservers, 0);
438                 while (!res && (nameserver = ao2_iterator_next(&it_nameservers))) {
439                         res = ub_ctx_set_fwd(cfg->global->state->resolver->context, (UNBOUND_CHAR *)nameserver);
440
441                         if (res) {
442                                 ast_log(LOG_ERROR, "Failed to add nameserver '%s' to unbound resolver: %s\n",
443                                         nameserver, ub_strerror(res));
444                         }
445                         ao2_ref(nameserver, -1);
446                 }
447                 ao2_iterator_destroy(&it_nameservers);
448                 if (res) {
449                         return -1;
450                 }
451         }
452
453         if (!strcmp(cfg->global->resolv, "system")) {
454                 res = ub_ctx_resolvconf(cfg->global->state->resolver->context, NULL);
455         } else if (!ast_strlen_zero(cfg->global->resolv)) {
456                 res = ub_ctx_resolvconf(cfg->global->state->resolver->context, (UNBOUND_CHAR *)cfg->global->resolv);
457         }
458
459         if (res) {
460                 ast_log(LOG_ERROR, "Failed to set resolv.conf file to '%s' in unbound resolver: %s\n",
461                         cfg->global->resolv, ub_strerror(res));
462                 return -1;
463         }
464
465         if (!ast_strlen_zero(cfg->global->ta_file)) {
466                 res = ub_ctx_add_ta_file(cfg->global->state->resolver->context, (UNBOUND_CHAR *)cfg->global->ta_file);
467
468                 if (res) {
469                         ast_log(LOG_ERROR, "Failed to set trusted anchor file to '%s' in unbound resolver: %s\n",
470                                 cfg->global->ta_file, ub_strerror(res));
471                         return -1;
472                 }
473         }
474
475         if (unbound_resolver_start(cfg->global->state->resolver)) {
476                 ast_log(LOG_ERROR, "Could not start unbound resolver thread\n");
477                 return -1;
478         }
479
480         return 0;
481 }
482
483 static int unbound_config_apply_default(void)
484 {
485         struct unbound_config *cfg;
486
487         cfg = unbound_config_alloc();
488         if (!cfg) {
489                 ast_log(LOG_ERROR, "Could not create default configuration for unbound resolver\n");
490                 return -1;
491         }
492
493         aco_set_defaults(&global_option, "general", cfg->global);
494
495         if (unbound_config_preapply(cfg)) {
496                 ao2_ref(cfg, -1);
497                 return -1;
498         }
499
500         ast_verb(1, "Starting unbound resolver using default configuration\n");
501
502         ao2_global_obj_replace_unref(globals, cfg);
503         ao2_ref(cfg, -1);
504
505         return 0;
506 }
507
508 static int unbound_config_preapply_callback(void)
509 {
510         return unbound_config_preapply(aco_pending_config(&cfg_info));
511 }
512
513 #ifdef TEST_FRAMEWORK
514
515 #include "asterisk/dns_naptr.h"
516
517 /*!
518  * \brief A DNS record to be used during a test
519  */
520 struct dns_record {
521         /*! String representation of the record, as would be found in a file */
522         const char *as_string;
523         /*! The domain this record belongs to */
524         const char *domain;
525         /*! The type of the record */
526         int rr_type;
527         /*! The class of the record */
528         int rr_class;
529         /*! The TTL of the record, in seconds */
530         int ttl;
531         /*! The RDATA of the DNS record */
532         const char *buf;
533         /*! The size of the RDATA */
534         const size_t bufsize;
535         /*! Whether a record checker has visited this record */
536         int visited;
537 };
538
539 /*!
540  * \brief Resolution function for tests.
541  *
542  * Several tests will have similar setups but will want to make use of a different
543  * means of actually making queries and checking their results. This pluggable
544  * function pointer allows for similar tests to be operated in different ways.
545  *
546  * \param test The test being run
547  * \param domain The domain to look up
548  * \param rr_type The record type to look up
549  * \param rr_class The class of record to look up
550  * \param records All records that exist for the test.
551  * \param num_records Number of records in the records array.
552  *
553  * \retval 0 The test has passed thus far.
554  * \retval -1 The test has failed.
555  */
556 typedef int (*resolve_fn)(struct ast_test *test, const char *domain, int rr_type,
557                 int rr_class, struct dns_record *records, size_t num_records);
558
559 /*!
560  * \brief Pluggable function for running a synchronous query and checking its results
561  */
562 static int nominal_sync_run(struct ast_test *test, const char *domain, int rr_type,
563                 int rr_class, struct dns_record *records, size_t num_records)
564 {
565         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
566         const struct ast_dns_record *record;
567         int i;
568
569         /* Start by making sure no records have been visited */
570         for (i = 0; i < num_records; ++i) {
571                 records[i].visited = 0;
572         }
573
574         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
575
576         if (ast_dns_resolve(domain, rr_type, rr_class, &result)) {
577                 ast_test_status_update(test, "Failed to perform synchronous resolution of domain %s\n", domain);
578                 return -1;
579         }
580
581         if (!result) {
582                 ast_test_status_update(test, "Successful synchronous resolution of domain %s gave NULL result\n", domain);
583                 return -1;
584         }
585
586         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
587                 int match = 0;
588
589                 /* Let's make sure this matches one of our known records */
590                 for (i = 0; i < num_records; ++i) {
591                         if (ast_dns_record_get_rr_type(record) == records[i].rr_type &&
592                                         ast_dns_record_get_rr_class(record) == records[i].rr_class &&
593                                         ast_dns_record_get_ttl(record) == records[i].ttl &&
594                                         !memcmp(ast_dns_record_get_data(record), records[i].buf, records[i].bufsize)) {
595                                 match = 1;
596                                 records[i].visited = 1;
597                                 break;
598                         }
599                 }
600
601                 if (!match) {
602                         ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
603                         return -1;
604                 }
605         }
606
607         return 0;
608 }
609
610 /*!
611  * \brief Data required for an asynchronous callback
612  */
613 struct async_data {
614         /*! The set of DNS records on a test */
615         struct dns_record *records;
616         /*! The number of DNS records on the test */
617         size_t num_records;
618         /*! Whether an asynchronous query failed */
619         int failed;
620         /*! Indicates the asynchronous query is complete */
621         int complete;
622         ast_mutex_t lock;
623         ast_cond_t cond;
624 };
625
626 static void async_data_destructor(void *obj)
627 {
628         struct async_data *adata = obj;
629
630         ast_mutex_destroy(&adata->lock);
631         ast_cond_destroy(&adata->cond);
632 }
633
634 static struct async_data *async_data_alloc(struct dns_record *records, size_t num_records)
635 {
636         struct async_data *adata;
637
638         adata = ao2_alloc(sizeof(*adata), async_data_destructor);
639         if (!adata) {
640                 return NULL;
641         }
642
643         ast_mutex_init(&adata->lock);
644         ast_cond_init(&adata->cond, NULL);
645         adata->records = records;
646         adata->num_records = num_records;
647
648         return adata;
649 }
650
651 /*!
652  * \brief Callback for asynchronous queries
653  *
654  * This query will check that the records in the DNS result match
655  * records that the test has created. The success or failure of the
656  * query is indicated through the async_data failed field.
657  *
658  * \param query The DNS query that has been resolved
659  */
660 static void async_callback(const struct ast_dns_query *query)
661 {
662         struct async_data *adata = ast_dns_query_get_data(query);
663         struct ast_dns_result *result = ast_dns_query_get_result(query);
664         const struct ast_dns_record *record;
665         int i;
666
667         if (!result) {
668                 adata->failed = -1;
669                 goto end;
670         }
671
672         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
673                 int match = 0;
674
675                 /* Let's make sure this matches one of our known records */
676                 for (i = 0; i < adata->num_records; ++i) {
677                         if (ast_dns_record_get_rr_type(record) == adata->records[i].rr_type &&
678                                         ast_dns_record_get_rr_class(record) == adata->records[i].rr_class &&
679                                         ast_dns_record_get_ttl(record) == adata->records[i].ttl &&
680                                         !memcmp(ast_dns_record_get_data(record), adata->records[i].buf, adata->records[i].bufsize)) {
681                                 match = 1;
682                                 adata->records[i].visited = 1;
683                                 break;
684                         }
685                 }
686
687                 if (!match) {
688                         adata->failed = -1;
689                         goto end;
690                 }
691         }
692
693 end:
694         ast_mutex_lock(&adata->lock);
695         adata->complete = 1;
696         ast_cond_signal(&adata->cond);
697         ast_mutex_unlock(&adata->lock);
698 }
699
700 /*!
701  * \brief Pluggable function for performing an asynchronous query during a test
702  *
703  * Unlike the synchronous version, this does not check the records, instead leaving
704  * that to be done in the asynchronous callback.
705  */
706 static int nominal_async_run(struct ast_test *test, const char *domain, int rr_type,
707                 int rr_class, struct dns_record *records, size_t num_records)
708 {
709         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
710         RAII_VAR(struct async_data *, adata, NULL, ao2_cleanup);
711         int i;
712
713         adata = async_data_alloc(records, num_records);
714         if (!adata) {
715                 ast_test_status_update(test, "Unable to allocate data for async query\n");
716                 return -1;
717         }
718
719         /* Start by making sure no records have been visited */
720         for (i = 0; i < num_records; ++i) {
721                 records[i].visited = 0;
722         }
723
724         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
725
726         active = ast_dns_resolve_async(domain, rr_type, rr_class, async_callback, adata);
727         if (!active) {
728                 ast_test_status_update(test, "Failed to perform asynchronous resolution of domain %s\n", domain);
729                 return -1;
730         }
731
732         ast_mutex_lock(&adata->lock);
733         while (!adata->complete) {
734                 ast_cond_wait(&adata->cond, &adata->lock);
735         }
736         ast_mutex_unlock(&adata->lock);
737
738         if (adata->failed) {
739                 ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
740         }
741         return adata->failed;
742 }
743
744 /*!
745  * \brief Framework for running a nominal DNS test
746  *
747  * Synchronous and asynchronous tests mostly have the same setup, so this function
748  * serves as a common way to set up both types of tests by accepting a pluggable
749  * function to determine which type of lookup is used
750  *
751  * \param test The test being run
752  * \param runner The method for resolving queries on this test
753  */
754 static enum ast_test_result_state nominal_test(struct ast_test *test, resolve_fn runner)
755 {
756         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
757         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
758
759         static const size_t V4_SIZE = sizeof(struct in_addr);
760         static const size_t V6_SIZE = sizeof(struct in6_addr);
761
762         static UNBOUND_CHAR *DOMAIN1 = "goose.feathers";
763         static UNBOUND_CHAR *DOMAIN2 = "duck.feathers";
764
765         static UNBOUND_CHAR *ADDR1 = "127.0.0.2";
766         static UNBOUND_CHAR *ADDR2 = "127.0.0.3";
767         static UNBOUND_CHAR *ADDR3 = "::1";
768         static UNBOUND_CHAR *ADDR4 = "127.0.0.4";
769
770         char addr1_buf[V4_SIZE];
771         char addr2_buf[V4_SIZE];
772         char addr3_buf[V6_SIZE];
773         char addr4_buf[V4_SIZE];
774
775         struct dns_record records [] = {
776                 { "goose.feathers 12345 IN A 127.0.0.2", DOMAIN1, ns_t_a,    ns_c_in, 12345, addr1_buf, V4_SIZE, 0 },
777                 { "goose.feathers 12345 IN A 127.0.0.3", DOMAIN1, ns_t_a,    ns_c_in, 12345, addr2_buf, V4_SIZE, 0 },
778                 { "goose.feathers 12345 IN AAAA ::1",    DOMAIN1, ns_t_aaaa, ns_c_in, 12345, addr3_buf, V6_SIZE, 0 },
779                 { "duck.feathers 12345 IN A 127.0.0.4",  DOMAIN2, ns_t_a,    ns_c_in, 12345, addr4_buf, V4_SIZE, 0 },
780         };
781
782         struct {
783                 const char *domain;
784                 int rr_type;
785                 int rr_class;
786                 int visited[ARRAY_LEN(records)];
787         } runs [] = {
788                 { DOMAIN1, ns_t_a,    ns_c_in, { 1, 1, 0, 0 } },
789                 { DOMAIN1, ns_t_aaaa, ns_c_in, { 0, 0, 1, 0 } },
790                 { DOMAIN2, ns_t_a,    ns_c_in, { 0, 0, 0, 1 } },
791         };
792
793         int i;
794         enum ast_test_result_state res = AST_TEST_PASS;
795
796         inet_pton(AF_INET,  ADDR1, addr1_buf);
797         inet_pton(AF_INET,  ADDR2, addr2_buf);
798         inet_pton(AF_INET6,  ADDR3, addr3_buf);
799         inet_pton(AF_INET, ADDR4, addr4_buf);
800
801         cfg = ao2_global_obj_ref(globals);
802         resolver = ao2_bump(cfg->global->state->resolver);
803
804         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
805         ub_ctx_zone_add(resolver->context, DOMAIN2, "static");
806
807         for (i = 0; i < ARRAY_LEN(records); ++i) {
808                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
809         }
810
811         for (i = 0; i < ARRAY_LEN(runs); ++i) {
812                 int j;
813
814                 if (runner(test, runs[i].domain, runs[i].rr_type, runs[i].rr_class, records, ARRAY_LEN(records))) {
815                         res = AST_TEST_FAIL;
816                         goto cleanup;
817                 }
818
819                 for (j = 0; j < ARRAY_LEN(records); ++j) {
820                         if (records[j].visited != runs[i].visited[j]) {
821                                 ast_test_status_update(test, "DNS results match unexpected records\n");
822                                 res = AST_TEST_FAIL;
823                                 goto cleanup;
824                         }
825                 }
826         }
827
828 cleanup:
829         for (i = 0; i < ARRAY_LEN(records); ++i) {
830                 ub_ctx_data_remove(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
831         }
832         ub_ctx_zone_remove(resolver->context, DOMAIN1);
833         ub_ctx_zone_remove(resolver->context, DOMAIN2);
834
835         return res;
836 }
837
838 AST_TEST_DEFINE(resolve_sync)
839 {
840
841         switch (cmd) {
842         case TEST_INIT:
843                 info->name = "resolve_sync";
844                 info->category = "/res/res_resolver_unbound/";
845                 info->summary = "Test nominal synchronous resolution using libunbound";
846                 info->description = "This test performs the following:\n"
847                         "\t* Set two static A records and one static AAAA record on one domain\n"
848                         "\t* Set an A record for a second domain\n"
849                         "\t* Perform an A record lookup on the first domain\n"
850                         "\t* Ensure that both A records are returned and no AAAA record is returned\n"
851                         "\t* Perform an AAAA record lookup on the first domain\n"
852                         "\t* Ensure that the AAAA record is returned and no A record is returned\n"
853                         "\t* Perform an A record lookup on the second domain\n"
854                         "\t* Ensure that the A record from the second domain is returned";
855                 return AST_TEST_NOT_RUN;
856         case TEST_EXECUTE:
857                 break;
858         }
859
860         return nominal_test(test, nominal_sync_run);
861 }
862
863 AST_TEST_DEFINE(resolve_async)
864 {
865         switch (cmd) {
866         case TEST_INIT:
867                 info->name = "resolve_async";
868                 info->category = "/res/res_resolver_unbound/";
869                 info->summary = "Test nominal asynchronous resolution using libunbound";
870                 info->description = "This test performs the following:\n"
871                         "\t* Set two static A records and one static AAAA record on one domain\n"
872                         "\t* Set an A record for a second domain\n"
873                         "\t* Perform an A record lookup on the first domain\n"
874                         "\t* Ensure that both A records are returned and no AAAA record is returned\n"
875                         "\t* Perform an AAAA record lookup on the first domain\n"
876                         "\t* Ensure that the AAAA record is returned and no A record is returned\n"
877                         "\t* Perform an A record lookup on the second domain\n"
878                         "\t* Ensure that the A record from the second domain is returned";
879                 return AST_TEST_NOT_RUN;
880         case TEST_EXECUTE:
881                 break;
882         }
883
884         return nominal_test(test, nominal_async_run);
885 }
886
887 typedef int (*off_nominal_resolve_fn)(struct ast_test *test, const char *domain, int rr_type,
888                 int rr_class, int expected_rcode);
889
890 static int off_nominal_sync_run(struct ast_test *test, const char *domain, int rr_type,
891                 int rr_class, int expected_rcode)
892 {
893         struct ast_dns_result *result;
894         int res = 0;
895
896         if (ast_dns_resolve(domain, rr_type, rr_class, &result)) {
897                 ast_test_status_update(test, "Failed to perform resolution :(\n");
898                 return -1;
899         }
900
901         if (!result) {
902                 ast_test_status_update(test, "Resolution returned no result\n");
903                 return -1;
904         }
905
906         if (ast_dns_result_get_rcode(result) != expected_rcode) {
907                 ast_test_status_update(test, "Unexpected rcode from DNS resolution\n");
908                 res = -1;
909         }
910
911         if (ast_dns_result_get_records(result)) {
912                 ast_test_status_update(test, "DNS resolution returned records unexpectedly\n");
913                 res = -1;
914         }
915
916         ast_dns_result_free(result);
917         return res;
918 }
919
920 /*!
921  * \brief User data for off-nominal async resolution test
922  */
923 struct off_nominal_async_data {
924         /*! The DNS result's expected rcode */
925         int expected_rcode;
926         /*! Whether an asynchronous query failed */
927         int failed;
928         /*! Indicates the asynchronous query is complete */
929         int complete;
930         ast_mutex_t lock;
931         ast_cond_t cond;
932 };
933
934 static void off_nominal_async_data_destructor(void *obj)
935 {
936         struct off_nominal_async_data *adata = obj;
937
938         ast_mutex_destroy(&adata->lock);
939         ast_cond_destroy(&adata->cond);
940 }
941
942 static struct off_nominal_async_data *off_nominal_async_data_alloc(int expected_rcode)
943 {
944         struct off_nominal_async_data *adata;
945
946         adata = ao2_alloc(sizeof(*adata), off_nominal_async_data_destructor);
947         if (!adata) {
948                 return NULL;
949         }
950
951         ast_mutex_init(&adata->lock);
952         ast_cond_init(&adata->cond, NULL);
953
954         adata->expected_rcode = expected_rcode;
955
956         return adata;
957 }
958
959 /*!
960  * \brief Async callback for off-nominal async test
961  *
962  * This test ensures that there is a result present on the query, then it checks
963  * that the rcode on the result is the expected value and that there are no
964  * records on the result.
965  *
966  * Once completed, the testing thread is signaled that the async query has
967  * completed.
968  */
969 static void off_nominal_async_callback(const struct ast_dns_query *query)
970 {
971         struct off_nominal_async_data *adata = ast_dns_query_get_data(query);
972         struct ast_dns_result *result = ast_dns_query_get_result(query);
973
974         if (!result) {
975                 adata->failed = -1;
976                 goto end;
977         }
978
979         if (ast_dns_result_get_rcode(result) != adata->expected_rcode) {
980                 adata->failed = -1;
981         }
982
983         if (ast_dns_result_get_records(result)) {
984                 adata->failed = -1;
985         }
986
987 end:
988         ast_mutex_lock(&adata->lock);
989         adata->complete = 1;
990         ast_cond_signal(&adata->cond);
991         ast_mutex_unlock(&adata->lock);
992 }
993
994 static int off_nominal_async_run(struct ast_test *test, const char *domain, int rr_type,
995                 int rr_class, int expected_rcode)
996 {
997         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
998         RAII_VAR(struct off_nominal_async_data *, adata, NULL, ao2_cleanup);
999
1000         adata = off_nominal_async_data_alloc(expected_rcode);
1001         if (!adata) {
1002                 ast_test_status_update(test, "Unable to allocate data for async query\n");
1003                 return -1;
1004         }
1005
1006         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
1007
1008         active = ast_dns_resolve_async(domain, rr_type, rr_class, off_nominal_async_callback, adata);
1009         if (!active) {
1010                 ast_test_status_update(test, "Failed to perform asynchronous resolution of domain %s\n", domain);
1011                 return -1;
1012         }
1013
1014         ast_mutex_lock(&adata->lock);
1015         while (!adata->complete) {
1016                 ast_cond_wait(&adata->cond, &adata->lock);
1017         }
1018         ast_mutex_unlock(&adata->lock);
1019
1020         if (adata->failed) {
1021                 ast_test_status_update(test, "Asynchronous resolution failure %s\n", domain);
1022         }
1023         return adata->failed;
1024 }
1025
1026 static enum ast_test_result_state off_nominal_test(struct ast_test *test,
1027                 off_nominal_resolve_fn runner)
1028 {
1029         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1030         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1031
1032         static const size_t V4_SIZE = sizeof(struct in_addr);
1033
1034         static UNBOUND_CHAR *DOMAIN1 = "goose.feathers";
1035         static UNBOUND_CHAR *DOMAIN2 = "duck.feathers";
1036
1037         static UNBOUND_CHAR *ADDR1 = "127.0.0.2";
1038
1039         char addr1_buf[V4_SIZE];
1040
1041         struct dns_record records [] = {
1042                 { "goose.feathers 12345 IN A 127.0.0.2", DOMAIN1, ns_t_a, ns_c_in, 12345, addr1_buf, V4_SIZE, 0, },
1043         };
1044
1045         int i;
1046         enum ast_test_result_state res = AST_TEST_PASS;
1047
1048         struct {
1049                 const char *domain;
1050                 int rr_type;
1051                 int rr_class;
1052                 int rcode;
1053         } runs [] = {
1054                 { DOMAIN2, ns_t_a,    ns_c_in, ns_r_nxdomain },
1055                 { DOMAIN1, ns_t_aaaa, ns_c_in, ns_r_noerror },
1056                 { DOMAIN1, ns_t_a,    ns_c_chaos, ns_r_refused },
1057         };
1058
1059         inet_pton(AF_INET,  ADDR1, addr1_buf);
1060
1061         cfg = ao2_global_obj_ref(globals);
1062         resolver = ao2_bump(cfg->global->state->resolver);
1063
1064         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1065         ub_ctx_zone_add(resolver->context, DOMAIN2, "static");
1066
1067         for (i = 0; i < ARRAY_LEN(records); ++i) {
1068                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
1069         }
1070
1071         for (i = 0; i < ARRAY_LEN(runs); ++i) {
1072                 if (runner(test, runs[i].domain, runs[i].rr_type, runs[i].rr_class, runs[i].rcode)) {
1073                         res = AST_TEST_FAIL;
1074                 }
1075         }
1076
1077         return res;
1078 }
1079
1080 AST_TEST_DEFINE(resolve_sync_off_nominal)
1081 {
1082         switch (cmd) {
1083         case TEST_INIT:
1084                 info->name = "resolve_sync_off_nominal";
1085                 info->category = "/res/res_resolver_unbound/";
1086                 info->summary = "Test off-nominal synchronous resolution using libunbound";
1087                 info->description = "This test performs the following:\n"
1088                         "\t* Attempt a lookup of a non-existent domain\n"
1089                         "\t* Attempt a lookup of a AAAA record on a domain that contains only A records\n"
1090                         "\t* Attempt a lookup of an A record on Chaos-net";
1091                 return AST_TEST_NOT_RUN;
1092         case TEST_EXECUTE:
1093                 break;
1094         }
1095
1096         return off_nominal_test(test, off_nominal_sync_run);
1097 }
1098
1099 AST_TEST_DEFINE(resolve_async_off_nominal)
1100 {
1101         switch (cmd) {
1102         case TEST_INIT:
1103                 info->name = "resolve_async_off_nominal";
1104                 info->category = "/res/res_resolver_unbound/";
1105                 info->summary = "Test off-nominal synchronous resolution using libunbound";
1106                 info->description = "This test performs the following:\n"
1107                         "\t* Attempt a lookup of a non-existent domain\n"
1108                         "\t* Attempt a lookup of a AAAA record on a domain that contains only A records\n"
1109                         "\t* Attempt a lookup of an A record on Chaos-net";
1110                 return AST_TEST_NOT_RUN;
1111         case TEST_EXECUTE:
1112                 break;
1113         }
1114
1115         return off_nominal_test(test, off_nominal_async_run);
1116 }
1117
1118 /*!
1119  * \brief Minimal data required to signal the completion of an async resolve
1120  */
1121 struct async_minimal_data {
1122         int complete;
1123         ast_mutex_t lock;
1124         ast_cond_t cond;
1125 };
1126
1127 static void async_minimal_data_destructor(void *obj)
1128 {
1129         struct async_minimal_data *adata = obj;
1130
1131         ast_mutex_destroy(&adata->lock);
1132         ast_cond_destroy(&adata->cond);
1133 }
1134
1135 static struct async_minimal_data *async_minimal_data_alloc(void)
1136 {
1137         struct async_minimal_data *adata;
1138
1139         adata = ao2_alloc(sizeof(*adata), async_minimal_data_destructor);
1140         if (!adata) {
1141                 return NULL;
1142         }
1143
1144         ast_mutex_init(&adata->lock);
1145         ast_cond_init(&adata->cond, NULL);
1146
1147         return adata;
1148 }
1149
1150 /*!
1151  * \brief Async callback for off-nominal cancellation test.
1152  *
1153  * This simply signals the testing thread that the query completed
1154  */
1155 static void minimal_callback(const struct ast_dns_query *query)
1156 {
1157         struct async_minimal_data *adata = ast_dns_query_get_data(query);
1158
1159         ast_mutex_lock(&adata->lock);
1160         adata->complete = 1;
1161         ast_cond_signal(&adata->cond);
1162         ast_mutex_unlock(&adata->lock);
1163 }
1164
1165 AST_TEST_DEFINE(resolve_cancel_off_nominal)
1166 {
1167         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
1168         RAII_VAR(struct async_minimal_data *, adata, NULL, ao2_cleanup);
1169
1170         switch (cmd) {
1171         case TEST_INIT:
1172                 info->name = "resolve_cancel_off_nominal";
1173                 info->category = "/res/res_resolver_unbound/";
1174                 info->summary = "Off nominal cancellation test using libunbound";
1175                 info->description = "This test does the following:\n"
1176                         "\t* Perform an asynchronous query\n"
1177                         "\t* Once the query has completed, attempt to cancel it";
1178                 return AST_TEST_NOT_RUN;
1179         case TEST_EXECUTE:
1180                 break;
1181         }
1182
1183         adata = async_minimal_data_alloc();
1184         if (!adata) {
1185                 ast_test_status_update(test, "Failed to allocate necessary data for test\n");
1186                 return AST_TEST_FAIL;
1187         }
1188
1189         active = ast_dns_resolve_async("crunchy.peanut.butter", ns_t_a, ns_c_in, minimal_callback, adata);
1190         if (!active) {
1191                 ast_test_status_update(test, "Failed to perform asynchronous query\n");
1192                 return AST_TEST_FAIL;
1193         }
1194
1195         /* Wait for async query to complete */
1196         ast_mutex_lock(&adata->lock);
1197         while (!adata->complete) {
1198                 ast_cond_wait(&adata->cond, &adata->lock);
1199         }
1200         ast_mutex_unlock(&adata->lock);
1201
1202         if (!ast_dns_resolve_cancel(active)) {
1203                 ast_test_status_update(test, "Successfully canceled completed query\n");
1204                 return AST_TEST_FAIL;
1205         }
1206
1207         return AST_TEST_PASS;
1208 }
1209
1210 AST_TEST_DEFINE(resolve_naptr)
1211 {
1212         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1213         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1214         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
1215
1216         const struct ast_dns_record *record;
1217
1218         static char * DOMAIN1 = "goose.feathers";
1219         int i;
1220         enum ast_test_result_state res = AST_TEST_PASS;
1221
1222         struct naptr_record {
1223                 const char *zone_entry;
1224                 uint16_t order;
1225                 uint16_t preference;
1226                 const char *flags;
1227                 const char *services;
1228                 const char *regexp;
1229                 const char *replacement;
1230                 int visited;
1231         } records [] = {
1232                 { "goose.feathers 12345 IN NAPTR 100 100 A SIP+D2U \"\" goose.down", 100, 100, "A", "SIP+D2U", "", "goose.down", 0},
1233                 { "goose.feathers 12345 IN NAPTR 100 200 A SIP+D2T \"\" duck.down", 100, 200, "A", "SIP+D2T", "", "duck.down", 0},
1234                 { "goose.feathers 12345 IN NAPTR 200 100 A SIPS+D2U \"\" pheasant.down", 200, 100, "A", "SIPS+D2U", "", "pheasant.down", 0},
1235                 { "goose.feathers 12345 IN NAPTR 200 200 A SIPS+D2T \"\" platypus.fur", 200, 200, "A", "SIPS+D2T", "", "platypus.fur", 0},
1236         };
1237
1238         switch (cmd) {
1239         case TEST_INIT:
1240                 info->name = "resolve_naptr";
1241                 info->category = "/res/res_resolver_unbound/";
1242                 info->summary = "Attempt resolution of NAPTR record";
1243                 info->description = "This test performs a NAPTR lookup and ensures that\n"
1244                         "the returned record has the appropriate values set";
1245                 return AST_TEST_NOT_RUN;
1246         case TEST_EXECUTE:
1247                 break;
1248         }
1249
1250         cfg = ao2_global_obj_ref(globals);
1251         resolver = ao2_bump(cfg->global->state->resolver);
1252
1253         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1254
1255         for (i = 0; i < ARRAY_LEN(records); ++i) {
1256                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].zone_entry);
1257         }
1258
1259         if (ast_dns_resolve(DOMAIN1, ns_t_naptr, ns_c_in, &result)) {
1260                 ast_test_status_update(test, "Failed to resolve domain\n");
1261                 return AST_TEST_FAIL;
1262         }
1263
1264         if (!result) {
1265                 ast_test_status_update(test, "Successful resolution set a NULL result\n");
1266                 return AST_TEST_FAIL;
1267         }
1268
1269         record = ast_dns_result_get_records(result);
1270         if (!record) {
1271                 ast_test_status_update(test, "Failed to get any DNS records from the result\n");
1272                 return AST_TEST_FAIL;
1273         }
1274
1275         i = 0;
1276         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
1277                 if (ast_dns_naptr_get_order(record) != records[i].order) {
1278                         ast_test_status_update(test, "Expected order %hu, got order %hu from NAPTR record\n",
1279                                         records[i].order, ast_dns_naptr_get_order(record));
1280                         res = AST_TEST_FAIL;
1281                 }
1282                 if (ast_dns_naptr_get_preference(record) != records[i].preference) {
1283                         ast_test_status_update(test, "Expected preference %hu, got preference %hu from NAPTR record\n",
1284                                         records[i].preference, ast_dns_naptr_get_preference(record));
1285                         res = AST_TEST_FAIL;
1286                 }
1287                 if (strcmp(ast_dns_naptr_get_flags(record), records[i].flags)) {
1288                         ast_test_status_update(test, "Expected flags %s, got flags %s from NAPTR record\n",
1289                                         records[i].flags, ast_dns_naptr_get_flags(record));
1290                         res = AST_TEST_FAIL;
1291                 }
1292                 if (strcmp(ast_dns_naptr_get_service(record), records[i].services)) {
1293                         ast_test_status_update(test, "Expected services %s, got services %s from NAPTR record\n",
1294                                         records[i].services, ast_dns_naptr_get_service(record));
1295                         res = AST_TEST_FAIL;
1296                 }
1297                 if (strcmp(ast_dns_naptr_get_regexp(record), records[i].regexp)) {
1298                         ast_test_status_update(test, "Expected regexp %s, got regexp %s from NAPTR record\n",
1299                                         records[i].regexp, ast_dns_naptr_get_regexp(record));
1300                         res = AST_TEST_FAIL;
1301                 }
1302                 if (strcmp(ast_dns_naptr_get_replacement(record), records[i].replacement)) {
1303                         ast_test_status_update(test, "Expected replacement %s, got replacement %s from NAPTR record\n",
1304                                         records[i].replacement, ast_dns_naptr_get_replacement(record));
1305                         res = AST_TEST_FAIL;
1306                 }
1307                 records[i].visited = 1;
1308                 ++i;
1309         }
1310
1311         if (i != ARRAY_LEN(records)) {
1312                 ast_test_status_update(test, "Unexpected number of records visited\n");
1313                 res = AST_TEST_FAIL;
1314         }
1315
1316         for (i = 0; i < ARRAY_LEN(records); ++i) {
1317                 if (!records[i].visited) {
1318                         ast_test_status_update(test, "Did not visit all expected NAPTR records\n");
1319                         res = AST_TEST_FAIL;
1320                 }
1321         }
1322
1323         return res;
1324
1325 }
1326
1327 AST_TEST_DEFINE(resolve_srv)
1328 {
1329         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1330         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1331         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
1332         const struct ast_dns_record *record;
1333         static UNBOUND_CHAR *DOMAIN1 = "taco.bananas";
1334         static UNBOUND_CHAR *DOMAIN1_SRV = "taco.bananas 12345 IN SRV 10 20 5060 sip.taco.bananas";
1335         enum ast_test_result_state res = AST_TEST_PASS;
1336
1337         switch (cmd) {
1338         case TEST_INIT:
1339                 info->name = "resolve_srv";
1340                 info->category = "/res/res_resolver_unbound/";
1341                 info->summary = "Test synchronous SRV resolution using libunbound";
1342                 info->description = "This test performs the following:\n"
1343                         "\t* Set one SRV record on one domain\n"
1344                         "\t* Perform an SRV lookup on the domain\n"
1345                         "\t* Ensure that the SRV record returned matches the expected value";
1346                 return AST_TEST_NOT_RUN;
1347         case TEST_EXECUTE:
1348                 break;
1349         }
1350
1351         cfg = ao2_global_obj_ref(globals);
1352         resolver = ao2_bump(cfg->global->state->resolver);
1353
1354         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1355         ub_ctx_data_add(resolver->context, DOMAIN1_SRV);
1356
1357         if (ast_dns_resolve(DOMAIN1, ns_t_srv, ns_c_in, &result)) {
1358                 ast_test_status_update(test, "Failed to synchronously resolve SRV for domain '%s'\n", DOMAIN1);
1359                 res = AST_TEST_FAIL;
1360                 goto cleanup;
1361         }
1362
1363         record = ast_dns_result_get_records(result);
1364         if (ast_dns_srv_get_priority(record) != 10) {
1365                 ast_test_status_update(test, "SRV Record returned priority '%d' when we expected 10\n", ast_dns_srv_get_priority(record));
1366                 res = AST_TEST_FAIL;
1367                 goto cleanup;
1368         }
1369
1370         if (ast_dns_srv_get_weight(record) != 20) {
1371                 ast_test_status_update(test, "SRV Record returned weight '%d' when we expected 20\n", ast_dns_srv_get_weight(record));
1372                 res = AST_TEST_FAIL;
1373                 goto cleanup;
1374         }
1375
1376         if (ast_dns_srv_get_port(record) != 5060) {
1377                 ast_test_status_update(test, "SRV Record returned port '%d' when we expected 5060\n", ast_dns_srv_get_port(record));
1378                 res = AST_TEST_FAIL;
1379                 goto cleanup;
1380         }
1381
1382         if (strcmp(ast_dns_srv_get_host(record), "sip.taco.bananas")) {
1383                 ast_test_status_update(test, "SRV Record returned host '%s' when we expected sip.taco.bananas\n", ast_dns_srv_get_host(record));
1384                 res = AST_TEST_FAIL;
1385                 goto cleanup;
1386         }
1387
1388 cleanup:
1389         ub_ctx_data_remove(resolver->context, DOMAIN1_SRV);
1390         ub_ctx_zone_remove(resolver->context, DOMAIN1);
1391
1392         return res;
1393 }
1394 #endif
1395
1396 static int reload_module(void)
1397 {
1398         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1399                 return AST_MODULE_RELOAD_ERROR;
1400         }
1401
1402         return 0;
1403 }
1404
1405 static int unload_module(void)
1406 {
1407         aco_info_destroy(&cfg_info);
1408         ao2_global_obj_release(globals);
1409
1410         AST_TEST_UNREGISTER(resolve_sync);
1411         AST_TEST_UNREGISTER(resolve_async);
1412         AST_TEST_UNREGISTER(resolve_sync_off_nominal);
1413         AST_TEST_UNREGISTER(resolve_sync_off_nominal);
1414         AST_TEST_UNREGISTER(resolve_cancel_off_nominal);
1415         AST_TEST_UNREGISTER(resolve_naptr);
1416         AST_TEST_UNREGISTER(resolve_srv);
1417         return 0;
1418 }
1419
1420 static int custom_nameserver_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
1421 {
1422         struct unbound_global_config *global = obj;
1423
1424         if (!global->nameservers) {
1425                 global->nameservers = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1);
1426                 if (!global->nameservers) {
1427                         return -1;
1428                 }
1429         }
1430
1431         return ast_str_container_add(global->nameservers, var->value);
1432 }
1433
1434 static int load_module(void)
1435 {
1436         struct ast_config *cfg;
1437         struct ast_flags cfg_flags = { 0, };
1438
1439         if (aco_info_init(&cfg_info)) {
1440                 return AST_MODULE_LOAD_DECLINE;
1441         }
1442
1443         aco_option_register(&cfg_info, "hosts", ACO_EXACT, global_options, "system", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, hosts));
1444         aco_option_register(&cfg_info, "resolv", ACO_EXACT, global_options, "system", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, resolv));
1445         aco_option_register_custom(&cfg_info, "nameserver", ACO_EXACT, global_options, "", custom_nameserver_handler, 0);
1446         aco_option_register(&cfg_info, "debug", ACO_EXACT, global_options, "0", OPT_UINT_T, 0, FLDSET(struct unbound_global_config, debug));
1447         aco_option_register(&cfg_info, "ta_file", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, ta_file));
1448
1449         /* This purposely checks for a configuration file so we don't output an error message in ACO if one is not present */
1450         cfg = ast_config_load(resolver_unbound_conf.filename, cfg_flags);
1451         if (!cfg) {
1452                 if (unbound_config_apply_default()) {
1453                         unload_module();
1454                         return AST_MODULE_LOAD_DECLINE;
1455                 }
1456         } else {
1457                 ast_config_destroy(cfg);
1458                 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1459                         unload_module();
1460                         return AST_MODULE_LOAD_DECLINE;
1461                 }
1462         }
1463
1464         ast_dns_resolver_register(&unbound_resolver);
1465
1466         ast_module_shutdown_ref(ast_module_info->self);
1467
1468         AST_TEST_REGISTER(resolve_sync);
1469         AST_TEST_REGISTER(resolve_async);
1470         AST_TEST_REGISTER(resolve_sync_off_nominal);
1471         AST_TEST_REGISTER(resolve_async_off_nominal);
1472         AST_TEST_REGISTER(resolve_cancel_off_nominal);
1473         AST_TEST_REGISTER(resolve_naptr);
1474         AST_TEST_REGISTER(resolve_srv);
1475
1476         return AST_MODULE_LOAD_SUCCESS;
1477 }
1478
1479 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Unbound DNS Resolver Support",
1480         .support_level = AST_MODULE_SUPPORT_CORE,
1481         .load = load_module,
1482         .unload = unload_module,
1483         .reload = reload_module,
1484         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
1485 );