mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-26 22:30:28 +00:00 
			
		
		
		
	Fix the tests for DNS to use older style nameser.h as in ASTERISK-26608. Tested on: OpenBSD 6.0, Debian 8 ASTERISK-26647 #close Change-Id: I285913c44202537c04b3ed09c015efa6e5f9052d
		
			
				
	
	
		
			1338 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1338 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2015, Mark Michelson
 | |
|  *
 | |
|  * Mark Michelson <mmichelson@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*** MODULEINFO
 | |
| 	<depend>TEST_FRAMEWORK</depend>
 | |
| 	<support_level>core</support_level>
 | |
|  ***/
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include <arpa/nameser.h>
 | |
| #include <arpa/inet.h>
 | |
| 
 | |
| #include "asterisk/test.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/dns_core.h"
 | |
| #include "asterisk/dns_resolver.h"
 | |
| #include "asterisk/dns_internal.h"
 | |
| 
 | |
| /* Used when a stub is needed for certain tests */
 | |
| static int stub_resolve(struct ast_dns_query *query)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* Used when a stub is needed for certain tests */
 | |
| static int stub_cancel(struct ast_dns_query *query)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_register_unregister)
 | |
| {
 | |
| 	struct ast_dns_resolver cool_guy_resolver = {
 | |
| 		.name = "A snake that swallowed a deer",
 | |
| 		.priority = 19890504,
 | |
| 		.resolve = stub_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_register_unregister";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test nominal resolver registration and unregistration";
 | |
| 		info->description =
 | |
| 			"The test performs the following steps:\n"
 | |
| 			"\t* Register a valid resolver.\n"
 | |
| 			"\t* Unregister the resolver.\n"
 | |
| 			"If either step fails, the test fails";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&cool_guy_resolver)) {
 | |
| 		ast_test_status_update(test, "Unable to register a perfectly good resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&cool_guy_resolver);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_register_off_nominal)
 | |
| {
 | |
| 	struct ast_dns_resolver valid = {
 | |
| 		.name = "valid",
 | |
| 		.resolve = stub_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	struct ast_dns_resolver incomplete1 = {
 | |
| 		.name = NULL,
 | |
| 		.resolve = stub_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	struct ast_dns_resolver incomplete2 = {
 | |
| 		.name = "incomplete2",
 | |
| 		.resolve = NULL,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	struct ast_dns_resolver incomplete3 = {
 | |
| 		.name = "incomplete3",
 | |
| 		.resolve = stub_resolve,
 | |
| 		.cancel = NULL,
 | |
| 	};
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_register_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test off-nominal resolver registration";
 | |
| 		info->description =
 | |
| 			"Test off-nominal resolver registration:\n"
 | |
| 			"\t* Register a duplicate resolver\n"
 | |
| 			"\t* Register a resolver without a name\n"
 | |
| 			"\t* Register a resolver without a resolve() method\n"
 | |
| 			"\t* Register a resolver without a cancel() method";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&valid)) {
 | |
| 		ast_test_status_update(test, "Failed to register valid resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_register(&valid)) {
 | |
| 		ast_test_status_update(test, "Successfully registered the same resolver multiple times\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&valid);
 | |
| 
 | |
| 	if (!ast_dns_resolver_register(NULL)) {
 | |
| 		ast_test_status_update(test, "Successfully registered a NULL resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_register(&incomplete1)) {
 | |
| 		ast_test_status_update(test, "Successfully registered a DNS resolver with no name\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_register(&incomplete2)) {
 | |
| 		ast_test_status_update(test, "Successfully registered a DNS resolver with no resolve() method\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_register(&incomplete3)) {
 | |
| 		ast_test_status_update(test, "Successfully registered a DNS resolver with no cancel() method\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_unregister_off_nominal)
 | |
| {
 | |
| 	struct ast_dns_resolver non_existent = {
 | |
| 		.name = "I do not exist",
 | |
| 		.priority = 20141004,
 | |
| 		.resolve = stub_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_unregister_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test off-nominal DNS resolver unregister";
 | |
| 		info->description =
 | |
| 			"The test attempts the following:\n"
 | |
| 			"\t* Unregister a resolver that is not registered.\n"
 | |
| 			"\t* Unregister a NULL pointer.\n"
 | |
| 			"Because unregistering a resolver does not return an indicator of success, the best\n"
 | |
| 			"this test can do is verify that nothing blows up when this is attempted.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&non_existent);
 | |
| 	ast_dns_resolver_unregister(NULL);
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_data)
 | |
| {
 | |
| 	struct ast_dns_query some_query;
 | |
| 
 | |
| 	struct digits {
 | |
| 		int fingers;
 | |
| 		int toes;
 | |
| 	};
 | |
| 
 | |
| 	RAII_VAR(struct digits *, average, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct digits *, polydactyl, NULL, ao2_cleanup);
 | |
| 
 | |
| 	struct digits *data_ptr;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_data";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test getting and setting data on a DNS resolver";
 | |
| 		info->description = "This test does the following:\n"
 | |
| 			"\t* Ensure that requesting resolver data results in a NULL return if no data has been set.\n"
 | |
| 			"\t* Ensure that setting resolver data does not result in an error.\n"
 | |
| 			"\t* Ensure that retrieving the set resolver data returns the data we expect\n"
 | |
| 			"\t* Ensure that setting new resolver data on the query does not result in an error\n"
 | |
| 			"\t* Ensure that retrieving the resolver data returns the new data that we set";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	memset(&some_query, 0, sizeof(some_query));
 | |
| 
 | |
| 	average = ao2_alloc(sizeof(*average), NULL);
 | |
| 	polydactyl = ao2_alloc(sizeof(*average), NULL);
 | |
| 
 | |
| 	if (!average || !polydactyl) {
 | |
| 		ast_test_status_update(test, "Allocation failure during unit test\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Ensure that NULL is retrieved if we haven't set anything on the query */
 | |
| 	data_ptr = ast_dns_resolver_get_data(&some_query);
 | |
| 	if (data_ptr) {
 | |
| 		ast_test_status_update(test, "Retrieved non-NULL resolver data from query unexpectedly\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_set_data(&some_query, average)) {
 | |
| 		ast_test_status_update(test, "Failed to set resolver data on query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Go ahead now and remove the query's reference to the resolver data to prevent memory leaks */
 | |
| 	ao2_ref(average, -1);
 | |
| 
 | |
| 	/* Ensure that data can be set and retrieved */
 | |
| 	data_ptr = ast_dns_resolver_get_data(&some_query);
 | |
| 	if (!data_ptr) {
 | |
| 		ast_test_status_update(test, "Unable to retrieve resolver data from DNS query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (data_ptr != average) {
 | |
| 		ast_test_status_update(test, "Unexpected resolver data retrieved from DNS query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Ensure that attempting to set new resolver data on the query fails */
 | |
| 	if (!ast_dns_resolver_set_data(&some_query, polydactyl)) {
 | |
| 		ast_test_status_update(test, "Successfully overwrote resolver data on a query. We shouldn't be able to do that\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| static int test_results(struct ast_test *test, const struct ast_dns_query *query,
 | |
| 		unsigned int expected_secure, unsigned int expected_bogus,
 | |
| 		unsigned int expected_rcode, const char *expected_canonical,
 | |
| 		const char *expected_answer, size_t answer_size)
 | |
| {
 | |
| 	struct ast_dns_result *result;
 | |
| 
 | |
| 	result = ast_dns_query_get_result(query);
 | |
| 	if (!result) {
 | |
| 		ast_test_status_update(test, "Unable to retrieve result from query\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_result_get_secure(result) != expected_secure ||
 | |
| 			ast_dns_result_get_bogus(result) != expected_bogus ||
 | |
| 			ast_dns_result_get_rcode(result) != expected_rcode ||
 | |
| 			strcmp(ast_dns_result_get_canonical(result), expected_canonical) ||
 | |
| 			memcmp(ast_dns_result_get_answer(result), expected_answer, answer_size)) {
 | |
| 		ast_test_status_update(test, "Unexpected values in result from query\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* When setting a DNS result, we have to provide the raw DNS answer. This
 | |
|  * is not happening. Sorry. Instead, we provide a dummy string and call it
 | |
|  * a day
 | |
|  */
 | |
| #define DNS_ANSWER "Grumble Grumble"
 | |
| #define DNS_ANSWER_SIZE strlen(DNS_ANSWER)
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_set_result)
 | |
| {
 | |
| 	struct ast_dns_query some_query;
 | |
| 	struct ast_dns_result *result;
 | |
| 
 | |
| 	struct dns_result {
 | |
| 		unsigned int secure;
 | |
| 		unsigned int bogus;
 | |
| 		unsigned int rcode;
 | |
| 	} results[] = {
 | |
| 		{ 0, 0, NOERROR, },
 | |
| 		{ 0, 1, NOERROR, },
 | |
| 		{ 1, 0, NOERROR, },
 | |
| 		{ 0, 0, NXDOMAIN, },
 | |
| 	};
 | |
| 	int i;
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_set_result";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test setting and getting results on DNS queries";
 | |
| 		info->description =
 | |
| 			"This test performs the following:\n"
 | |
| 			"\t* Sets a result that is not secure, bogus, and has rcode 0\n"
 | |
| 			"\t* Sets a result that is not secure, has rcode 0, but is secure\n"
 | |
| 			"\t* Sets a result that is not bogus, has rcode 0, but is secure\n"
 | |
| 			"\t* Sets a result that is not secure or bogus, but has rcode NXDOMAIN\n"
 | |
| 			"After each result is set, we ensure that parameters retrieved from\n"
 | |
| 			"the result have the expected values.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	memset(&some_query, 0, sizeof(some_query));
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_LEN(results); ++i) {
 | |
| 		if (ast_dns_resolver_set_result(&some_query, results[i].secure, results[i].bogus,
 | |
| 				results[i].rcode, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 			ast_test_status_update(test, "Unable to add DNS result to query\n");
 | |
| 			res = AST_TEST_FAIL;
 | |
| 		}
 | |
| 
 | |
| 		if (test_results(test, &some_query, results[i].secure, results[i].bogus,
 | |
| 				results[i].rcode, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 			res = AST_TEST_FAIL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* The final result we set needs to be freed */
 | |
| 	result = ast_dns_query_get_result(&some_query);
 | |
| 	ast_dns_result_free(result);
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_set_result_off_nominal)
 | |
| {
 | |
| 	struct ast_dns_query some_query;
 | |
| 	struct ast_dns_result *result;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_set_result_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test setting off-nominal DNS results";
 | |
| 		info->description =
 | |
| 			"This test performs the following:\n"
 | |
| 			"\t* Attempt to add a DNS result that is both bogus and secure\n"
 | |
| 			"\t* Attempt to add a DNS result that has no canonical name";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	memset(&some_query, 0, sizeof(some_query));
 | |
| 
 | |
| 	if (!ast_dns_resolver_set_result(&some_query, 1, 1, NOERROR, "asterisk.org",
 | |
| 				DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added a result that was both secure and bogus\n");
 | |
| 		result = ast_dns_query_get_result(&some_query);
 | |
| 		ast_dns_result_free(result);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_set_result(&some_query, 0, 0, NOERROR, NULL,
 | |
| 				DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added result with no canonical name\n");
 | |
| 		result = ast_dns_query_get_result(&some_query);
 | |
| 		ast_dns_result_free(result);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| static int test_record(struct ast_test *test, const struct ast_dns_record *record,
 | |
| 		int rr_type, int rr_class, int ttl, const char *data, const size_t size)
 | |
| {
 | |
| 	if (ast_dns_record_get_rr_type(record) != rr_type) {
 | |
| 		ast_test_status_update(test, "Unexpected rr_type from DNS record\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_record_get_rr_class(record) != rr_class) {
 | |
| 		ast_test_status_update(test, "Unexpected rr_class from DNS record\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_record_get_ttl(record) != ttl) {
 | |
| 		ast_test_status_update(test, "Unexpected ttl from DNS record\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	if (memcmp(ast_dns_record_get_data(record), data, size)) {
 | |
| 		ast_test_status_update(test, "Unexpected data in DNS record\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_add_record)
 | |
| {
 | |
| 	RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
 | |
| 	struct ast_dns_query some_query;
 | |
| 	const struct ast_dns_record *record;
 | |
| 
 | |
| 	static const char *V4 = "127.0.0.1";
 | |
| 	static const size_t V4_BUFSIZE = sizeof(struct in_addr);
 | |
| 	char v4_buf[V4_BUFSIZE];
 | |
| 
 | |
| 	static const char *V6 = "::1";
 | |
| 	static const size_t V6_BUFSIZE = sizeof(struct in6_addr);
 | |
| 	char v6_buf[V6_BUFSIZE];
 | |
| 
 | |
| 	struct dns_record_details {
 | |
| 		int type;
 | |
| 		int class;
 | |
| 		int ttl;
 | |
| 		const char *data;
 | |
| 		const size_t size;
 | |
| 		int visited;
 | |
| 	} records[] = {
 | |
| 		{ T_A, C_IN, 12345, v4_buf, V4_BUFSIZE, 0, },
 | |
| 		{ T_AAAA, C_IN, 12345, v6_buf, V6_BUFSIZE, 0, },
 | |
| 	};
 | |
| 
 | |
| 	int num_records_visited = 0;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_add_record";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test adding DNS records to a query";
 | |
| 		info->description =
 | |
| 			"This test performs the following:\n"
 | |
| 			"\t* Ensure a nominal A record can be added to a query result\n"
 | |
| 			"\t* Ensures that the record can be retrieved\n"
 | |
| 			"\t* Ensure that a second record can be added to the query result\n"
 | |
| 			"\t* Ensures that both records can be retrieved";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	memset(&some_query, 0, sizeof(some_query));
 | |
| 
 | |
| 	if (ast_dns_resolver_set_result(&some_query, 0, 0, NOERROR, "asterisk.org",
 | |
| 				DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 		ast_test_status_update(test, "Unable to set result for DNS query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	result = ast_dns_query_get_result(&some_query);
 | |
| 	if (!result) {
 | |
| 		ast_test_status_update(test, "Unable to retrieve result from query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	inet_pton(AF_INET, V4, v4_buf);
 | |
| 
 | |
| 	/* Nominal Record */
 | |
| 	if (ast_dns_resolver_add_record(&some_query, records[0].type, records[0].class,
 | |
| 				records[0].ttl, records[0].data, records[0].size)) {
 | |
| 		ast_test_status_update(test, "Unable to add nominal record to query result\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* I should only be able to retrieve one record */
 | |
| 	record = ast_dns_result_get_records(result);
 | |
| 	if (!record) {
 | |
| 		ast_test_status_update(test, "Unable to retrieve record from result\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (test_record(test, record, records[0].type, records[0].class, records[0].ttl,
 | |
| 				records[0].data, records[0].size)) {
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_record_get_next(record)) {
 | |
| 		ast_test_status_update(test, "Multiple records returned when only one was expected\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	inet_pton(AF_INET6, V6, v6_buf);
 | |
| 
 | |
| 	if (ast_dns_resolver_add_record(&some_query, records[1].type, records[1].class,
 | |
| 				records[1].ttl, records[1].data, records[1].size)) {
 | |
| 		ast_test_status_update(test, "Unable to add second record to query result\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
 | |
| 		int res;
 | |
| 
 | |
| 		/* The order of returned records is not specified by the API. We use the record type
 | |
| 		 * as the discriminator to determine which record data to expect.
 | |
| 		 */
 | |
| 		if (ast_dns_record_get_rr_type(record) == records[0].type) {
 | |
| 			res = test_record(test, record, records[0].type, records[0].class, records[0].ttl, records[0].data, records[0].size);
 | |
| 			records[0].visited = 1;
 | |
| 		} else if (ast_dns_record_get_rr_type(record) == records[1].type) {
 | |
| 			res = test_record(test, record, records[1].type, records[1].class, records[1].ttl, records[1].data, records[1].size);
 | |
| 			records[1].visited = 1;
 | |
| 		} else {
 | |
| 			ast_test_status_update(test, "Unknown record type found in DNS results\n");
 | |
| 			return AST_TEST_FAIL;
 | |
| 		}
 | |
| 
 | |
| 		if (res) {
 | |
| 			return AST_TEST_FAIL;
 | |
| 		}
 | |
| 
 | |
| 		++num_records_visited;
 | |
| 	}
 | |
| 
 | |
| 	if (!records[0].visited || !records[1].visited) {
 | |
| 		ast_test_status_update(test, "Did not visit all added DNS records\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (num_records_visited != ARRAY_LEN(records)) {
 | |
| 		ast_test_status_update(test, "Did not visit the expected number of DNS records\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_add_record_off_nominal)
 | |
| {
 | |
| 	RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
 | |
| 	struct ast_dns_query some_query;
 | |
| 	static const char *V4 = "127.0.0.1";
 | |
| 	static const size_t V4_BUFSIZE = sizeof(struct in_addr);
 | |
| 	char v4_buf[V4_BUFSIZE];
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_add_record_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test adding off-nominal DNS records to a query";
 | |
| 		info->description =
 | |
| 			"This test performs the following:\n"
 | |
| 			"\t* Ensure a nominal A record cannot be added if no result has been set.\n"
 | |
| 			"\t* Ensure that an A record with invalid RR types cannot be added to a query\n"
 | |
| 			"\t* Ensure that an A record with invalid RR classes cannot be added to a query\n"
 | |
| 			"\t* Ensure that an A record with invalid TTL cannot be added to a query\n"
 | |
| 			"\t* Ensure that an A record with NULL data cannot be added to a query\n"
 | |
| 			"\t* Ensure that an A record with invalid length cannot be added to a query";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	memset(&some_query, 0, sizeof(some_query));
 | |
| 
 | |
| 	inet_ntop(AF_INET, V4, v4_buf, V4_BUFSIZE);
 | |
| 
 | |
| 	/* Add record before setting result */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, C_IN, 12345, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record to query before setting a result\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_set_result(&some_query, 0, 0, NOERROR, "asterisk.org",
 | |
| 				DNS_ANSWER, DNS_ANSWER_SIZE)) {
 | |
| 		ast_test_status_update(test, "Unable to set result for DNS query\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* We get the result so it will be cleaned up when the function exits */
 | |
| 	result = ast_dns_query_get_result(&some_query);
 | |
| 
 | |
| 	/* Invalid RR types */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, -1, C_IN, 12345, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record with negative RR type\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, 65536 + 1, C_IN, 12345, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record with too large RR type\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Invalid RR classes */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, -1, 12345, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record with negative RR class\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, 65536 + 1, 12345, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record with too large RR class\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Invalid TTL */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, C_IN, -1, v4_buf, V4_BUFSIZE)) {
 | |
| 		ast_test_status_update(test, "Successfully added DNS record with negative TTL\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* No data */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, C_IN, 12345, NULL, 0)) {
 | |
| 		ast_test_status_update(test, "Successfully added a DNS record with no data\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Lie about the length */
 | |
| 	if (!ast_dns_resolver_add_record(&some_query, T_A, C_IN, 12345, v4_buf, 0)) {
 | |
| 		ast_test_status_update(test, "Successfully added a DNS record with length zero\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return AST_TEST_PASS;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief File-scoped data used during resolver tests
 | |
|  *
 | |
|  * This data has to live at file-scope since it needs to be
 | |
|  * accessible by multiple threads.
 | |
|  */
 | |
| static struct resolver_data {
 | |
| 	/*! True if the resolver's resolve() method has been called */
 | |
| 	int resolve_called;
 | |
| 	/*! True if the resolver's cancel() method has been called */
 | |
| 	int canceled;
 | |
| 	/*! True if resolution successfully completed. This is mutually exclusive with \ref canceled */
 | |
| 	int resolution_complete;
 | |
| 	/*! Lock used for protecting \ref cancel_cond */
 | |
| 	ast_mutex_t lock;
 | |
| 	/*! Condition variable used to coordinate canceling a query */
 | |
| 	ast_cond_t cancel_cond;
 | |
| } test_resolver_data;
 | |
| 
 | |
| /*!
 | |
|  * \brief Thread spawned by the mock resolver
 | |
|  *
 | |
|  * All DNS resolvers are required to be asynchronous. The mock resolver
 | |
|  * spawns this thread for every DNS query that is executed.
 | |
|  *
 | |
|  * This thread waits for 5 seconds and then returns the same A record
 | |
|  * every time. The 5 second wait is to allow for the query to be
 | |
|  * canceled if desired
 | |
|  *
 | |
|  * \param dns_query The ast_dns_query that is being resolved
 | |
|  * \return NULL
 | |
|  */
 | |
| static void *resolution_thread(void *dns_query)
 | |
| {
 | |
| 	struct ast_dns_query *query = dns_query;
 | |
| 	struct timespec timeout;
 | |
| 
 | |
| 	static const char *V4 = "127.0.0.1";
 | |
| 	static const size_t V4_BUFSIZE = sizeof(struct in_addr);
 | |
| 	char v4_buf[V4_BUFSIZE];
 | |
| 
 | |
| 	timeout = ast_tsnow();
 | |
| 	timeout.tv_sec += 5;
 | |
| 
 | |
| 	ast_mutex_lock(&test_resolver_data.lock);
 | |
| 	while (!test_resolver_data.canceled) {
 | |
| 		if (ast_cond_timedwait(&test_resolver_data.cancel_cond, &test_resolver_data.lock, &timeout) == ETIMEDOUT) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	ast_mutex_unlock(&test_resolver_data.lock);
 | |
| 
 | |
| 	if (test_resolver_data.canceled) {
 | |
| 		ast_dns_resolver_completed(query);
 | |
| 		ao2_ref(query, -1);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_set_result(query, 0, 0, NOERROR, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
 | |
| 
 | |
| 	inet_pton(AF_INET, V4, v4_buf);
 | |
| 	ast_dns_resolver_add_record(query, T_A, C_IN, 12345, v4_buf, V4_BUFSIZE);
 | |
| 
 | |
| 	test_resolver_data.resolution_complete = 1;
 | |
| 	ast_dns_resolver_completed(query);
 | |
| 
 | |
| 	ao2_ref(query, -1);
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Mock resolver's resolve method
 | |
|  *
 | |
|  * \param query The query to resolve
 | |
|  * \retval 0 Successfully spawned resolution thread
 | |
|  * \retval non-zero Failed to spawn the resolution thread
 | |
|  */
 | |
| static int test_resolve(struct ast_dns_query *query)
 | |
| {
 | |
| 	pthread_t resolver_thread;
 | |
| 
 | |
| 	test_resolver_data.resolve_called = 1;
 | |
| 	return ast_pthread_create_detached(&resolver_thread, NULL, resolution_thread, ao2_bump(query));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Mock resolver's cancel method
 | |
|  *
 | |
|  * This signals the resolution thread not to return any DNS results.
 | |
|  *
 | |
|  * \param query DNS query to cancel
 | |
|  * \return 0
 | |
|  */
 | |
| static int test_cancel(struct ast_dns_query *query)
 | |
| {
 | |
| 	ast_mutex_lock(&test_resolver_data.lock);
 | |
| 	test_resolver_data.canceled = 1;
 | |
| 	ast_cond_signal(&test_resolver_data.cancel_cond);
 | |
| 	ast_mutex_unlock(&test_resolver_data.lock);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Initialize global mock resolver data.
 | |
|  *
 | |
|  * This must be called at the beginning of tests that use the mock resolver
 | |
|  */
 | |
| static void resolver_data_init(void)
 | |
| {
 | |
| 	test_resolver_data.resolve_called = 0;
 | |
| 	test_resolver_data.canceled = 0;
 | |
| 	test_resolver_data.resolution_complete = 0;
 | |
| 
 | |
| 	ast_mutex_init(&test_resolver_data.lock);
 | |
| 	ast_cond_init(&test_resolver_data.cancel_cond, NULL);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Cleanup global mock resolver data
 | |
|  *
 | |
|  * This must be called at the end of tests that use the mock resolver
 | |
|  */
 | |
| static void resolver_data_cleanup(void)
 | |
| {
 | |
| 	ast_mutex_destroy(&test_resolver_data.lock);
 | |
| 	ast_cond_destroy(&test_resolver_data.cancel_cond);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief The mock resolver
 | |
|  *
 | |
|  * The mock resolver does not care about the DNS query that is
 | |
|  * actually being made on it. It simply regurgitates the same
 | |
|  * DNS record no matter what.
 | |
|  */
 | |
| static struct ast_dns_resolver test_resolver = {
 | |
| 	.name = "test",
 | |
| 	.priority = 0,
 | |
| 	.resolve = test_resolve,
 | |
| 	.cancel = test_cancel,
 | |
| };
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_resolve_sync)
 | |
| {
 | |
| 	RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free);
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_resolve_sync";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test a nominal synchronous DNS resolution";
 | |
| 		info->description =
 | |
| 			"This test performs a synchronous DNS resolution of a domain. The goal of this\n"
 | |
| 			"test is not to check the records for accuracy. Rather, the goal is to ensure that\n"
 | |
| 			"the resolver is called into as expected, that the query completes entirely before\n"
 | |
| 			"returning from the synchronous resolution, that nothing tried to cancel the resolution\n,"
 | |
| 			"and that some records were returned.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&test_resolver)) {
 | |
| 		ast_test_status_update(test, "Unable to register test resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	resolver_data_init();
 | |
| 
 | |
| 	if (ast_dns_resolve("asterisk.org", T_A, C_IN, &result)) {
 | |
| 		ast_test_status_update(test, "Resolution of address failed\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!result) {
 | |
| 		ast_test_status_update(test, "DNS resolution returned a NULL result\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!test_resolver_data.resolve_called) {
 | |
| 		ast_test_status_update(test, "DNS resolution did not call resolver's resolve() method\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (test_resolver_data.canceled) {
 | |
| 		ast_test_status_update(test, "Resolver's cancel() method called for no reason\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!test_resolver_data.resolution_complete) {
 | |
| 		ast_test_status_update(test, "Synchronous resolution completed early?\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_result_get_records(result)) {
 | |
| 		ast_test_status_update(test, "Synchronous resolution yielded no records.\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	ast_dns_resolver_unregister(&test_resolver);
 | |
| 	resolver_data_cleanup();
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief A resolve() method that simply fails
 | |
|  *
 | |
|  * \param query The DNS query to resolve. This is ignored.
 | |
|  * \return -1
 | |
|  */
 | |
| static int fail_resolve(struct ast_dns_query *query)
 | |
| {
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_resolve_sync_off_nominal)
 | |
| {
 | |
| 	struct ast_dns_resolver terrible_resolver = {
 | |
| 		.name = "Uwe Boll's Filmography",
 | |
| 		.priority = 0,
 | |
| 		.resolve = fail_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	struct ast_dns_result *result = NULL;
 | |
| 
 | |
| 	struct dns_resolve_data {
 | |
| 		const char *name;
 | |
| 		int rr_type;
 | |
| 		int rr_class;
 | |
| 		struct ast_dns_result **result;
 | |
| 	} resolves [] = {
 | |
| 		{ NULL,           T_A,       C_IN,      &result },
 | |
| 		{ "asterisk.org", -1,        C_IN,      &result },
 | |
| 		{ "asterisk.org", 65536 + 1, C_IN,      &result },
 | |
| 		{ "asterisk.org", T_A,       -1,        &result },
 | |
| 		{ "asterisk.org", T_A,       65536 + 1, &result },
 | |
| 		{ "asterisk.org", T_A,       C_IN,      NULL },
 | |
| 	};
 | |
| 
 | |
| 	int i;
 | |
| 
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_resolve_sync_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test off-nominal synchronous DNS resolution";
 | |
| 		info->description =
 | |
| 			"This test performs several off-nominal synchronous DNS resolutions:\n"
 | |
| 			"\t* Attempt resolution with NULL name\n"
 | |
| 			"\t* Attempt resolution with invalid RR type\n"
 | |
| 			"\t* Attempt resolution with invalid RR class\n"
 | |
| 			"\t* Attempt resolution with NULL result pointer\n"
 | |
| 			"\t* Attempt resolution with resolver that returns an error";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&test_resolver)) {
 | |
| 		ast_test_status_update(test, "Failed to register test resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_LEN(resolves); ++i) {
 | |
| 		if (!ast_dns_resolve(resolves[i].name, resolves[i].rr_type, resolves[i].rr_class, resolves[i].result)) {
 | |
| 			ast_test_status_update(test, "Successfully resolved DNS query with invalid parameters\n");
 | |
| 			res = AST_TEST_FAIL;
 | |
| 		} else if (result) {
 | |
| 			ast_test_status_update(test, "Failed resolution set a non-NULL result\n");
 | |
| 			ast_dns_result_free(result);
 | |
| 			res = AST_TEST_FAIL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&test_resolver);
 | |
| 
 | |
| 	/* As a final test, try a legitimate query with a bad resolver */
 | |
| 	if (ast_dns_resolver_register(&terrible_resolver)) {
 | |
| 		ast_test_status_update(test, "Failed to register the terrible resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_resolve("asterisk.org", T_A, C_IN, &result)) {
 | |
| 		ast_test_status_update(test, "DNS resolution succeeded when we expected it not to\n");
 | |
| 		ast_dns_resolver_unregister(&terrible_resolver);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&terrible_resolver);
 | |
| 
 | |
| 	if (result) {
 | |
| 		ast_test_status_update(test, "Failed DNS resolution set the result to something non-NULL\n");
 | |
| 		ast_dns_result_free(result);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Data used by async result callback
 | |
|  *
 | |
|  * This is the typical combination of boolean, lock, and condition
 | |
|  * used to synchronize the activities of two threads. In this case,
 | |
|  * the testing thread waits on the condition, and the async callback
 | |
|  * signals the condition when the asynchronous callback is complete.
 | |
|  */
 | |
| struct async_resolution_data {
 | |
| 	int complete;
 | |
| 	ast_mutex_t lock;
 | |
| 	ast_cond_t cond;
 | |
| };
 | |
| 
 | |
| /*!
 | |
|  * \brief Destructor for async_resolution_data
 | |
|  */
 | |
| static void async_data_destructor(void *obj)
 | |
| {
 | |
| 	struct async_resolution_data *async_data = obj;
 | |
| 
 | |
| 	ast_mutex_destroy(&async_data->lock);
 | |
| 	ast_cond_destroy(&async_data->cond);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Allocation/initialization for async_resolution_data
 | |
|  *
 | |
|  * The DNS core mandates that a query's user data has to be ao2 allocated,
 | |
|  * so this is a helper method for doing that.
 | |
|  *
 | |
|  * \retval NULL Failed allocation
 | |
|  * \retval non-NULL Newly allocated async_resolution_data
 | |
|  */
 | |
| static struct async_resolution_data *async_data_alloc(void)
 | |
| {
 | |
| 	struct async_resolution_data *async_data;
 | |
| 
 | |
| 	async_data = ao2_alloc(sizeof(*async_data), async_data_destructor);
 | |
| 	if (!async_data) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	async_data->complete = 0;
 | |
| 	ast_mutex_init(&async_data->lock);
 | |
| 	ast_cond_init(&async_data->cond, NULL);
 | |
| 
 | |
| 	return async_data;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|  * \brief Async DNS callback
 | |
|  *
 | |
|  * This is called when an async query completes, either because it resolved or
 | |
|  * because it was canceled. In our case, this callback is used to signal to the
 | |
|  * test that it can continue
 | |
|  *
 | |
|  * \param query The DNS query that has completed
 | |
|  */
 | |
| static void async_callback(const struct ast_dns_query *query)
 | |
| {
 | |
| 	struct async_resolution_data *async_data = ast_dns_query_get_data(query);
 | |
| 
 | |
| 	ast_mutex_lock(&async_data->lock);
 | |
| 	async_data->complete = 1;
 | |
| 	ast_cond_signal(&async_data->cond);
 | |
| 	ast_mutex_unlock(&async_data->lock);
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_resolve_async)
 | |
| {
 | |
| 	RAII_VAR(struct async_resolution_data *, async_data, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
 | |
| 	struct ast_dns_result *result;
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 	struct timespec timeout;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_resolve_async";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test a nominal asynchronous DNS resolution";
 | |
| 		info->description =
 | |
| 			"This test performs an asynchronous DNS resolution of a domain. The goal of this\n"
 | |
| 			"test is not to check the records for accuracy. Rather, the goal is to ensure that\n"
 | |
| 			"the resolver is called into as expected, that we regain control before the query\n"
 | |
| 			"is completed, and to ensure that nothing tried to cancel the resolution.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&test_resolver)) {
 | |
| 		ast_test_status_update(test, "Unable to register test resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	resolver_data_init();
 | |
| 
 | |
| 	async_data = async_data_alloc();
 | |
| 	if (!async_data) {
 | |
| 		ast_test_status_update(test, "Failed to allocate asynchronous data\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	active = ast_dns_resolve_async("asterisk.org", T_A, C_IN, async_callback, async_data);
 | |
| 	if (!active) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution of address failed\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!test_resolver_data.resolve_called) {
 | |
| 		ast_test_status_update(test, "DNS resolution did not call resolver's resolve() method\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (test_resolver_data.canceled) {
 | |
| 		ast_test_status_update(test, "Resolver's cancel() method called for no reason\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	timeout = ast_tsnow();
 | |
| 	timeout.tv_sec += 10;
 | |
| 	ast_mutex_lock(&async_data->lock);
 | |
| 	while (!async_data->complete) {
 | |
| 		if (ast_cond_timedwait(&async_data->cond, &async_data->lock, &timeout) == ETIMEDOUT) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	ast_mutex_unlock(&async_data->lock);
 | |
| 
 | |
| 	if (!async_data->complete) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution timed out\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!test_resolver_data.resolution_complete) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution completed early?\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	result = ast_dns_query_get_result(active->query);
 | |
| 	if (!result) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution yielded no result\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_dns_result_get_records(result)) {
 | |
| 		ast_test_status_update(test, "Asynchronous result had no records\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	ast_dns_resolver_unregister(&test_resolver);
 | |
| 	resolver_data_cleanup();
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| /*! Stub async resolution callback */
 | |
| static void stub_callback(const struct ast_dns_query *query)
 | |
| {
 | |
| 	return;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_resolve_async_off_nominal)
 | |
| {
 | |
| 	struct ast_dns_resolver terrible_resolver = {
 | |
| 		.name = "Ed Wood's Filmography",
 | |
| 		.priority = 0,
 | |
| 		.resolve = fail_resolve,
 | |
| 		.cancel = stub_cancel,
 | |
| 	};
 | |
| 
 | |
| 	struct dns_resolve_data {
 | |
| 		const char *name;
 | |
| 		int rr_type;
 | |
| 		int rr_class;
 | |
| 		ast_dns_resolve_callback callback;
 | |
| 	} resolves [] = {
 | |
| 		{ NULL,           T_A,       C_IN,      stub_callback },
 | |
| 		{ "asterisk.org", -1,        C_IN,      stub_callback },
 | |
| 		{ "asterisk.org", 65536 + 1, C_IN,      stub_callback },
 | |
| 		{ "asterisk.org", T_A,       -1,        stub_callback },
 | |
| 		{ "asterisk.org", T_A,       65536 + 1, stub_callback },
 | |
| 		{ "asterisk.org", T_A,       C_IN,      NULL },
 | |
| 	};
 | |
| 
 | |
| 	struct ast_dns_query_active *active;
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 	int i;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_resolve_async_off_nominal";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test off-nominal asynchronous DNS resolution";
 | |
| 		info->description =
 | |
| 			"This test performs several off-nominal asynchronous DNS resolutions:\n"
 | |
| 			"\t* Attempt resolution with NULL name\n"
 | |
| 			"\t* Attempt resolution with invalid RR type\n"
 | |
| 			"\t* Attempt resolution with invalid RR class\n"
 | |
| 			"\t* Attempt resolution with NULL callback pointer\n"
 | |
| 			"\t* Attempt resolution with resolver that returns an error";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&test_resolver)) {
 | |
| 		ast_test_status_update(test, "Failed to register test resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_LEN(resolves); ++i) {
 | |
| 		active = ast_dns_resolve_async(resolves[i].name, resolves[i].rr_type, resolves[i].rr_class,
 | |
| 				resolves[i].callback, NULL);
 | |
| 		if (active) {
 | |
| 			ast_test_status_update(test, "Successfully performed asynchronous resolution with invalid data\n");
 | |
| 			ao2_ref(active, -1);
 | |
| 			res = AST_TEST_FAIL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&test_resolver);
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&terrible_resolver)) {
 | |
| 		ast_test_status_update(test, "Failed to register the DNS resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	active = ast_dns_resolve_async("asterisk.org", T_A, C_IN, stub_callback, NULL);
 | |
| 
 | |
| 	ast_dns_resolver_unregister(&terrible_resolver);
 | |
| 
 | |
| 	if (active) {
 | |
| 		ast_test_status_update(test, "Successfully performed asynchronous resolution with invalid data\n");
 | |
| 		ao2_ref(active, -1);
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| AST_TEST_DEFINE(resolver_resolve_async_cancel)
 | |
| {
 | |
| 	RAII_VAR(struct async_resolution_data *, async_data, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct ast_dns_query_active *, active, NULL, ao2_cleanup);
 | |
| 	struct ast_dns_result *result;
 | |
| 	enum ast_test_result_state res = AST_TEST_PASS;
 | |
| 	struct timespec timeout;
 | |
| 
 | |
| 	switch (cmd) {
 | |
| 	case TEST_INIT:
 | |
| 		info->name = "resolver_resolve_async_cancel";
 | |
| 		info->category = "/main/dns/";
 | |
| 		info->summary = "Test canceling an asynchronous DNS resolution";
 | |
| 		info->description =
 | |
| 			"This test performs an asynchronous DNS resolution of a domain and then cancels\n"
 | |
| 			"the resolution. The goal of this test is to ensure that the cancel() callback of\n"
 | |
| 			"the resolver is called and that it properly interrupts the resolution such that no\n"
 | |
| 			"records are returned.";
 | |
| 		return AST_TEST_NOT_RUN;
 | |
| 	case TEST_EXECUTE:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_dns_resolver_register(&test_resolver)) {
 | |
| 		ast_test_status_update(test, "Unable to register test resolver\n");
 | |
| 		return AST_TEST_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	resolver_data_init();
 | |
| 
 | |
| 	async_data = async_data_alloc();
 | |
| 	if (!async_data) {
 | |
| 		ast_test_status_update(test, "Failed to allocate asynchronous data\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	active = ast_dns_resolve_async("asterisk.org", T_A, C_IN, async_callback, async_data);
 | |
| 	if (!active) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution of address failed\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (!test_resolver_data.resolve_called) {
 | |
| 		ast_test_status_update(test, "DNS resolution did not call resolver's resolve() method\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (test_resolver_data.canceled) {
 | |
| 		ast_test_status_update(test, "Resolver's cancel() method called for no reason\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	ast_dns_resolve_cancel(active);
 | |
| 
 | |
| 	if (!test_resolver_data.canceled) {
 | |
| 		ast_test_status_update(test, "Resolver's cancel() method was not called\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	timeout = ast_tsnow();
 | |
| 	timeout.tv_sec += 10;
 | |
| 	ast_mutex_lock(&async_data->lock);
 | |
| 	while (!async_data->complete) {
 | |
| 		if (ast_cond_timedwait(&async_data->cond, &async_data->lock, &timeout) == ETIMEDOUT) {
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	ast_mutex_unlock(&async_data->lock);
 | |
| 
 | |
| 	if (!async_data->complete) {
 | |
| 		ast_test_status_update(test, "Asynchronous resolution timed out\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (test_resolver_data.resolution_complete) {
 | |
| 		ast_test_status_update(test, "Resolution completed without cancelation\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	result = ast_dns_query_get_result(active->query);
 | |
| 	if (result) {
 | |
| 		ast_test_status_update(test, "Canceled resolution had a result\n");
 | |
| 		res = AST_TEST_FAIL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	ast_dns_resolver_unregister(&test_resolver);
 | |
| 	resolver_data_cleanup();
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int unload_module(void)
 | |
| {
 | |
| 	AST_TEST_UNREGISTER(resolver_register_unregister);
 | |
| 	AST_TEST_UNREGISTER(resolver_register_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_unregister_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_data);
 | |
| 	AST_TEST_UNREGISTER(resolver_set_result);
 | |
| 	AST_TEST_UNREGISTER(resolver_set_result_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_add_record);
 | |
| 	AST_TEST_UNREGISTER(resolver_add_record_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_resolve_sync);
 | |
| 	AST_TEST_UNREGISTER(resolver_resolve_sync_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_resolve_async);
 | |
| 	AST_TEST_UNREGISTER(resolver_resolve_async_off_nominal);
 | |
| 	AST_TEST_UNREGISTER(resolver_resolve_async_cancel);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int load_module(void)
 | |
| {
 | |
| 	AST_TEST_REGISTER(resolver_register_unregister);
 | |
| 	AST_TEST_REGISTER(resolver_register_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_unregister_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_data);
 | |
| 	AST_TEST_REGISTER(resolver_set_result);
 | |
| 	AST_TEST_REGISTER(resolver_set_result_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_add_record);
 | |
| 	AST_TEST_REGISTER(resolver_add_record_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_resolve_sync);
 | |
| 	AST_TEST_REGISTER(resolver_resolve_sync_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_resolve_async);
 | |
| 	AST_TEST_REGISTER(resolver_resolve_async_off_nominal);
 | |
| 	AST_TEST_REGISTER(resolver_resolve_async_cancel);
 | |
| 
 | |
| 	return AST_MODULE_LOAD_SUCCESS;
 | |
| }
 | |
| 
 | |
| AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DNS API Tests");
 |