res_pjsip: Use reasonable buffer lengths for endpoint identification
[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,
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                 const char *nameserver;
436
437                 it_nameservers = ao2_iterator_init(cfg->global->nameservers, 0);
438                 while ((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                                 ao2_iterator_destroy(&it_nameservers);
445                                 return -1;
446                         }
447                 }
448                 ao2_iterator_destroy(&it_nameservers);
449         }
450
451         if (!strcmp(cfg->global->resolv, "system")) {
452                 res = ub_ctx_resolvconf(cfg->global->state->resolver->context, NULL);
453         } else if (!ast_strlen_zero(cfg->global->resolv)) {
454                 res = ub_ctx_resolvconf(cfg->global->state->resolver->context, (UNBOUND_CHAR *)cfg->global->resolv);
455         }
456
457         if (res) {
458                 ast_log(LOG_ERROR, "Failed to set resolv.conf file to '%s' in unbound resolver: %s\n",
459                         cfg->global->resolv, ub_strerror(res));
460                 return -1;
461         }
462
463         if (!ast_strlen_zero(cfg->global->ta_file)) {
464                 res = ub_ctx_add_ta_file(cfg->global->state->resolver->context, (UNBOUND_CHAR *)cfg->global->ta_file);
465
466                 if (res) {
467                         ast_log(LOG_ERROR, "Failed to set trusted anchor file to '%s' in unbound resolver: %s\n",
468                                 cfg->global->ta_file, ub_strerror(res));
469                         return -1;
470                 }
471         }
472
473         if (unbound_resolver_start(cfg->global->state->resolver)) {
474                 ast_log(LOG_ERROR, "Could not start unbound resolver thread\n");
475                 return -1;
476         }
477
478         return 0;
479 }
480
481 static int unbound_config_apply_default(void)
482 {
483         struct unbound_config *cfg;
484
485         cfg = unbound_config_alloc();
486         if (!cfg) {
487                 ast_log(LOG_ERROR, "Could not create default configuration for unbound resolver\n");
488                 return -1;
489         }
490
491         aco_set_defaults(&global_option, "general", cfg->global);
492
493         if (unbound_config_preapply(cfg)) {
494                 ao2_ref(cfg, -1);
495                 return -1;
496         }
497
498         ast_verb(1, "Starting unbound resolver using default configuration\n");
499
500         ao2_global_obj_replace_unref(globals, cfg);
501         ao2_ref(cfg, -1);
502
503         return 0;
504 }
505
506 static int unbound_config_preapply_callback(void)
507 {
508         return unbound_config_preapply(aco_pending_config(&cfg_info));
509 }
510
511 #ifdef TEST_FRAMEWORK
512
513 #include "asterisk/dns_naptr.h"
514
515 /*!
516  * \brief A DNS record to be used during a test
517  */
518 struct dns_record {
519         /*! String representation of the record, as would be found in a file */
520         const char *as_string;
521         /*! The domain this record belongs to */
522         const char *domain;
523         /*! The type of the record */
524         int rr_type;
525         /*! The class of the record */
526         int rr_class;
527         /*! The TTL of the record, in seconds */
528         int ttl;
529         /*! The RDATA of the DNS record */
530         const char *buf;
531         /*! The size of the RDATA */
532         const size_t bufsize;
533         /*! Whether a record checker has visited this record */
534         int visited;
535 };
536
537 /*!
538  * \brief Resolution function for tests.
539  *
540  * Several tests will have similar setups but will want to make use of a different
541  * means of actually making queries and checking their results. This pluggable
542  * function pointer allows for similar tests to be operated in different ways.
543  *
544  * \param test The test being run
545  * \param domain The domain to look up
546  * \param rr_type The record type to look up
547  * \param rr_class The class of record to look up
548  * \param records All records that exist for the test.
549  * \param num_records Number of records in the records array.
550  *
551  * \retval 0 The test has passed thus far.
552  * \retval -1 The test has failed.
553  */
554 typedef int (*resolve_fn)(struct ast_test *test, const char *domain, int rr_type,
555                 int rr_class, struct dns_record *records, size_t num_records);
556
557 /*!
558  * \brief Pluggable function for running a synchronous query and checking its results
559  */
560 static int nominal_sync_run(struct ast_test *test, const char *domain, int rr_type,
561                 int rr_class, struct dns_record *records, size_t num_records)
562 {
563         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
564         const struct ast_dns_record *record;
565         int i;
566
567         /* Start by making sure no records have been visited */
568         for (i = 0; i < num_records; ++i) {
569                 records[i].visited = 0;
570         }
571
572         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
573
574         if (ast_dns_resolve(domain, rr_type, rr_class, &result)) {
575                 ast_test_status_update(test, "Failed to perform synchronous resolution of domain %s\n", domain);
576                 return -1;
577         }
578
579         if (!result) {
580                 ast_test_status_update(test, "Successful synchronous resolution of domain %s gave NULL result\n", domain);
581                 return -1;
582         }
583
584         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
585                 int match = 0;
586
587                 /* Let's make sure this matches one of our known records */
588                 for (i = 0; i < num_records; ++i) {
589                         if (ast_dns_record_get_rr_type(record) == records[i].rr_type &&
590                                         ast_dns_record_get_rr_class(record) == records[i].rr_class &&
591                                         ast_dns_record_get_ttl(record) == records[i].ttl &&
592                                         !memcmp(ast_dns_record_get_data(record), records[i].buf, records[i].bufsize)) {
593                                 match = 1;
594                                 records[i].visited = 1;
595                                 break;
596                         }
597                 }
598
599                 if (!match) {
600                         ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
601                         return -1;
602                 }
603         }
604
605         return 0;
606 }
607
608 /*!
609  * \brief Data required for an asynchronous callback
610  */
611 struct async_data {
612         /*! The set of DNS records on a test */
613         struct dns_record *records;
614         /*! The number of DNS records on the test */
615         size_t num_records;
616         /*! Whether an asynchronous query failed */
617         int failed;
618         /*! Indicates the asynchronous query is complete */
619         int complete;
620         ast_mutex_t lock;
621         ast_cond_t cond;
622 };
623
624 static void async_data_destructor(void *obj)
625 {
626         struct async_data *adata = obj;
627
628         ast_mutex_destroy(&adata->lock);
629         ast_cond_destroy(&adata->cond);
630 }
631
632 static struct async_data *async_data_alloc(struct dns_record *records, size_t num_records)
633 {
634         struct async_data *adata;
635
636         adata = ao2_alloc(sizeof(*adata), async_data_destructor);
637         if (!adata) {
638                 return NULL;
639         }
640
641         ast_mutex_init(&adata->lock);
642         ast_cond_init(&adata->cond, NULL);
643         adata->records = records;
644         adata->num_records = num_records;
645
646         return adata;
647 }
648
649 /*!
650  * \brief Callback for asynchronous queries
651  *
652  * This query will check that the records in the DNS result match
653  * records that the test has created. The success or failure of the
654  * query is indicated through the async_data failed field.
655  *
656  * \param query The DNS query that has been resolved
657  */
658 static void async_callback(const struct ast_dns_query *query)
659 {
660         struct async_data *adata = ast_dns_query_get_data(query);
661         struct ast_dns_result *result = ast_dns_query_get_result(query);
662         const struct ast_dns_record *record;
663         int i;
664
665         if (!result) {
666                 adata->failed = -1;
667                 goto end;
668         }
669
670         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
671                 int match = 0;
672
673                 /* Let's make sure this matches one of our known records */
674                 for (i = 0; i < adata->num_records; ++i) {
675                         if (ast_dns_record_get_rr_type(record) == adata->records[i].rr_type &&
676                                         ast_dns_record_get_rr_class(record) == adata->records[i].rr_class &&
677                                         ast_dns_record_get_ttl(record) == adata->records[i].ttl &&
678                                         !memcmp(ast_dns_record_get_data(record), adata->records[i].buf, adata->records[i].bufsize)) {
679                                 match = 1;
680                                 adata->records[i].visited = 1;
681                                 break;
682                         }
683                 }
684
685                 if (!match) {
686                         adata->failed = -1;
687                         goto end;
688                 }
689         }
690
691 end:
692         ast_mutex_lock(&adata->lock);
693         adata->complete = 1;
694         ast_cond_signal(&adata->cond);
695         ast_mutex_unlock(&adata->lock);
696 }
697
698 /*!
699  * \brief Pluggable function for performing an asynchronous query during a test
700  *
701  * Unlike the synchronous version, this does not check the records, instead leaving
702  * that to be done in the asynchronous callback.
703  */
704 static int nominal_async_run(struct ast_test *test, const char *domain, int rr_type,
705                 int rr_class, struct dns_record *records, size_t num_records)
706 {
707         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
708         RAII_VAR(struct async_data *, adata, NULL, ao2_cleanup);
709         int i;
710
711         adata = async_data_alloc(records, num_records);
712         if (!adata) {
713                 ast_test_status_update(test, "Unable to allocate data for async query\n");
714                 return -1;
715         }
716
717         /* Start by making sure no records have been visited */
718         for (i = 0; i < num_records; ++i) {
719                 records[i].visited = 0;
720         }
721
722         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
723
724         active = ast_dns_resolve_async(domain, rr_type, rr_class, async_callback, adata);
725         if (!active) {
726                 ast_test_status_update(test, "Failed to perform asynchronous resolution of domain %s\n", domain);
727                 return -1;
728         }
729
730         ast_mutex_lock(&adata->lock);
731         while (!adata->complete) {
732                 ast_cond_wait(&adata->cond, &adata->lock);
733         }
734         ast_mutex_unlock(&adata->lock);
735
736         if (adata->failed) {
737                 ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
738         }
739         return adata->failed;
740 }
741
742 /*!
743  * \brief Framework for running a nominal DNS test
744  *
745  * Synchronous and asynchronous tests mostly have the same setup, so this function
746  * serves as a common way to set up both types of tests by accepting a pluggable
747  * function to determine which type of lookup is used
748  *
749  * \param test The test being run
750  * \param runner The method for resolving queries on this test
751  */
752 static enum ast_test_result_state nominal_test(struct ast_test *test, resolve_fn runner)
753 {
754         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
755         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
756
757         static const size_t V4_SIZE = sizeof(struct in_addr);
758         static const size_t V6_SIZE = sizeof(struct in6_addr);
759
760         static UNBOUND_CHAR *DOMAIN1 = "goose.feathers";
761         static UNBOUND_CHAR *DOMAIN2 = "duck.feathers";
762
763         static UNBOUND_CHAR *ADDR1 = "127.0.0.2";
764         static UNBOUND_CHAR *ADDR2 = "127.0.0.3";
765         static UNBOUND_CHAR *ADDR3 = "::1";
766         static UNBOUND_CHAR *ADDR4 = "127.0.0.4";
767
768         char addr1_buf[V4_SIZE];
769         char addr2_buf[V4_SIZE];
770         char addr3_buf[V6_SIZE];
771         char addr4_buf[V4_SIZE];
772
773         struct dns_record records [] = {
774                 { "goose.feathers 12345 IN A 127.0.0.2", DOMAIN1, ns_t_a,    ns_c_in, 12345, addr1_buf, V4_SIZE, 0 },
775                 { "goose.feathers 12345 IN A 127.0.0.3", DOMAIN1, ns_t_a,    ns_c_in, 12345, addr2_buf, V4_SIZE, 0 },
776                 { "goose.feathers 12345 IN AAAA ::1",    DOMAIN1, ns_t_aaaa, ns_c_in, 12345, addr3_buf, V6_SIZE, 0 },
777                 { "duck.feathers 12345 IN A 127.0.0.4",  DOMAIN2, ns_t_a,    ns_c_in, 12345, addr4_buf, V4_SIZE, 0 },
778         };
779
780         struct {
781                 const char *domain;
782                 int rr_type;
783                 int rr_class;
784                 int visited[ARRAY_LEN(records)];
785         } runs [] = {
786                 { DOMAIN1, ns_t_a,    ns_c_in, { 1, 1, 0, 0 } },
787                 { DOMAIN1, ns_t_aaaa, ns_c_in, { 0, 0, 1, 0 } },
788                 { DOMAIN2, ns_t_a,    ns_c_in, { 0, 0, 0, 1 } },
789         };
790
791         int i;
792         enum ast_test_result_state res = AST_TEST_PASS;
793
794         inet_pton(AF_INET,  ADDR1, addr1_buf);
795         inet_pton(AF_INET,  ADDR2, addr2_buf);
796         inet_pton(AF_INET6,  ADDR3, addr3_buf);
797         inet_pton(AF_INET, ADDR4, addr4_buf);
798
799         cfg = ao2_global_obj_ref(globals);
800         resolver = ao2_bump(cfg->global->state->resolver);
801
802         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
803         ub_ctx_zone_add(resolver->context, DOMAIN2, "static");
804
805         for (i = 0; i < ARRAY_LEN(records); ++i) {
806                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
807         }
808
809         for (i = 0; i < ARRAY_LEN(runs); ++i) {
810                 int j;
811
812                 if (runner(test, runs[i].domain, runs[i].rr_type, runs[i].rr_class, records, ARRAY_LEN(records))) {
813                         res = AST_TEST_FAIL;
814                         goto cleanup;
815                 }
816
817                 for (j = 0; j < ARRAY_LEN(records); ++j) {
818                         if (records[j].visited != runs[i].visited[j]) {
819                                 ast_test_status_update(test, "DNS results match unexpected records\n");
820                                 res = AST_TEST_FAIL;
821                                 goto cleanup;
822                         }
823                 }
824         }
825
826 cleanup:
827         for (i = 0; i < ARRAY_LEN(records); ++i) {
828                 ub_ctx_data_remove(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
829         }
830         ub_ctx_zone_remove(resolver->context, DOMAIN1);
831         ub_ctx_zone_remove(resolver->context, DOMAIN2);
832
833         return res;
834 }
835
836 AST_TEST_DEFINE(resolve_sync)
837 {
838
839         switch (cmd) {
840         case TEST_INIT:
841                 info->name = "resolve_sync";
842                 info->category = "/res/res_resolver_unbound/";
843                 info->summary = "Test nominal synchronous resolution using libunbound";
844                 info->description = "This test performs the following:\n"
845                         "\t* Set two static A records and one static AAAA record on one domain\n"
846                         "\t* Set an A record for a second domain\n"
847                         "\t* Perform an A record lookup on the first domain\n"
848                         "\t* Ensure that both A records are returned and no AAAA record is returned\n"
849                         "\t* Perform an AAAA record lookup on the first domain\n"
850                         "\t* Ensure that the AAAA record is returned and no A record is returned\n"
851                         "\t* Perform an A record lookup on the second domain\n"
852                         "\t* Ensure that the A record from the second domain is returned";
853                 return AST_TEST_NOT_RUN;
854         case TEST_EXECUTE:
855                 break;
856         }
857
858         return nominal_test(test, nominal_sync_run);
859 }
860
861 AST_TEST_DEFINE(resolve_async)
862 {
863         switch (cmd) {
864         case TEST_INIT:
865                 info->name = "resolve_async";
866                 info->category = "/res/res_resolver_unbound/";
867                 info->summary = "Test nominal asynchronous resolution using libunbound";
868                 info->description = "This test performs the following:\n"
869                         "\t* Set two static A records and one static AAAA record on one domain\n"
870                         "\t* Set an A record for a second domain\n"
871                         "\t* Perform an A record lookup on the first domain\n"
872                         "\t* Ensure that both A records are returned and no AAAA record is returned\n"
873                         "\t* Perform an AAAA record lookup on the first domain\n"
874                         "\t* Ensure that the AAAA record is returned and no A record is returned\n"
875                         "\t* Perform an A record lookup on the second domain\n"
876                         "\t* Ensure that the A record from the second domain is returned";
877                 return AST_TEST_NOT_RUN;
878         case TEST_EXECUTE:
879                 break;
880         }
881
882         return nominal_test(test, nominal_async_run);
883 }
884
885 typedef int (*off_nominal_resolve_fn)(struct ast_test *test, const char *domain, int rr_type,
886                 int rr_class, int expected_rcode);
887
888 static int off_nominal_sync_run(struct ast_test *test, const char *domain, int rr_type,
889                 int rr_class, int expected_rcode)
890 {
891         struct ast_dns_result *result;
892         int res = 0;
893
894         if (ast_dns_resolve(domain, rr_type, rr_class, &result)) {
895                 ast_test_status_update(test, "Failed to perform resolution :(\n");
896                 return -1;
897         }
898
899         if (!result) {
900                 ast_test_status_update(test, "Resolution returned no result\n");
901                 return -1;
902         }
903
904         if (ast_dns_result_get_rcode(result) != expected_rcode) {
905                 ast_test_status_update(test, "Unexpected rcode from DNS resolution\n");
906                 res = -1;
907         }
908
909         if (ast_dns_result_get_records(result)) {
910                 ast_test_status_update(test, "DNS resolution returned records unexpectedly\n");
911                 res = -1;
912         }
913
914         ast_dns_result_free(result);
915         return res;
916 }
917
918 /*!
919  * \brief User data for off-nominal async resolution test
920  */
921 struct off_nominal_async_data {
922         /*! The DNS result's expected rcode */
923         int expected_rcode;
924         /*! Whether an asynchronous query failed */
925         int failed;
926         /*! Indicates the asynchronous query is complete */
927         int complete;
928         ast_mutex_t lock;
929         ast_cond_t cond;
930 };
931
932 static void off_nominal_async_data_destructor(void *obj)
933 {
934         struct off_nominal_async_data *adata = obj;
935
936         ast_mutex_destroy(&adata->lock);
937         ast_cond_destroy(&adata->cond);
938 }
939
940 static struct off_nominal_async_data *off_nominal_async_data_alloc(int expected_rcode)
941 {
942         struct off_nominal_async_data *adata;
943
944         adata = ao2_alloc(sizeof(*adata), off_nominal_async_data_destructor);
945         if (!adata) {
946                 return NULL;
947         }
948
949         ast_mutex_init(&adata->lock);
950         ast_cond_init(&adata->cond, NULL);
951
952         adata->expected_rcode = expected_rcode;
953
954         return adata;
955 }
956
957 /*!
958  * \brief Async callback for off-nominal async test
959  *
960  * This test ensures that there is a result present on the query, then it checks
961  * that the rcode on the result is the expected value and that there are no
962  * records on the result.
963  *
964  * Once completed, the testing thread is signaled that the async query has
965  * completed.
966  */
967 static void off_nominal_async_callback(const struct ast_dns_query *query)
968 {
969         struct off_nominal_async_data *adata = ast_dns_query_get_data(query);
970         struct ast_dns_result *result = ast_dns_query_get_result(query);
971
972         if (!result) {
973                 adata->failed = -1;
974                 goto end;
975         }
976
977         if (ast_dns_result_get_rcode(result) != adata->expected_rcode) {
978                 adata->failed = -1;
979         }
980
981         if (ast_dns_result_get_records(result)) {
982                 adata->failed = -1;
983         }
984
985 end:
986         ast_mutex_lock(&adata->lock);
987         adata->complete = 1;
988         ast_cond_signal(&adata->cond);
989         ast_mutex_unlock(&adata->lock);
990 }
991
992 static int off_nominal_async_run(struct ast_test *test, const char *domain, int rr_type,
993                 int rr_class, int expected_rcode)
994 {
995         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
996         RAII_VAR(struct off_nominal_async_data *, adata, NULL, ao2_cleanup);
997
998         adata = off_nominal_async_data_alloc(expected_rcode);
999         if (!adata) {
1000                 ast_test_status_update(test, "Unable to allocate data for async query\n");
1001                 return -1;
1002         }
1003
1004         ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
1005
1006         active = ast_dns_resolve_async(domain, rr_type, rr_class, off_nominal_async_callback, adata);
1007         if (!active) {
1008                 ast_test_status_update(test, "Failed to perform asynchronous resolution of domain %s\n", domain);
1009                 return -1;
1010         }
1011
1012         ast_mutex_lock(&adata->lock);
1013         while (!adata->complete) {
1014                 ast_cond_wait(&adata->cond, &adata->lock);
1015         }
1016         ast_mutex_unlock(&adata->lock);
1017
1018         if (adata->failed) {
1019                 ast_test_status_update(test, "Asynchronous resolution failure %s\n", domain);
1020         }
1021         return adata->failed;
1022 }
1023
1024 static enum ast_test_result_state off_nominal_test(struct ast_test *test,
1025                 off_nominal_resolve_fn runner)
1026 {
1027         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1028         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1029
1030         static const size_t V4_SIZE = sizeof(struct in_addr);
1031
1032         static UNBOUND_CHAR *DOMAIN1 = "goose.feathers";
1033         static UNBOUND_CHAR *DOMAIN2 = "duck.feathers";
1034
1035         static UNBOUND_CHAR *ADDR1 = "127.0.0.2";
1036
1037         char addr1_buf[V4_SIZE];
1038
1039         struct dns_record records [] = {
1040                 { "goose.feathers 12345 IN A 127.0.0.2", DOMAIN1, ns_t_a, ns_c_in, 12345, addr1_buf, V4_SIZE, 0, },
1041         };
1042
1043         int i;
1044         enum ast_test_result_state res = AST_TEST_PASS;
1045
1046         struct {
1047                 const char *domain;
1048                 int rr_type;
1049                 int rr_class;
1050                 int rcode;
1051         } runs [] = {
1052                 { DOMAIN2, ns_t_a,    ns_c_in, ns_r_nxdomain },
1053                 { DOMAIN1, ns_t_aaaa, ns_c_in, ns_r_noerror },
1054                 { DOMAIN1, ns_t_a,    ns_c_chaos, ns_r_refused },
1055         };
1056
1057         inet_pton(AF_INET,  ADDR1, addr1_buf);
1058
1059         cfg = ao2_global_obj_ref(globals);
1060         resolver = ao2_bump(cfg->global->state->resolver);
1061
1062         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1063         ub_ctx_zone_add(resolver->context, DOMAIN2, "static");
1064
1065         for (i = 0; i < ARRAY_LEN(records); ++i) {
1066                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].as_string);
1067         }
1068
1069         for (i = 0; i < ARRAY_LEN(runs); ++i) {
1070                 if (runner(test, runs[i].domain, runs[i].rr_type, runs[i].rr_class, runs[i].rcode)) {
1071                         res = AST_TEST_FAIL;
1072                 }
1073         }
1074
1075         return res;
1076 }
1077
1078 AST_TEST_DEFINE(resolve_sync_off_nominal)
1079 {
1080         switch (cmd) {
1081         case TEST_INIT:
1082                 info->name = "resolve_sync_off_nominal";
1083                 info->category = "/res/res_resolver_unbound/";
1084                 info->summary = "Test off-nominal synchronous resolution using libunbound";
1085                 info->description = "This test performs the following:\n"
1086                         "\t* Attempt a lookup of a non-existent domain\n"
1087                         "\t* Attempt a lookup of a AAAA record on a domain that contains only A records\n"
1088                         "\t* Attempt a lookup of an A record on Chaos-net";
1089                 return AST_TEST_NOT_RUN;
1090         case TEST_EXECUTE:
1091                 break;
1092         }
1093
1094         return off_nominal_test(test, off_nominal_sync_run);
1095 }
1096
1097 AST_TEST_DEFINE(resolve_async_off_nominal)
1098 {
1099         switch (cmd) {
1100         case TEST_INIT:
1101                 info->name = "resolve_async_off_nominal";
1102                 info->category = "/res/res_resolver_unbound/";
1103                 info->summary = "Test off-nominal synchronous resolution using libunbound";
1104                 info->description = "This test performs the following:\n"
1105                         "\t* Attempt a lookup of a non-existent domain\n"
1106                         "\t* Attempt a lookup of a AAAA record on a domain that contains only A records\n"
1107                         "\t* Attempt a lookup of an A record on Chaos-net";
1108                 return AST_TEST_NOT_RUN;
1109         case TEST_EXECUTE:
1110                 break;
1111         }
1112
1113         return off_nominal_test(test, off_nominal_async_run);
1114 }
1115
1116 /*!
1117  * \brief Minimal data required to signal the completion of an async resolve
1118  */
1119 struct async_minimal_data {
1120         int complete;
1121         ast_mutex_t lock;
1122         ast_cond_t cond;
1123 };
1124
1125 static void async_minimal_data_destructor(void *obj)
1126 {
1127         struct async_minimal_data *adata = obj;
1128
1129         ast_mutex_destroy(&adata->lock);
1130         ast_cond_destroy(&adata->cond);
1131 }
1132
1133 static struct async_minimal_data *async_minimal_data_alloc(void)
1134 {
1135         struct async_minimal_data *adata;
1136
1137         adata = ao2_alloc(sizeof(*adata), async_minimal_data_destructor);
1138         if (!adata) {
1139                 return NULL;
1140         }
1141
1142         ast_mutex_init(&adata->lock);
1143         ast_cond_init(&adata->cond, NULL);
1144
1145         return adata;
1146 }
1147
1148 /*!
1149  * \brief Async callback for off-nominal cancellation test.
1150  *
1151  * This simply signals the testing thread that the query completed
1152  */
1153 static void minimal_callback(const struct ast_dns_query *query)
1154 {
1155         struct async_minimal_data *adata = ast_dns_query_get_data(query);
1156
1157         ast_mutex_lock(&adata->lock);
1158         adata->complete = 1;
1159         ast_cond_signal(&adata->cond);
1160         ast_mutex_unlock(&adata->lock);
1161 }
1162
1163 AST_TEST_DEFINE(resolve_cancel_off_nominal)
1164 {
1165         RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
1166         RAII_VAR(struct async_minimal_data *, adata, NULL, ao2_cleanup);
1167
1168         switch (cmd) {
1169         case TEST_INIT:
1170                 info->name = "resolve_cancel_off_nominal";
1171                 info->category = "/res/res_resolver_unbound/";
1172                 info->summary = "Off nominal cancellation test using libunbound";
1173                 info->description = "This test does the following:\n"
1174                         "\t* Perform an asynchronous query\n"
1175                         "\t* Once the query has completed, attempt to cancel it";
1176                 return AST_TEST_NOT_RUN;
1177         case TEST_EXECUTE:
1178                 break;
1179         }
1180
1181         adata = async_minimal_data_alloc();
1182         if (!adata) {
1183                 ast_test_status_update(test, "Failed to allocate necessary data for test\n");
1184                 return AST_TEST_FAIL;
1185         }
1186
1187         active = ast_dns_resolve_async("crunchy.peanut.butter", ns_t_a, ns_c_in, minimal_callback, adata);
1188         if (!active) {
1189                 ast_test_status_update(test, "Failed to perform asynchronous query\n");
1190                 return AST_TEST_FAIL;
1191         }
1192
1193         /* Wait for async query to complete */
1194         ast_mutex_lock(&adata->lock);
1195         while (!adata->complete) {
1196                 ast_cond_wait(&adata->cond, &adata->lock);
1197         }
1198         ast_mutex_unlock(&adata->lock);
1199
1200         if (!ast_dns_resolve_cancel(active)) {
1201                 ast_test_status_update(test, "Successfully canceled completed query\n");
1202                 return AST_TEST_FAIL;
1203         }
1204
1205         return AST_TEST_PASS;
1206 }
1207
1208 AST_TEST_DEFINE(resolve_naptr)
1209 {
1210         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1211         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1212         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
1213
1214         const struct ast_dns_record *record;
1215
1216         static char * DOMAIN1 = "goose.feathers";
1217         int i;
1218         enum ast_test_result_state res = AST_TEST_PASS;
1219
1220         struct naptr_record {
1221                 const char *zone_entry;
1222                 uint16_t order;
1223                 uint16_t preference;
1224                 const char *flags;
1225                 const char *services;
1226                 const char *regexp;
1227                 const char *replacement;
1228                 int visited;
1229         } records [] = {
1230                 { "goose.feathers 12345 IN NAPTR 100 100 A SIP+D2U \"\" goose.down", 100, 100, "A", "SIP+D2U", "", "goose.down", 0},
1231                 { "goose.feathers 12345 IN NAPTR 100 200 A SIP+D2T \"\" duck.down", 100, 200, "A", "SIP+D2T", "", "duck.down", 0},
1232                 { "goose.feathers 12345 IN NAPTR 200 100 A SIPS+D2U \"\" pheasant.down", 200, 100, "A", "SIPS+D2U", "", "pheasant.down", 0},
1233                 { "goose.feathers 12345 IN NAPTR 200 200 A SIPS+D2T \"\" platypus.fur", 200, 200, "A", "SIPS+D2T", "", "platypus.fur", 0},
1234         };
1235
1236         switch (cmd) {
1237         case TEST_INIT:
1238                 info->name = "resolve_naptr";
1239                 info->category = "/res/res_resolver_unbound/";
1240                 info->summary = "Attempt resolution of NAPTR record";
1241                 info->description = "This test performs a NAPTR lookup and ensures that\n"
1242                         "the returned record has the appropriate values set";
1243                 return AST_TEST_NOT_RUN;
1244         case TEST_EXECUTE:
1245                 break;
1246         }
1247
1248         cfg = ao2_global_obj_ref(globals);
1249         resolver = ao2_bump(cfg->global->state->resolver);
1250
1251         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1252
1253         for (i = 0; i < ARRAY_LEN(records); ++i) {
1254                 ub_ctx_data_add(resolver->context, (UNBOUND_CHAR *)records[i].zone_entry);
1255         }
1256
1257         if (ast_dns_resolve(DOMAIN1, ns_t_naptr, ns_c_in, &result)) {
1258                 ast_test_status_update(test, "Failed to resolve domain\n");
1259                 return AST_TEST_FAIL;
1260         }
1261
1262         if (!result) {
1263                 ast_test_status_update(test, "Successful resolution set a NULL result\n");
1264                 return AST_TEST_FAIL;
1265         }
1266
1267         record = ast_dns_result_get_records(result);
1268         if (!record) {
1269                 ast_test_status_update(test, "Failed to get any DNS records from the result\n");
1270                 return AST_TEST_FAIL;
1271         }
1272
1273         i = 0;
1274         for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
1275                 if (ast_dns_naptr_get_order(record) != records[i].order) {
1276                         ast_test_status_update(test, "Expected order %hu, got order %hu from NAPTR record\n",
1277                                         records[i].order, ast_dns_naptr_get_order(record));
1278                         res = AST_TEST_FAIL;
1279                 }
1280                 if (ast_dns_naptr_get_preference(record) != records[i].preference) {
1281                         ast_test_status_update(test, "Expected preference %hu, got preference %hu from NAPTR record\n",
1282                                         records[i].preference, ast_dns_naptr_get_preference(record));
1283                         res = AST_TEST_FAIL;
1284                 }
1285                 if (strcmp(ast_dns_naptr_get_flags(record), records[i].flags)) {
1286                         ast_test_status_update(test, "Expected flags %s, got flags %s from NAPTR record\n",
1287                                         records[i].flags, ast_dns_naptr_get_flags(record));
1288                         res = AST_TEST_FAIL;
1289                 }
1290                 if (strcmp(ast_dns_naptr_get_service(record), records[i].services)) {
1291                         ast_test_status_update(test, "Expected services %s, got services %s from NAPTR record\n",
1292                                         records[i].services, ast_dns_naptr_get_service(record));
1293                         res = AST_TEST_FAIL;
1294                 }
1295                 if (strcmp(ast_dns_naptr_get_regexp(record), records[i].regexp)) {
1296                         ast_test_status_update(test, "Expected regexp %s, got regexp %s from NAPTR record\n",
1297                                         records[i].regexp, ast_dns_naptr_get_regexp(record));
1298                         res = AST_TEST_FAIL;
1299                 }
1300                 if (strcmp(ast_dns_naptr_get_replacement(record), records[i].replacement)) {
1301                         ast_test_status_update(test, "Expected replacement %s, got replacement %s from NAPTR record\n",
1302                                         records[i].replacement, ast_dns_naptr_get_replacement(record));
1303                         res = AST_TEST_FAIL;
1304                 }
1305                 records[i].visited = 1;
1306                 ++i;
1307         }
1308
1309         if (i != ARRAY_LEN(records)) {
1310                 ast_test_status_update(test, "Unexpected number of records visited\n");
1311                 res = AST_TEST_FAIL;
1312         }
1313
1314         for (i = 0; i < ARRAY_LEN(records); ++i) {
1315                 if (!records[i].visited) {
1316                         ast_test_status_update(test, "Did not visit all expected NAPTR records\n");
1317                         res = AST_TEST_FAIL;
1318                 }
1319         }
1320
1321         return res;
1322
1323 }
1324
1325 AST_TEST_DEFINE(resolve_srv)
1326 {
1327         RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
1328         RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
1329         RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
1330         const struct ast_dns_record *record;
1331         static UNBOUND_CHAR *DOMAIN1 = "taco.bananas";
1332         static UNBOUND_CHAR *DOMAIN1_SRV = "taco.bananas 12345 IN SRV 10 20 5060 sip.taco.bananas";
1333         enum ast_test_result_state res = AST_TEST_PASS;
1334
1335         switch (cmd) {
1336         case TEST_INIT:
1337                 info->name = "resolve_srv";
1338                 info->category = "/res/res_resolver_unbound/";
1339                 info->summary = "Test synchronous SRV resolution using libunbound";
1340                 info->description = "This test performs the following:\n"
1341                         "\t* Set one SRV record on one domain\n"
1342                         "\t* Perform an SRV lookup on the domain\n"
1343                         "\t* Ensure that the SRV record returned matches the expected value";
1344                 return AST_TEST_NOT_RUN;
1345         case TEST_EXECUTE:
1346                 break;
1347         }
1348
1349         cfg = ao2_global_obj_ref(globals);
1350         resolver = ao2_bump(cfg->global->state->resolver);
1351
1352         ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
1353         ub_ctx_data_add(resolver->context, DOMAIN1_SRV);
1354
1355         if (ast_dns_resolve(DOMAIN1, ns_t_srv, ns_c_in, &result)) {
1356                 ast_test_status_update(test, "Failed to synchronously resolve SRV for domain '%s'\n", DOMAIN1);
1357                 res = AST_TEST_FAIL;
1358                 goto cleanup;
1359         }
1360
1361         record = ast_dns_result_get_records(result);
1362         if (ast_dns_srv_get_priority(record) != 10) {
1363                 ast_test_status_update(test, "SRV Record returned priority '%d' when we expected 10\n", ast_dns_srv_get_priority(record));
1364                 res = AST_TEST_FAIL;
1365                 goto cleanup;
1366         }
1367
1368         if (ast_dns_srv_get_weight(record) != 20) {
1369                 ast_test_status_update(test, "SRV Record returned weight '%d' when we expected 20\n", ast_dns_srv_get_weight(record));
1370                 res = AST_TEST_FAIL;
1371                 goto cleanup;
1372         }
1373
1374         if (ast_dns_srv_get_port(record) != 5060) {
1375                 ast_test_status_update(test, "SRV Record returned port '%d' when we expected 5060\n", ast_dns_srv_get_port(record));
1376                 res = AST_TEST_FAIL;
1377                 goto cleanup;
1378         }
1379
1380         if (strcmp(ast_dns_srv_get_host(record), "sip.taco.bananas")) {
1381                 ast_test_status_update(test, "SRV Record returned host '%s' when we expected sip.taco.bananas\n", ast_dns_srv_get_host(record));
1382                 res = AST_TEST_FAIL;
1383                 goto cleanup;
1384         }
1385
1386 cleanup:
1387         ub_ctx_data_remove(resolver->context, DOMAIN1_SRV);
1388         ub_ctx_zone_remove(resolver->context, DOMAIN1);
1389
1390         return res;
1391 }
1392 #endif
1393
1394 static int reload_module(void)
1395 {
1396         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
1397                 return AST_MODULE_RELOAD_ERROR;
1398         }
1399
1400         return 0;
1401 }
1402
1403 static int unload_module(void)
1404 {
1405         aco_info_destroy(&cfg_info);
1406         ao2_global_obj_release(globals);
1407
1408         AST_TEST_UNREGISTER(resolve_sync);
1409         AST_TEST_UNREGISTER(resolve_async);
1410         AST_TEST_UNREGISTER(resolve_sync_off_nominal);
1411         AST_TEST_UNREGISTER(resolve_sync_off_nominal);
1412         AST_TEST_UNREGISTER(resolve_cancel_off_nominal);
1413         AST_TEST_UNREGISTER(resolve_naptr);
1414         AST_TEST_UNREGISTER(resolve_srv);
1415         return 0;
1416 }
1417
1418 static int custom_nameserver_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
1419 {
1420         struct unbound_global_config *global = obj;
1421
1422         if (!global->nameservers) {
1423                 global->nameservers = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1);
1424                 if (!global->nameservers) {
1425                         return -1;
1426                 }
1427         }
1428
1429         return ast_str_container_add(global->nameservers, var->value);
1430 }
1431
1432 static int load_module(void)
1433 {
1434         struct ast_config *cfg;
1435         struct ast_flags cfg_flags = { 0, };
1436
1437         if (aco_info_init(&cfg_info)) {
1438                 return AST_MODULE_LOAD_DECLINE;
1439         }
1440
1441         aco_option_register(&cfg_info, "hosts", ACO_EXACT, global_options, "system", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, hosts));
1442         aco_option_register(&cfg_info, "resolv", ACO_EXACT, global_options, "system", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, resolv));
1443         aco_option_register_custom(&cfg_info, "nameserver", ACO_EXACT, global_options, "", custom_nameserver_handler, 0);
1444         aco_option_register(&cfg_info, "debug", ACO_EXACT, global_options, "0", OPT_UINT_T, 0, FLDSET(struct unbound_global_config, debug));
1445         aco_option_register(&cfg_info, "ta_file", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, ta_file));
1446
1447         /* This purposely checks for a configuration file so we don't output an error message in ACO if one is not present */
1448         cfg = ast_config_load(resolver_unbound_conf.filename, cfg_flags);
1449         if (!cfg) {
1450                 if (unbound_config_apply_default()) {
1451                         unload_module();
1452                         return AST_MODULE_LOAD_DECLINE;
1453                 }
1454         } else {
1455                 ast_config_destroy(cfg);
1456                 if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
1457                         unload_module();
1458                         return AST_MODULE_LOAD_DECLINE;
1459                 }
1460         }
1461
1462         ast_dns_resolver_register(&unbound_resolver);
1463
1464         ast_module_shutdown_ref(ast_module_info->self);
1465
1466         AST_TEST_REGISTER(resolve_sync);
1467         AST_TEST_REGISTER(resolve_async);
1468         AST_TEST_REGISTER(resolve_sync_off_nominal);
1469         AST_TEST_REGISTER(resolve_async_off_nominal);
1470         AST_TEST_REGISTER(resolve_cancel_off_nominal);
1471         AST_TEST_REGISTER(resolve_naptr);
1472         AST_TEST_REGISTER(resolve_srv);
1473
1474         return AST_MODULE_LOAD_SUCCESS;
1475 }
1476
1477 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Unbound DNS Resolver Support",
1478         .support_level = AST_MODULE_SUPPORT_CORE,
1479         .load = load_module,
1480         .unload = unload_module,
1481         .reload = reload_module,
1482         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
1483 );