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