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