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");
|