diff --git a/include/asterisk/dns_internal.h b/include/asterisk/dns_internal.h
index 48fa26471b4fe681767642a1d28764853ca41729..eb149245a17bc39ae79bebd9bc9a6e6d36e028dd 100644
--- a/include/asterisk/dns_internal.h
+++ b/include/asterisk/dns_internal.h
@@ -35,6 +35,7 @@ struct ast_dns_record {
 	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 @@ struct ast_dns_srv_record {
 	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 @@ struct ast_dns_result {
 	/*! \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 @@ struct ast_sched_context;
  * \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);
+
+
diff --git a/main/dns_core.c b/main/dns_core.c
index 394eaa514bedda9f38c7dfadbe410897fb01b04c..4fd80193ef08ae0c427ee17c1f7471ee493787b3 100644
--- a/main/dns_core.c
+++ b/main/dns_core.c
@@ -43,6 +43,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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 @@ int ast_dns_record_get_ttl(const struct ast_dns_record *record)
 
 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,10 +407,25 @@ int ast_dns_resolver_set_result(struct ast_dns_query *query, unsigned int secure
 	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)
 {
 	struct ast_dns_record *record;
@@ -444,7 +460,12 @@ int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr
 		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 @@ int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr
 	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 @@ int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr
 
 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);
 }
 
diff --git a/main/dns_srv.c b/main/dns_srv.c
index eeba9a6795cc7db860385817da82080378e5e0f2..7895073defef947367b61b74eff9228119e5d112 100644
--- a/main/dns_srv.c
+++ b/main/dns_srv.c
@@ -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;
-}
\ No newline at end of file
+	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;
+}
diff --git a/res/res_resolver_unbound.c b/res/res_resolver_unbound.c
index 43f2acdfd940381c9729dafe6cc10eb27cb6396c..94af28e7fb267de14192a1133f99e0dc2510bbe1 100644
--- a/res/res_resolver_unbound.c
+++ b/res/res_resolver_unbound.c
@@ -36,6 +36,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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">
 		<configFile name="resolver_unbound.conf">
@@ -1181,6 +1185,74 @@ AST_TEST_DEFINE(resolve_cancel_off_nominal)
 
 	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 @@ static int unload_module(void)
 	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 @@ static int load_module(void)
 	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;
 }
diff --git a/tests/test_dns_srv.c b/tests/test_dns_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..de79be1d94791b1868e875b3ef3138665ae50863
--- /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(&copy, "."))) {
+		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");