[svn-commits] mmichelson: branch group/dns r432917 - /team/group/dns/res/

SVN commits to the Digium repositories svn-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 svn-commits mailing list