diff options
Diffstat (limited to 'tests/test_dns_srv.c')
-rw-r--r-- | tests/test_dns_srv.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/tests/test_dns_srv.c b/tests/test_dns_srv.c new file mode 100644 index 000000000..de79be1d9 --- /dev/null +++ b/tests/test_dns_srv.c @@ -0,0 +1,697 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Digium, Inc. + * + * Joshua Colp <jcolp@digium.com> + * 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 "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"); |