[asterisk-commits] mmichelson: branch group/dns r432917 - /team/group/dns/res/
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Fri Mar 13 16:16:36 CDT 2015
Author: mmichelson
Date: Fri Mar 13 16:16:34 2015
New Revision: 432917
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=432917
Log:
Add libunbound nominal resolution tests.
Modified:
team/group/dns/res/res_resolver_unbound.c
Modified: team/group/dns/res/res_resolver_unbound.c
URL: http://svnview.digium.com/svn/asterisk/team/group/dns/res/res_resolver_unbound.c?view=diff&rev=432917&r1=432916&r2=432917
==============================================================================
--- team/group/dns/res/res_resolver_unbound.c (original)
+++ team/group/dns/res/res_resolver_unbound.c Fri Mar 13 16:16:34 2015
@@ -26,6 +26,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <unbound.h>
+#include <arpa/nameser.h>
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
@@ -33,6 +34,7 @@
#include "asterisk/dns_resolver.h"
#include "asterisk/config.h"
#include "asterisk/config_options.h"
+#include "asterisk/test.h"
/*** DOCUMENTATION
<configInfo name="res_resolver_unbound" language="en_US">
@@ -483,6 +485,380 @@
return unbound_config_preapply(aco_pending_config(&cfg_info));
}
+#ifdef TEST_FRAMEWORK
+
+/*!
+ * \brief A DNS record to be used during a test
+ */
+struct dns_record {
+ /*! String representation of the record, as would be found in a file */
+ const char *as_string;
+ /*! The domain this record belongs to */
+ const char *domain;
+ /*! The type of the record */
+ int rr_type;
+ /*! The class of the record */
+ int rr_class;
+ /*! The TTL of the record, in seconds */
+ int ttl;
+ /*! The RDATA of the DNS record */
+ const char *buf;
+ /*! The size of the RDATA */
+ const size_t bufsize;
+ /*! Whether a record checker has visited this record */
+ int visited;
+};
+
+/*!
+ * \brief Resolution function for tests.
+ *
+ * Several tests will have similar setups but will want to make use of a different
+ * means of actually making queries and checking their results. This pluggable
+ * function pointer allows for similar tests to be operated in different ways.
+ *
+ * \param test The test being run
+ * \param domain The domain to look up
+ * \param rr_type The record type to look up
+ * \param rr_class The class of record to look up
+ * \param records All records that exist for the test.
+ * \param num_records Number of records in the records array.
+ *
+ * \retval 0 The test has passed thus far.
+ * \retval -1 The test has failed.
+ */
+typedef int (*resolve_fn)(struct ast_test *test, const char *domain, int rr_type,
+ int rr_class, struct dns_record *records, size_t num_records);
+
+/*!
+ * \brief Pluggable function for running a synchronous query and checking its results
+ */
+static int sync_run(struct ast_test *test, const char *domain, int rr_type,
+ int rr_class, struct dns_record *records, size_t num_records)
+{
+ RAII_VAR(struct ast_dns_result *, result, NULL, ao2_cleanup);
+ const struct ast_dns_record *record;
+ int i;
+
+ /* Start by making sure no records have been visited */
+ for (i = 0; i < num_records; ++i) {
+ records[i].visited = 0;
+ }
+
+ ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
+
+ if (ast_dns_resolve(domain, rr_type, rr_class, &result)) {
+ ast_test_status_update(test, "Failed to perform synchronous resolution of domain %s\n", domain);
+ return -1;
+ }
+
+ if (!result) {
+ ast_test_status_update(test, "Successful synchronous resolution of domain %s gave NULL result\n", domain);
+ return -1;
+ }
+
+ for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
+ int match = 0;
+
+ /* Let's make sure this matches one of our known records */
+ for (i = 0; i < num_records; ++i) {
+ if (ast_dns_record_get_rr_type(record) == records[i].rr_type &&
+ ast_dns_record_get_rr_class(record) == records[i].rr_class &&
+ ast_dns_record_get_ttl(record) == records[i].ttl &&
+ !memcmp(ast_dns_record_get_data(record), records[i].buf, records[i].bufsize)) {
+ match = 1;
+ records[i].visited = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Data required for an asynchronous callback
+ */
+struct async_data {
+ /*! The set of DNS records on a test */
+ struct dns_record *records;
+ /*! The number of DNS records on the test */
+ size_t num_records;
+ /*! Whether an asynchronous query failed */
+ int failed;
+ /*! Indicates the asynchronous query is complete */
+ int complete;
+ ast_mutex_t lock;
+ ast_cond_t cond;
+};
+
+static void async_data_destructor(void *obj)
+{
+ struct async_data *adata = obj;
+
+ ast_mutex_destroy(&adata->lock);
+ ast_cond_destroy(&adata->cond);
+}
+
+static struct async_data *async_data_alloc(struct dns_record *records, size_t num_records)
+{
+ struct async_data *adata;
+
+ adata = ao2_alloc(sizeof(*adata), async_data_destructor);
+ if (!adata) {
+ return NULL;
+ }
+
+ ast_mutex_init(&adata->lock);
+ ast_cond_init(&adata->cond, NULL);
+ adata->records = records;
+ adata->num_records = num_records;
+
+ return adata;
+}
+
+/*!
+ * \brief Callback for asynchronous queries
+ *
+ * This query will check that the records in the DNS result match
+ * records that the test has created. The success or failure of the
+ * query is indicated through the async_data failed field.
+ *
+ * \param query The DNS query that has been resolved
+ */
+static void async_callback(const struct ast_dns_query *query)
+{
+ struct async_data *adata = ast_dns_query_get_data(query);
+ struct ast_dns_result *result = ast_dns_query_get_result(query);
+ const struct ast_dns_record *record;
+ int i;
+
+ if (!result) {
+ adata->failed = -1;
+ goto end;
+ }
+
+ for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
+ int match = 0;
+
+ /* Let's make sure this matches one of our known records */
+ for (i = 0; i < adata->num_records; ++i) {
+ if (ast_dns_record_get_rr_type(record) == adata->records[i].rr_type &&
+ ast_dns_record_get_rr_class(record) == adata->records[i].rr_class &&
+ ast_dns_record_get_ttl(record) == adata->records[i].ttl &&
+ !memcmp(ast_dns_record_get_data(record), adata->records[i].buf, adata->records[i].bufsize)) {
+ match = 1;
+ adata->records[i].visited = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ adata->failed = -1;
+ goto end;
+ }
+ }
+
+end:
+ ast_mutex_lock(&adata->lock);
+ adata->complete = 1;
+ ast_cond_signal(&adata->cond);
+ ast_mutex_unlock(&adata->lock);
+}
+
+/*!
+ * \brief Pluggable function for performing an asynchronous query during a test
+ *
+ * Unlike the synchronous version, this does not check the records, instead leaving
+ * that to be done in the asynchronous callback.
+ */
+static int async_run(struct ast_test *test, const char *domain, int rr_type,
+ int rr_class, struct dns_record *records, size_t num_records)
+{
+ RAII_VAR(struct ast_dns_query *, query, NULL, ao2_cleanup);
+ RAII_VAR(struct async_data *, adata, NULL, ao2_cleanup);
+ int i;
+
+ adata = async_data_alloc(records, num_records);
+ if (!adata) {
+ ast_test_status_update(test, "Unable to allocate data for async query\n");
+ return -1;
+ }
+
+ /* Start by making sure no records have been visited */
+ for (i = 0; i < num_records; ++i) {
+ records[i].visited = 0;
+ }
+
+ ast_test_status_update(test, "Performing DNS query '%s', type %d\n", domain, rr_type);
+
+ query = ast_dns_resolve_async(domain, rr_type, rr_class, async_callback, adata);
+ if (!query) {
+ ast_test_status_update(test, "Failed to perform asynchronous resolution of domain %s\n", domain);
+ return -1;
+ }
+
+ ast_mutex_lock(&adata->lock);
+ while (!adata->complete) {
+ ast_cond_wait(&adata->cond, &adata->lock);
+ }
+ ast_mutex_unlock(&adata->lock);
+
+ if (adata->failed) {
+ ast_test_status_update(test, "Unknown DNS record returned from domain %s\n", domain);
+ }
+ return adata->failed;
+}
+
+/*!
+ * \brief Framework for running a nominal DNS test
+ *
+ * Synchronous and asynchronous tests mostly have the same setup, so this function
+ * serves as a common way to set up both types of tests by accepting a pluggable
+ * function to determine which type of lookup is used
+ *
+ * \param test The test being run
+ * \param runner The method for resolving queries on this test
+ */
+static enum ast_test_result_state nominal_test(struct ast_test *test, resolve_fn runner)
+{
+ RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup);
+ RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup);
+
+ static const size_t V4_SIZE = sizeof(struct in_addr);
+ static const size_t V6_SIZE = sizeof(struct in6_addr);
+
+ static const char *DOMAIN1 = "goose.feathers";
+ static const char *DOMAIN2 = "duck.feathers";
+
+ static const char *ADDR1 = "127.0.0.2";
+ static const char *ADDR2 = "127.0.0.3";
+ static const char *ADDR3 = "::1";
+ static const char *ADDR4 = "127.0.0.4";
+
+ char addr1_buf[V4_SIZE];
+ char addr2_buf[V4_SIZE];
+ char addr3_buf[V6_SIZE];
+ char addr4_buf[V4_SIZE];
+
+ struct dns_record records [] = {
+ { "goose.feathers 12345 IN A 127.0.0.2", DOMAIN1, ns_t_a, ns_c_in, 12345, addr1_buf, V4_SIZE, 0 },
+ { "goose.feathers 12345 IN A 127.0.0.3", DOMAIN1, ns_t_a, ns_c_in, 12345, addr2_buf, V4_SIZE, 0 },
+ { "goose.feathers 12345 IN AAAA ::1", DOMAIN1, ns_t_aaaa, ns_c_in, 12345, addr3_buf, V6_SIZE, 0 },
+ { "duck.feathers 12345 IN A 127.0.0.4", DOMAIN2, ns_t_a, ns_c_in, 12345, addr4_buf, V4_SIZE, 0 },
+ };
+
+ struct {
+ const char *domain;
+ int rr_type;
+ int rr_class;
+ int visited[ARRAY_LEN(records)];
+ } runs [] = {
+ { DOMAIN1, ns_t_a, ns_c_in, { 1, 1, 0, 0 } },
+ { DOMAIN1, ns_t_aaaa, ns_c_in, { 0, 0, 1, 0 } },
+ { DOMAIN2, ns_t_a, ns_c_in, { 0, 0, 0, 1 } },
+ };
+
+ int i;
+ enum ast_test_result_state res = AST_TEST_PASS;
+
+ inet_pton(AF_INET, ADDR1, addr1_buf);
+ inet_pton(AF_INET, ADDR2, addr2_buf);
+ inet_pton(AF_INET6, ADDR3, addr3_buf);
+ inet_pton(AF_INET, ADDR4, addr4_buf);
+
+ cfg = ao2_global_obj_ref(globals);
+ resolver = ao2_bump(cfg->global->state->resolver);
+
+ ub_ctx_zone_add(resolver->context, DOMAIN1, "static");
+ ub_ctx_zone_add(resolver->context, DOMAIN2, "static");
+
+ for (i = 0; i < ARRAY_LEN(records); ++i) {
+ ub_ctx_data_add(resolver->context, records[i].as_string);
+ }
+
+ for (i = 0; i < ARRAY_LEN(runs); ++i) {
+ int j;
+
+ if (runner(test, runs[i].domain, runs[i].rr_type, runs[i].rr_class, records, ARRAY_LEN(records))) {
+ res = AST_TEST_FAIL;
+ goto cleanup;
+ }
+
+ for (j = 0; j < ARRAY_LEN(records); ++j) {
+ if (records[j].visited != runs[i].visited[j]) {
+ ast_test_status_update(test, "DNS results match unexpected records\n");
+ res = AST_TEST_FAIL;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ for (i = 0; i < ARRAY_LEN(records); ++i) {
+ ub_ctx_data_remove(resolver->context, records[i].as_string);
+ }
+ ub_ctx_zone_remove(resolver->context, DOMAIN1);
+ ub_ctx_zone_remove(resolver->context, DOMAIN2);
+
+ return res;
+
+}
+
+AST_TEST_DEFINE(resolve_sync)
+{
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "resolve_sync";
+ info->category = "/res/res_resolver_unbound/";
+ info->summary = "Test nominal synchronous resolution using libunbound\n";
+ info->description = "This test performs the following:\n"
+ "\t* Set two static A records and one static AAAA record on one domain\n"
+ "\t* Set an A record for a second domain\n"
+ "\t* Perform an A record lookup on the first domain\n"
+ "\t* Ensure that both A records are returned and no AAAA record is returned\n"
+ "\t* Perform an AAAA record lookup on the first domain\n"
+ "\t* Ensure that the AAAA record is returned and no A record is returned\n"
+ "\t* Perform an A record lookup on the second domain\n"
+ "\t* Ensure that the A record from the second domain is returned\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return nominal_test(test, sync_run);
+}
+
+AST_TEST_DEFINE(resolve_async)
+{
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "resolve_async";
+ info->category = "/res/res_resolver_unbound/";
+ info->summary = "Test nominal asynchronous resolution using libunbound\n";
+ info->description = "This test performs the following:\n"
+ "\t* Set two static A records and one static AAAA record on one domain\n"
+ "\t* Set an A record for a second domain\n"
+ "\t* Perform an A record lookup on the first domain\n"
+ "\t* Ensure that both A records are returned and no AAAA record is returned\n"
+ "\t* Perform an AAAA record lookup on the first domain\n"
+ "\t* Ensure that the AAAA record is returned and no A record is returned\n"
+ "\t* Perform an A record lookup on the second domain\n"
+ "\t* Ensure that the A record from the second domain is returned\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return nominal_test(test, async_run);
+}
+
+#endif
+
static int reload_module(void)
{
if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
@@ -496,6 +872,9 @@
{
aco_info_destroy(&cfg_info);
ao2_global_obj_release(globals);
+
+ AST_TEST_UNREGISTER(resolve_sync);
+ AST_TEST_UNREGISTER(resolve_async);
return 0;
}
@@ -539,6 +918,9 @@
ast_dns_resolver_register(&unbound_resolver);
ast_module_shutdown_ref(ast_module_info->self);
+
+ AST_TEST_REGISTER(resolve_sync);
+ AST_TEST_REGISTER(resolve_async);
return AST_MODULE_LOAD_SUCCESS;
}
More information about the asterisk-commits
mailing list