Author: file Date: Wed Apr 1 11:27:48 2015 New Revision: 433889 URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=433889 Log: dns: Add support for SRV record parsing and sorting.
This change adds support for parsing SRV records and consuming their values in an easy fashion. It also adds automatic sorting of SRV records according to RFC 2782. Tests have also been included which cover parsing, sorting, and off-nominal cases where the record is corrupted. ASTERISK-24931 #close Reported by: Joshua Colp Review: https://reviewboard.asterisk.org/r/4528/ Added: trunk/tests/test_dns_srv.c (with props) Modified: trunk/include/asterisk/dns_internal.h trunk/main/dns_core.c trunk/main/dns_srv.c trunk/res/res_resolver_unbound.c Modified: trunk/include/asterisk/dns_internal.h URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/dns_internal.h?view=diff&rev=433889&r1=433888&r2=433889 ============================================================================== --- trunk/include/asterisk/dns_internal.h (original) +++ trunk/include/asterisk/dns_internal.h Wed Apr 1 11:27:48 2015 @@ -35,6 +35,7 @@ size_t data_len; /*! \brief Linked list information */ AST_LIST_ENTRY(ast_dns_record) list; + char *data_ptr; /*! \brief The raw DNS record */ char data[0]; }; @@ -51,6 +52,10 @@ unsigned short weight; /*! \brief The port in the SRV record */ unsigned short port; + /*! \brief The running weight sum */ + unsigned int weight_sum; + /*! \brief Additional data */ + char data[0]; }; /*! \brief A NAPTR record */ @@ -80,11 +85,13 @@ /*! \brief Optional rcode, set if an error occurred */ unsigned int rcode; /*! \brief Records returned */ - AST_LIST_HEAD_NOLOCK(, ast_dns_record) records; + AST_LIST_HEAD_NOLOCK(dns_records, ast_dns_record) records; /*! \brief The canonical name */ const char *canonical; /*! \brief The raw DNS answer */ const char *answer; + /*! \brief The size of the raw DNS answer */ + size_t answer_size; /*! \brief Buffer for dynamic data */ char buf[0]; }; @@ -143,3 +150,24 @@ * \return scheduler context */ struct ast_sched_context *ast_dns_get_sched(void); + +/*! + * \brief Allocate and parse a DNS SRV record + * + * \param query The DNS query + * \param data This specific SRV record + * \param size The size of the SRV record + * + * \retval non-NULL success + * \retval NULL failure + */ +struct ast_dns_record *ast_dns_srv_alloc(struct ast_dns_query *query, const char *data, const size_t size); + +/*! + * \brief Sort the SRV records on a result + * + * \param result The DNS result + */ +void ast_dns_srv_sort(struct ast_dns_result *result); + + Modified: trunk/main/dns_core.c URL: http://svnview.digium.com/svn/asterisk/trunk/main/dns_core.c?view=diff&rev=433889&r1=433888&r2=433889 ============================================================================== --- trunk/main/dns_core.c (original) +++ trunk/main/dns_core.c Wed Apr 1 11:27:48 2015 @@ -43,6 +43,7 @@ #include "asterisk/dns_resolver.h" #include "asterisk/dns_internal.h" +#include <netinet/in.h> #include <arpa/nameser.h> AST_RWLIST_HEAD_STATIC(resolvers, ast_dns_resolver); @@ -159,7 +160,7 @@ const char *ast_dns_record_get_data(const struct ast_dns_record *record) { - return record->data; + return record->data_ptr; } const struct ast_dns_record *ast_dns_record_get_next(const struct ast_dns_record *record) @@ -406,8 +407,23 @@ buf_ptr += strlen(canonical) + 1; memcpy(buf_ptr, answer, answer_size); /* SAFE */ query->result->answer = buf_ptr; + query->result->answer_size = answer_size; return 0; +} + +static struct ast_dns_record *generic_record_alloc(struct ast_dns_query *query, const char *data, const size_t size) +{ + struct ast_dns_record *record; + + record = ast_calloc(1, sizeof(*record) + size); + if (!record) { + return NULL; + } + + record->data_ptr = record->data; + + return record; } int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr_class, int ttl, const char *data, const size_t size) @@ -444,7 +460,12 @@ return -1; } - record = ast_calloc(1, sizeof(*record) + size); + if (rr_type == ns_t_srv) { + record = ast_dns_srv_alloc(query, data, size); + } else { + record = generic_record_alloc(query, data, size); + } + if (!record) { return -1; } @@ -452,8 +473,8 @@ record->rr_type = rr_type; record->rr_class = rr_class; record->ttl = ttl; - memcpy(record->data, data, size); record->data_len = size; + memcpy(record->data_ptr, data, size); AST_LIST_INSERT_TAIL(&query->result->records, record, list); @@ -462,6 +483,10 @@ void ast_dns_resolver_completed(struct ast_dns_query *query) { + if (ast_dns_query_get_rr_type(query) == ns_t_srv) { + ast_dns_srv_sort(query->result); + } + query->callback(query); } Modified: trunk/main/dns_srv.c URL: http://svnview.digium.com/svn/asterisk/trunk/main/dns_srv.c?view=diff&rev=433889&r1=433888&r2=433889 ============================================================================== --- trunk/main/dns_srv.c (original) +++ trunk/main/dns_srv.c Wed Apr 1 11:27:48 2015 @@ -31,25 +31,205 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> + #include "asterisk/dns_core.h" #include "asterisk/dns_srv.h" +#include "asterisk/linkedlists.h" +#include "asterisk/dns_internal.h" +#include "asterisk/utils.h" + +struct ast_dns_record *ast_dns_srv_alloc(struct ast_dns_query *query, const char *data, const size_t size) +{ + uint16_t priority; + uint16_t weight; + uint16_t port; + const char *ptr; + char *srv_offset; + char *srv_search_base = (char *)query->result->answer; + size_t remaining_size = query->result->answer_size; + const char *end_of_record; + struct ast_dns_srv_record *srv; + int host_size; + char host[NI_MAXHOST] = ""; + + while (1) { + srv_offset = memchr(srv_search_base, data[0], remaining_size); + + ast_assert(srv_offset != NULL); + ast_assert(srv_search_base + remaining_size - srv_offset >= size); + + if (!memcmp(srv_offset, data, size)) { + ptr = srv_offset; + break; + } + + remaining_size -= srv_offset - srv_search_base; + srv_search_base = srv_offset + 1; + } + + ast_assert(ptr != NULL); + + end_of_record = ptr + size; + + /* PRIORITY */ + priority = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8); + ptr += 2; + + if (ptr >= end_of_record) { + return NULL; + } + + /* WEIGHT */ + weight = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8); + ptr += 2; + + if (ptr >= end_of_record) { + return NULL; + } + + /* PORT */ + port = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8); + ptr += 2; + + if (ptr >= end_of_record) { + return NULL; + } + + host_size = dn_expand((unsigned char *)query->result->answer, (unsigned char *) end_of_record, (unsigned char *) ptr, host, sizeof(host) - 1); + if (host_size < 0) { + ast_log(LOG_ERROR, "Failed to expand domain name: %s\n", strerror(errno)); + return NULL; + } + + if (!strcmp(host, ".")) { + return NULL; + } + + srv = ast_calloc(1, sizeof(*srv) + size + host_size + 1); + if (!srv) { + return NULL; + } + + srv->priority = priority; + srv->weight = weight; + srv->port = port; + + srv->host = srv->data + size; + strcpy((char *)srv->host, host); /* SAFE */ + ((char *)srv->host)[host_size] = '\0'; + + srv->generic.data_ptr = srv->data; + + return (struct ast_dns_record *)srv; +} + +/* This implementation was taken from the existing srv.c which, after reading the RFC, implements it + * as it should. + */ +void ast_dns_srv_sort(struct ast_dns_result *result) +{ + struct ast_dns_record *current; + struct dns_records newlist = AST_LIST_HEAD_NOLOCK_INIT_VALUE; + + while (AST_LIST_FIRST(&result->records)) { + unsigned short cur_priority = 0; + struct dns_records temp_list = AST_LIST_HEAD_NOLOCK_INIT_VALUE; + + /* Find the lowest current priority to work on */ + AST_LIST_TRAVERSE(&result->records, current, list) { + if (!cur_priority || ((struct ast_dns_srv_record *)current)->priority < cur_priority) { + cur_priority = ((struct ast_dns_srv_record *)current)->priority; + } + } + + /* Find all records which match this priority */ + AST_LIST_TRAVERSE_SAFE_BEGIN(&result->records, current, list) { + if (((struct ast_dns_srv_record *)current)->priority != cur_priority) { + continue; + } + + AST_LIST_REMOVE_CURRENT(list); + + /* Records with a weight of zero must always be at the head */ + if (((struct ast_dns_srv_record *)current)->weight == 0) { + AST_LIST_INSERT_HEAD(&temp_list, current, list); + } else { + AST_LIST_INSERT_TAIL(&temp_list, current, list); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + /* Apply weighting - as each record is passed the sum of all previous weights (plus its own) is stored away, and then a random weight + * is calculated. The first record with a weight sum greater than the random weight is put in the new list and the whole thing starts + * once again. + */ + while (AST_LIST_FIRST(&temp_list)) { + unsigned int weight_sum = 0; + unsigned int random_weight; + + AST_LIST_TRAVERSE(&temp_list, current, list) { + ((struct ast_dns_srv_record *)current)->weight_sum = weight_sum += ((struct ast_dns_srv_record *)current)->weight; + } + + /* if all the remaining entries have weight == 0, + then just append them to the result list and quit */ + if (weight_sum == 0) { + AST_LIST_APPEND_LIST(&newlist, &temp_list, list); + break; + } + + random_weight = 1 + (unsigned int) ((float) weight_sum * (ast_random() / ((float) RAND_MAX + 1.0))); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&temp_list, current, list) { + if (((struct ast_dns_srv_record *)current)->weight_sum < random_weight) { + continue; + } + + AST_LIST_MOVE_CURRENT(&newlist, list); + break; + } + AST_LIST_TRAVERSE_SAFE_END; + } + + } + + /* now that the new list has been ordered, + put it in place */ + + AST_LIST_APPEND_LIST(&result->records, &newlist, list); +} const char *ast_dns_srv_get_host(const struct ast_dns_record *record) { - return NULL; + struct ast_dns_srv_record *srv = (struct ast_dns_srv_record *) record; + + ast_assert(ast_dns_record_get_rr_type(record) == ns_t_srv); + return srv->host; } unsigned short ast_dns_srv_get_priority(const struct ast_dns_record *record) { - return 0; + struct ast_dns_srv_record *srv = (struct ast_dns_srv_record *) record; + + ast_assert(ast_dns_record_get_rr_type(record) == ns_t_srv); + return srv->priority; } unsigned short ast_dns_srv_get_weight(const struct ast_dns_record *record) { - return 0; + struct ast_dns_srv_record *srv = (struct ast_dns_srv_record *) record; + + ast_assert(ast_dns_record_get_rr_type(record) == ns_t_srv); + return srv->weight; } unsigned short ast_dns_srv_get_port(const struct ast_dns_record *record) { - return 0; -} + struct ast_dns_srv_record *srv = (struct ast_dns_srv_record *) record; + + ast_assert(ast_dns_record_get_rr_type(record) == ns_t_srv); + return srv->port; +} Modified: trunk/res/res_resolver_unbound.c URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_resolver_unbound.c?view=diff&rev=433889&r1=433888&r2=433889 ============================================================================== --- trunk/res/res_resolver_unbound.c (original) +++ trunk/res/res_resolver_unbound.c Wed Apr 1 11:27:48 2015 @@ -35,6 +35,10 @@ #include "asterisk/config.h" #include "asterisk/config_options.h" #include "asterisk/test.h" + +#ifdef TEST_FRAMEWORK +#include "asterisk/dns_srv.h" +#endif /*** DOCUMENTATION <configInfo name="res_resolver_unbound" language="en_US"> @@ -1181,6 +1185,74 @@ return AST_TEST_PASS; } + +AST_TEST_DEFINE(resolve_srv) +{ + RAII_VAR(struct unbound_resolver *, resolver, NULL, ao2_cleanup); + RAII_VAR(struct unbound_config *, cfg, NULL, ao2_cleanup); + RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free); + const struct ast_dns_record *record; + static const char *DOMAIN1 = "taco.bananas"; + static const char *DOMAIN1_SRV = "taco.bananas 12345 IN SRV 10 20 5060 sip.taco.bananas"; + enum ast_test_result_state res = AST_TEST_PASS; + + switch (cmd) { + case TEST_INIT: + info->name = "resolve_srv"; + info->category = "/res/res_resolver_unbound/"; + info->summary = "Test synchronous SRV resolution using libunbound\n"; + info->description = "This test performs the following:\n" + "\t* Set one SRV record on one domain\n" + "\t* Perform an SRV lookup on the domain\n" + "\t* Ensure that the SRV record returned matches the expected value\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + cfg = ao2_global_obj_ref(globals); + resolver = ao2_bump(cfg->global->state->resolver); + + ub_ctx_zone_add(resolver->context, DOMAIN1, "static"); + ub_ctx_data_add(resolver->context, DOMAIN1_SRV); + + if (ast_dns_resolve(DOMAIN1, ns_t_srv, ns_c_in, &result)) { + ast_test_status_update(test, "Failed to synchronously resolve SRV for domain '%s'\n", DOMAIN1); + res = AST_TEST_FAIL; + goto cleanup; + } + + record = ast_dns_result_get_records(result); + if (ast_dns_srv_get_priority(record) != 10) { + ast_test_status_update(test, "SRV Record returned priority '%d' when we expected 10\n", ast_dns_srv_get_priority(record)); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (ast_dns_srv_get_weight(record) != 20) { + ast_test_status_update(test, "SRV Record returned weight '%d' when we expected 20\n", ast_dns_srv_get_weight(record)); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (ast_dns_srv_get_port(record) != 5060) { + ast_test_status_update(test, "SRV Record returned port '%d' when we expected 5060\n", ast_dns_srv_get_port(record)); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (strcmp(ast_dns_srv_get_host(record), "sip.taco.bananas")) { + ast_test_status_update(test, "SRV Record returned host '%s' when we expected sip.taco.bananas\n", ast_dns_srv_get_host(record)); + res = AST_TEST_FAIL; + goto cleanup; + } + +cleanup: + ub_ctx_data_remove(resolver->context, DOMAIN1_SRV); + ub_ctx_zone_remove(resolver->context, DOMAIN1); + + return res; +} #endif static int reload_module(void) @@ -1202,6 +1274,7 @@ AST_TEST_UNREGISTER(resolve_sync_off_nominal); AST_TEST_UNREGISTER(resolve_sync_off_nominal); AST_TEST_UNREGISTER(resolve_cancel_off_nominal); + AST_TEST_UNREGISTER(resolve_srv); return 0; } @@ -1258,6 +1331,7 @@ AST_TEST_REGISTER(resolve_sync_off_nominal); AST_TEST_REGISTER(resolve_async_off_nominal); AST_TEST_REGISTER(resolve_cancel_off_nominal); + AST_TEST_REGISTER(resolve_srv); return AST_MODULE_LOAD_SUCCESS; } Added: trunk/tests/test_dns_srv.c URL: http://svnview.digium.com/svn/asterisk/trunk/tests/test_dns_srv.c?view=auto&rev=433889 ============================================================================== --- trunk/tests/test_dns_srv.c (added) +++ trunk/tests/test_dns_srv.c Wed Apr 1 11:27:48 2015 @@ -1,0 +1,697 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Digium, Inc. + * + * Joshua Colp <jc...@digium.com> + * Mark Michelson <mmichel...@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 "asterisk/test.h" +#include "asterisk/module.h" +#include "asterisk/dns_core.h" +#include "asterisk/dns_resolver.h" +#include "asterisk/dns_srv.h" + +#define DNS_HEADER_SIZE 96 + +const char DNS_HEADER[] = { + /* ID == 0 */ + 0x00, 0x00, + /* QR == 1, Opcode == 0, AA == 1, TC == 0, RD == 1 */ + 0x85, + /* RA == 1, Z == 0, RCODE == 0 */ + 0x80, + /* QDCOUNT == 1 */ + 0x00, 0x01, + /* ANCOUNT == 1 */ + 0x00, 0x00, + /* NSCOUNT == 0 */ + 0x00, 0x00, + /* ARCOUNT == 0 */ + 0x00, 0x00, +}; + +static int generate_dns_header(unsigned short num_records, char *buf) +{ + unsigned short net_num_records = htons(num_records); + + memcpy(buf, DNS_HEADER, ARRAY_LEN(DNS_HEADER)); + /* Overwrite the ANCOUNT with the actual number of answers */ + memcpy(&buf[6], &net_num_records, sizeof(num_records)); + + return ARRAY_LEN(DNS_HEADER); +} + +const char DNS_QUESTION [] = { + /* goose */ + 0x05, 0x67, 0x6f, 0x6f, 0x73, 0x65, + /* feathers */ + 0x08, 0x66, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x73, + /* end label */ + 0x00, + /* SRV type */ + 0x00, 0x23, + /* IN class */ + 0x00, 0x01, +}; + +static int generate_dns_question(char *buf) +{ + memcpy(buf, DNS_QUESTION, ARRAY_LEN(DNS_QUESTION)); + return ARRAY_LEN(DNS_QUESTION); +} + +const char SRV_ANSWER [] = { + /* Domain points to name from question */ + 0xc0, 0x0c, + /* NAPTR type */ + 0x00, 0x23, + /* IN Class */ + 0x00, 0x01, + /* TTL (12345 by default) */ + 0x00, 0x00, 0x30, 0x39, +}; + +static int generate_dns_answer(int ttl, char *buf) +{ + int net_ttl = htonl(ttl); + + memcpy(buf, SRV_ANSWER, ARRAY_LEN(SRV_ANSWER)); + /* Overwrite TTL if one is provided */ + if (ttl) { + memcpy(&buf[6], &net_ttl, sizeof(int)); + } + + return ARRAY_LEN(SRV_ANSWER); +} + +static int write_dns_string(const char *string, char *buf) +{ + uint8_t len = strlen(string); + buf[0] = len; + if (len) { + memcpy(&buf[1], string, len); + } + + return len + 1; +} + +static int write_dns_domain(const char *string, char *buf) +{ + char *copy = ast_strdupa(string); + char *part; + char *ptr = buf; + + while ((part = strsep(©, "."))) { + ptr += write_dns_string(part, ptr); + } + ptr += write_dns_string("", ptr); + + return ptr - buf; +} + +struct srv_record { + uint16_t priority; + uint16_t weight; + uint16_t port; + const char *host; + unsigned int ignore_priority; + unsigned int ignore_weight; + unsigned int ignore_port; + unsigned int ignore_host; +}; + +static int generate_srv_record(struct srv_record *record, char *buf) +{ + uint16_t priority = htons(record->priority); + uint16_t weight = htons(record->weight); + uint16_t port = htons(record->port); + char *ptr = buf; + + if (!record->ignore_priority) { + memcpy(ptr, &priority, sizeof(priority)); + ptr += sizeof(priority); + } + + if (!record->ignore_weight) { + memcpy(ptr, &weight, sizeof(weight)); + ptr += sizeof(weight); + } + + if (!record->ignore_port) { + memcpy(ptr, &port, sizeof(port)); + ptr += sizeof(port); + } + + if (!record->ignore_host) { + ptr += write_dns_domain(record->host, ptr); + } + + return ptr - buf; +} + +static struct srv_record *test_records; +static int num_test_records; +static char ans_buffer[1024]; + +static void *srv_thread(void *dns_query) +{ + struct ast_dns_query *query = dns_query; + int i; + char *ptr = ans_buffer; + + ptr += generate_dns_header(num_test_records, ptr); + ptr += generate_dns_question(ptr); + + for (i = 0; i < num_test_records; ++i) { + unsigned short rdlength; + unsigned short net_rdlength; + + ptr += generate_dns_answer(0, ptr); + rdlength = generate_srv_record(&test_records[i], ptr + 2); + net_rdlength = htons(rdlength); + memcpy(ptr, &net_rdlength, 2); + ptr += 2; + ptr += rdlength; + } + + ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "goose.feathers", ans_buffer, ptr - ans_buffer); + + for (i = 0; i < num_test_records; ++i) { + char record[128]; + ptr = record; + + ptr += generate_srv_record(&test_records[i], ptr); + ast_dns_resolver_add_record(query, ns_t_srv, ns_c_in, 12345, record, ptr - record); + } + + ast_dns_resolver_completed(query); + + ao2_ref(query, -1); + return NULL; +} + +static int srv_resolve(struct ast_dns_query *query) +{ + pthread_t thread; + + return ast_pthread_create_detached(&thread, NULL, srv_thread, ao2_bump(query)); +} + +static int srv_cancel(struct ast_dns_query *query) +{ + return -1; +} + +static struct ast_dns_resolver srv_resolver = { + .name = "srv_test", + .priority = 0, + .resolve = srv_resolve, + .cancel = srv_cancel, +}; + +static enum ast_test_result_state nominal_test(struct ast_test *test, struct srv_record *records, + int *srv_record_order, int num_records) +{ + RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free); + const struct ast_dns_record *record; + enum ast_test_result_state res = AST_TEST_PASS; + int i; + + test_records = records; + num_test_records = num_records; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + ast_dns_resolver_register(&srv_resolver); + + if (ast_dns_resolve("goose.feathers", ns_t_srv, ns_c_in, &result)) { + ast_test_status_update(test, "DNS resolution failed\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (!result) { + ast_test_status_update(test, "DNS resolution returned no result\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + i = 0; + for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) { + if (ast_dns_srv_get_priority(record) != records[srv_record_order[i]].priority) { + ast_test_status_update(test, "Unexpected priority in returned SRV record\n"); + res = AST_TEST_FAIL; + } + if (ast_dns_srv_get_weight(record) != records[srv_record_order[i]].weight) { + ast_test_status_update(test, "Unexpected weight in returned SRV record\n"); + res = AST_TEST_FAIL; + } + if (ast_dns_srv_get_port(record) != records[srv_record_order[i]].port) { + ast_test_status_update(test, "Unexpected port in returned SRV record\n"); + res = AST_TEST_FAIL; + } + if (strcmp(ast_dns_srv_get_host(record), records[srv_record_order[i]].host)) { + ast_test_status_update(test, "Unexpected host in returned SRV record\n"); + res = AST_TEST_FAIL; + } + ++i; + } + + if (i != num_records) { + ast_test_status_update(test, "Unexpected number of records returned in SRV lookup\n"); + res = AST_TEST_FAIL; + } + +cleanup: + + ast_dns_resolver_unregister(&srv_resolver); + + test_records = NULL; + num_test_records = 0; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + return res; +} + +AST_TEST_DEFINE(srv_resolve_single_record) +{ + struct srv_record records[] = { + { 10, 10, 5060, "goose.down" }, + }; + + int srv_record_order[] = { 0, }; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_single_record"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns a single record"; + info->description = "This test defines a single SRV record and performs a\n" + "resolution of the domain to which they belong. The test ensures that all\n" + "fields of the SRV record are parsed correctly\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return nominal_test(test, records, srv_record_order, ARRAY_LEN(records)); +} + +AST_TEST_DEFINE(srv_resolve_sort_priority) +{ + struct srv_record records[] = { + { 20, 10, 5060, "tacos" }, + { 10, 10, 5060, "goose.down" }, + }; + int srv_record_order[] = { 1, 0}; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_sort_priority"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns two records with differing priorities"; + info->description = "This test defines two SRV records with differing priorities and\n" + "performs a resolution of the domain to which they belong. The test ensures that\n" + "the two records are sorted according to priority and that all fields of the SRV\n" + "records are parsed correctly\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return nominal_test(test, records, srv_record_order, ARRAY_LEN(records)); +} + +AST_TEST_DEFINE(srv_resolve_same_priority_zero_weight) +{ + struct srv_record records[] = { + { 10, 0, 5060, "tacos" }, + { 10, 10, 5060, "goose.down" }, + }; + int srv_record_order[] = { 1, 0}; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_same_priority_zero_weight"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns two records with same priority but different weights"; + info->description = "This test defines two SRV records with same priority but different weights and\n" + "performs a resolution of the domain to which they belong. The test ensures that\n" + "the record with zero weight comes last and that all fields of the SRV\n" + "records are parsed correctly\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return nominal_test(test, records, srv_record_order, ARRAY_LEN(records)); +} + +AST_TEST_DEFINE(srv_resolve_same_priority_different_weights) +{ + struct srv_record records[] = { + { 10, 10, 5060, "tacos" }, + { 10, 20, 5060, "goose.down" }, + }; + + int srv_record_occurence[2] = { 0, }; + enum ast_test_result_state res = AST_TEST_PASS; + int count = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_same_priority_different_weights"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns two records with same priority but different weights"; + info->description = "This test defines two SRV records with same priority but different weights and\n" + "performs a resolution of the domain to which they belong. The test ensures that\n" + "the record with higher weight occurs more often than the one of lesser weight\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + test_records = records; + num_test_records = ARRAY_LEN(records); + + ast_dns_resolver_register(&srv_resolver); + + for (count = 0; count < 100; count++) { + struct ast_dns_result *result; + const struct ast_dns_record *record; + int i; + + memset(ans_buffer, 0, sizeof(ans_buffer)); + + if (ast_dns_resolve("goose.feathers", ns_t_srv, ns_c_in, &result)) { + ast_test_status_update(test, "DNS resolution failed\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (!result) { + ast_test_status_update(test, "DNS resolution returned no result\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + record = ast_dns_result_get_records(result); + for (i = 0; i < ARRAY_LEN(records); i++) { + if (ast_dns_srv_get_priority(record) != records[i].priority) { + continue; + } + if (ast_dns_srv_get_weight(record) != records[i].weight) { + continue; + } + if (ast_dns_srv_get_port(record) != records[i].port) { + continue; + } + if (strcmp(ast_dns_srv_get_host(record), records[i].host)) { + continue; + } + + srv_record_occurence[i]++; + break; + } + + ast_dns_result_free(result); + } + + if (srv_record_occurence[0] > srv_record_occurence[1]) { + ast_test_status_update(test, "SRV sorting resulted in lesser weight being returned more often\n"); + res = AST_TEST_FAIL; + } + +cleanup: + + ast_dns_resolver_unregister(&srv_resolver); + + test_records = NULL; + num_test_records = 0; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + return res; +} + +AST_TEST_DEFINE(srv_resolve_different_priorities_different_weights) +{ + struct srv_record records[] = { + { 10, 10, 5060, "tacos" }, + { 10, 20, 5060, "goose.down" }, + { 5, 80, 5060, "moo" }, + { 5, 10, 5060, "Canada" }, + }; + + int srv_record_priority[4] = { 5, 5, 10, 10 }; + int srv_record_occurence[4] = { 0, }; + enum ast_test_result_state res = AST_TEST_PASS; + int count = 0; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_different_priorities_different_weights"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns four records with different priority and different weights"; + info->description = "This test defines four SRV records, two with one priority and two with another priority,\n" + "and different weights and performs a resolution of the domain to which they belong.\n" + "The test ensures that the priorities are sorted properly and that the records with higher weight\n" + "occur more often than the ones of less weight.\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + test_records = records; + num_test_records = ARRAY_LEN(records); + + ast_dns_resolver_register(&srv_resolver); + + for (count = 0; count < 100; count++) { + struct ast_dns_result *result; + const struct ast_dns_record *record; + int i; + + memset(ans_buffer, 0, sizeof(ans_buffer)); + + if (ast_dns_resolve("goose.feathers", ns_t_srv, ns_c_in, &result)) { + ast_test_status_update(test, "DNS resolution failed\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (!result) { + ast_test_status_update(test, "DNS resolution returned no result\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + i = 0; + for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) { + if (ast_dns_srv_get_priority(record) != srv_record_priority[i]) { + ast_test_status_update(test, "Unexpected priority in returned SRV record\n"); + res = AST_TEST_FAIL; + } + i++; + } + + record = ast_dns_result_get_records(result); + for (i = 0; i < ARRAY_LEN(records); i++) { + if (ast_dns_srv_get_priority(record) != records[i].priority) { + continue; + } + if (ast_dns_srv_get_weight(record) != records[i].weight) { + continue; + } + if (ast_dns_srv_get_port(record) != records[i].port) { + continue; + } + if (strcmp(ast_dns_srv_get_host(record), records[i].host)) { + continue; + } + + srv_record_occurence[i]++; + break; + } + + ast_dns_result_free(result); + } + + if (srv_record_occurence[0] > srv_record_occurence[1]) { + ast_test_status_update(test, "SRV sorting resulted in lesser weight being returned more often for priority 10\n"); + res = AST_TEST_FAIL; + } + + if (srv_record_occurence[3] > srv_record_occurence[2]) { + ast_test_status_update(test, "SRV sorting resulted in lesser weight being returned more often for priority 5\n"); + res = AST_TEST_FAIL; + } + +cleanup: + + ast_dns_resolver_unregister(&srv_resolver); + + test_records = NULL; + num_test_records = 0; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + return res; +} + +static enum ast_test_result_state invalid_record_test(struct ast_test *test, struct srv_record *records, + int num_records) +{ + RAII_VAR(struct ast_dns_result *, result, NULL, ast_dns_result_free); + const struct ast_dns_record *record; + enum ast_test_result_state res = AST_TEST_PASS; + + test_records = records; + num_test_records = num_records; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + ast_dns_resolver_register(&srv_resolver); + + if (ast_dns_resolve("goose.feathers", ns_t_srv, ns_c_in, &result)) { + ast_test_status_update(test, "DNS resolution failed\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + if (!result) { + ast_test_status_update(test, "DNS resolution returned no result\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + + record = ast_dns_result_get_records(result); + if (record) { + ast_test_status_update(test, "Unexpected record returned from SRV query\n"); + res = AST_TEST_FAIL; + } + +cleanup: + + ast_dns_resolver_unregister(&srv_resolver); + + test_records = NULL; + num_test_records = 0; + memset(ans_buffer, 0, sizeof(ans_buffer)); + + return res; +} + +AST_TEST_DEFINE(srv_resolve_record_missing_weight_port_host) +{ + struct srv_record records[] = { + { 10, 10, 5060, "tacos.com", 0, 1, 1, 1 }, + }; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_record_missing_weight_port_host"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns a single invalid record"; + info->description = "This test defines a single SRV record and performs a\n" + "resolution of the domain to which they belong. The test ensures that the\n" + "record is determined to be corrupt as it contains only a priority\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return invalid_record_test(test, records, ARRAY_LEN(records)); +} + +AST_TEST_DEFINE(srv_resolve_record_missing_port_host) +{ + struct srv_record records[] = { + { 10, 10, 5060, "tacos.com", 0, 0, 1, 1 }, + }; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_record_missing_port_host"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns a single invalid record"; + info->description = "This test defines a single SRV record and performs a\n" + "resolution of the domain to which they belong. The test ensures that the\n" + "record is determined to be corrupt as it contains only a priority and weight\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return invalid_record_test(test, records, ARRAY_LEN(records)); +} + +AST_TEST_DEFINE(srv_resolve_record_missing_host) +{ + struct srv_record records[] = { + { 10, 10, 5060, "tacos.com", 0, 0, 0, 1 }, + }; + + switch (cmd) { + case TEST_INIT: + info->name = "srv_resolve_record_missing_host"; + info->category = "/main/dns/srv/"; + info->summary = "Test an SRV lookup which returns a single invalid record"; + info->description = "This test defines a single SRV record and performs a\n" + "resolution of the domain to which they belong. The test ensures that the\n" + "record is determined to be corrupt as it contains only a priority, weight,\n" + "and port\n"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return invalid_record_test(test, records, ARRAY_LEN(records)); +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(srv_resolve_single_record); + AST_TEST_UNREGISTER(srv_resolve_sort_priority); + AST_TEST_UNREGISTER(srv_resolve_same_priority_zero_weight); + AST_TEST_UNREGISTER(srv_resolve_same_priority_different_weights); + AST_TEST_UNREGISTER(srv_resolve_different_priorities_different_weights); + AST_TEST_UNREGISTER(srv_resolve_record_missing_weight_port_host); + AST_TEST_UNREGISTER(srv_resolve_record_missing_port_host); + AST_TEST_UNREGISTER(srv_resolve_record_missing_host); + + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(srv_resolve_single_record); + AST_TEST_REGISTER(srv_resolve_sort_priority); + AST_TEST_REGISTER(srv_resolve_same_priority_zero_weight); + AST_TEST_REGISTER(srv_resolve_same_priority_different_weights); + AST_TEST_REGISTER(srv_resolve_different_priorities_different_weights); + AST_TEST_REGISTER(srv_resolve_record_missing_weight_port_host); + AST_TEST_REGISTER(srv_resolve_record_missing_port_host); + AST_TEST_REGISTER(srv_resolve_record_missing_host); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DNS SRV Tests"); Propchange: trunk/tests/test_dns_srv.c ------------------------------------------------------------------------------ svn:eol-style = native Propchange: trunk/tests/test_dns_srv.c ------------------------------------------------------------------------------ svn:keywords = Author Date Id Revision Propchange: trunk/tests/test_dns_srv.c ------------------------------------------------------------------------------ svn:mime-type = text/plain -- _____________________________________________________________________ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- svn-commits mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/svn-commits