diff --git a/include/asterisk/dns_internal.h b/include/asterisk/dns_internal.h
index e42e3f7d87c042a1affc68337c7c8f79700e72cb..d518f90669c11ed75ff7838e88135cb668f4ec7c 100644
--- a/include/asterisk/dns_internal.h
+++ b/include/asterisk/dns_internal.h
@@ -195,12 +195,49 @@ void dns_naptr_sort(struct ast_dns_result *result);
  * \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);
+struct ast_dns_record *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);
+void dns_srv_sort(struct ast_dns_result *result);
 
+/*!
+ * \brief Find the location of a DNS record within the entire DNS answer
+ *
+ * The DNS record that has been returned by the resolver may be a copy of the record that was
+ * found in the complete DNS response. If so, then some DNS record types (specifically those that
+ * parse domains) will need to locate the DNS record within the complete DNS response. This is so
+ * that if the domain contains pointers to other sections of the DNS response, then the referenced
+ * domains may be located.
+ *
+ * \param record The DNS record returned by a resolver implementation
+ * \param record_size The size of the DNS record in bytes
+ * \param response The complete DNS answer
+ * \param response_size The size of the complete DNS response
+ */
+char *dns_find_record(const char *record, size_t record_size, const char *response, size_t response_size);
+
+/*!
+ * \brief Parse a 16-bit unsigned value from a DNS record
+ *
+ * \param cur Pointer to the location of the 16-bit value in the DNS record
+ * \param[out] val The parsed 16-bit unsigned integer
+ * \return The number of bytes consumed while parsing
+ */
+int dns_parse_short(unsigned char *cur, uint16_t *val);
+
+/*!
+ * \brief Parse a DNS string from a DNS record
+ *
+ * A DNS string consists of an 8-bit size, followed by the
+ * string value (not NULL-terminated).
+ *
+ * \param cur Pointer to the location of the DNS string
+ * \param[out] size The parsed size of the DNS string
+ * \param[out] val The contained string (not NULL-terminated)
+ * \return The number of bytes consumed while parsing
+ */
+int dns_parse_string(char *cur, uint8_t *size, char **val);
diff --git a/include/asterisk/dns_test.h b/include/asterisk/dns_test.h
new file mode 100644
index 0000000000000000000000000000000000000000..0858bab33205c424e2874eecf5e257308b3ecf8d
--- /dev/null
+++ b/include/asterisk/dns_test.h
@@ -0,0 +1,109 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * 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.
+ */
+
+#ifndef DNS_TEST_H
+#define DNS_TEST_H
+
+/*!
+ * \brief Representation of a string in DNS
+ *
+ * In DNS, a string has a byte to indicate the length,
+ * followed by a series of bytes representing the string.
+ * DNS does not NULL-terminate its strings. However, the
+ * string stored in this structure is expected to be NULL-
+ * terminated.
+ */
+struct ast_dns_test_string {
+	uint8_t len;
+	const char *val;
+};
+
+/*!
+ * \brief Write a DNS string to a buffer
+ *
+ * This writes the DNS string to the buffer and returns the total
+ * number of bytes written to the buffer.
+ *
+ * There is no buffer size passed to this function. Tests are expected to
+ * use a buffer that is sufficiently large for their tests.
+ *
+ * \param string The string to write
+ * \param buf The buffer to write the string into
+ * \return The number of bytes written to the buffer
+ */
+int ast_dns_test_write_string(const struct ast_dns_test_string *string, char *buf);
+
+/*!
+ * \brief Write a DNS domain to a buffer
+ *
+ * A DNS domain consists of a series of labels separated
+ * by dots. Each of these labels gets written as a DNS
+ * string. A DNS domain ends with a NULL label, which is
+ * essentially a zero-length DNS string.
+ *
+ * There is no buffer size passed to this function. Tests are expected to
+ * use a buffer that is sufficiently large for their tests.
+ *
+ * \param string The DNS domain to write
+ * \param buf The buffer to write the domain into
+ * \return The number of bytes written to the buffer
+ */
+int ast_dns_test_write_domain(const char *string, char *buf);
+
+/*!
+ * \brief Callback to write specific DNS record to an answer
+ *
+ * When generating a DNS result, the type of DNS record being generated
+ * will need to be performed by individual test cases. This is a callback
+ * that tests can define to write a specific type of DNS record to the
+ * provided buffer.
+ *
+ * There is no buffer size passed to this function. Tests are expected to
+ * use a buffer that is sufficiently large for their tests.
+ *
+ * \param record Pointer to test-specific DNS record data
+ * \param buf The buffer into which to write the DNS record
+ * \return The number of bytes written to the buffer
+ */
+typedef int (*record_fn)(void *record, char *buf);
+
+/*!
+ * \brief Generate a full DNS response for the given DNS records.
+ *
+ * This function takes care of generating the DNS header, question, and
+ * answer sections of a DNS response. In order to place test-specific
+ * record data into the DNS answers, a callback is provided as a parameter
+ * to this function so that the necessary records can be encoded properly
+ * by the tests.
+ *
+ * There is no buffer size passed to this function. Tests are expected to
+ * use a buffer that is sufficiently large for their tests.
+ *
+ * \param query The DNS query that is being processed
+ * \param records An array of test-specific representations of DNS records
+ * \param num_records The number of elements in the records array
+ * \param record_size The size of each element in the records array
+ * \param generate The test-specific encoder for DNS records
+ * \param buffer The buffer into which to write the DNS response
+ */
+int ast_dns_test_generate_result(struct ast_dns_query *query, void *records, size_t num_records,
+		size_t record_size, record_fn generate, char *buffer);
+
+#endif /* DNS_TEST_H */
diff --git a/main/dns_core.c b/main/dns_core.c
index 228147bbfc6adb6dbcd908732be94888e65ed61c..53ea1d09eabce75536adf557cace357e753c9a06 100644
--- a/main/dns_core.c
+++ b/main/dns_core.c
@@ -426,6 +426,20 @@ static struct ast_dns_record *generic_record_alloc(struct ast_dns_query *query,
 	return record;
 }
 
+typedef struct ast_dns_record *(*dns_alloc_fn)(struct ast_dns_query *query, const char *data, const size_t size);
+
+static dns_alloc_fn dns_alloc_table [] = {
+	[ns_t_naptr] = dns_naptr_alloc,
+	[ns_t_srv] = dns_srv_alloc,
+};
+
+static struct ast_dns_record *allocate_dns_record(int rr_type, struct ast_dns_query *query, const char *data, const size_t size)
+{
+	dns_alloc_fn allocator = dns_alloc_table[rr_type] ?: generic_record_alloc;
+
+	return allocator(query, data, size);
+}
+
 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;
@@ -460,14 +474,7 @@ int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr
 		return -1;
 	}
 
-	if (rr_type == ns_t_naptr) {
-		record = dns_naptr_alloc(query, data, size);
-	} else if (rr_type == ns_t_srv) {
-		record = ast_dns_srv_alloc(query, data, size);
-	} else {
-		record = generic_record_alloc(query, data, size);
-	}
-
+	record = allocate_dns_record(rr_type, query, data, size);
 	if (!record) {
 		return -1;
 	}
@@ -483,13 +490,23 @@ int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr
 	return 0;
 }
 
-void ast_dns_resolver_completed(struct ast_dns_query *query)
+typedef void (*dns_sort_fn)(struct ast_dns_result *result);
+
+static dns_sort_fn dns_sort_table [] = {
+	[ns_t_naptr] = dns_naptr_sort,
+	[ns_t_srv] = dns_srv_sort,
+};
+
+static void sort_result(int rr_type, struct ast_dns_result *result)
 {
-	if (ast_dns_query_get_rr_type(query) == ns_t_naptr) {
-		dns_naptr_sort(query->result);
-	} else if (ast_dns_query_get_rr_type(query) == ns_t_srv) {
-		ast_dns_srv_sort(query->result);
+	if (dns_sort_table[rr_type]) {
+		dns_sort_table[rr_type](result);
 	}
+}
+
+void ast_dns_resolver_completed(struct ast_dns_query *query)
+{
+	sort_result(ast_dns_query_get_rr_type(query), query->result);
 
 	query->callback(query);
 }
@@ -593,3 +610,43 @@ void ast_dns_resolver_unregister(struct ast_dns_resolver *resolver)
 
 	ast_verb(2, "Unregistered DNS resolver '%s'\n", resolver->name);
 }
+
+char *dns_find_record(const char *record, size_t record_size, const char *response, size_t response_size)
+{
+	size_t remaining_size = response_size;
+	const char *search_base = response;
+	char *record_offset;
+
+	while (1) {
+		record_offset = memchr(search_base, record[0], remaining_size);
+
+		ast_assert(record_offset != NULL);
+		ast_assert(search_base + remaining_size - record_offset >= record_size);
+
+		if (!memcmp(record_offset, record, record_size)) {
+			return record_offset;
+		}
+
+		remaining_size -= record_offset - search_base;
+		search_base = record_offset + 1;
+	}
+}
+
+int dns_parse_short(unsigned char *cur, uint16_t *val)
+{
+	/* This assignment takes a big-endian 16-bit value and stores it in the
+	 * machine's native byte order. Using this method allows us to avoid potential
+	 * alignment issues in case the order is not on a short-addressable boundary.
+	 * See http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for
+	 * more information
+	 */
+	*val = (cur[1] << 0) | (cur[0] << 8);
+	return sizeof(*val);
+}
+
+int dns_parse_string(char *cur, uint8_t *size, char **val)
+{
+	*size = *cur++;
+	*val = cur;
+	return *size + 1;
+}
diff --git a/main/dns_naptr.c b/main/dns_naptr.c
index 7e870002f5f9327e70bfcbba5a8e6a69b64ad21f..72f28dc81a5ae75a8e869b157f9999b582efcd72 100644
--- a/main/dns_naptr.c
+++ b/main/dns_naptr.c
@@ -392,54 +392,10 @@ struct ast_dns_record *dns_naptr_alloc(struct ast_dns_query *query, const char *
 	char *regexp;
 	char replacement[256] = "";
 	int replacement_size;
-	char *naptr_offset;
-	char *naptr_search_base = (char *)query->result->answer;
-	size_t remaining_size = query->result->answer_size;
-	char *end_of_record;
+	const char *end_of_record;
 	enum flags_result flags_res;
 
-	/*
-	 * This is bordering on the hackiest thing I've ever written.
-	 * Part of parsing a NAPTR record is to parse a potential replacement
-	 * domain name. Decoding this domain name requires the use of the
-	 * dn_expand() function. This function requires that the domain you
-	 * pass in be a pointer to within the full DNS answer. Unfortunately,
-	 * libunbound gives its RRs back as copies of data from the DNS answer
-	 * instead of pointers to within the DNS answer. This means that in order
-	 * to be able to parse the domain name correctly, I need to find the
-	 * current NAPTR record inside the DNS answer and operate on it. This
-	 * loop is designed to find the current NAPTR record within the full
-	 * DNS answer and set the "ptr" variable to the beginning of the
-	 * NAPTR RDATA
-	 */
-	while (1) {
-		naptr_offset = memchr(naptr_search_base, data[0], remaining_size);
-
-		/* Since the NAPTR record we have been given came from the DNS answer,
-		 * we should never run into a situation where we can't find ourself
-		 * in the answer
-		 */
-		ast_assert(naptr_offset != NULL);
-		ast_assert(naptr_search_base + remaining_size - naptr_offset >= size);
-
-		/* ... but just to be on the safe side, let's be sure we can break
-		 * out if the assertion doesn't hold
-		 */
-		if (!naptr_offset || naptr_search_base + remaining_size - naptr_offset < size) {
-			ast_log(LOG_ERROR, "Failed to locate NAPTR record within DNS result\n");
-			return NULL;
-		}
-
-		if (!memcmp(naptr_offset, data, size)) {
-			/* BAM! FOUND IT! */
-			ptr = naptr_offset;
-			break;
-		}
-		/* Data didn't match us, so keep looking */
-		remaining_size -= naptr_offset - naptr_search_base;
-		naptr_search_base = naptr_offset + 1;
-	}
-
+	ptr = dns_find_record(data, size, query->result->answer, query->result->answer_size);
 	ast_assert(ptr != NULL);
 
 	end_of_record = ptr + size;
@@ -451,53 +407,31 @@ struct ast_dns_record *dns_naptr_alloc(struct ast_dns_query *query, const char *
 	 * See http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for
 	 * more information
 	 */
-	order = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8);
-	ptr += 2;
-
+	ptr += dns_parse_short((unsigned char *) ptr, &order);
 	if (PAST_END_OF_RECORD) {
 		return NULL;
 	}
 
 	/* PREFERENCE */
-	preference = ((unsigned char) (ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8);
-	ptr += 2;
-
+	ptr += dns_parse_short((unsigned char *) ptr, &preference);
 	if (PAST_END_OF_RECORD) {
 		return NULL;
 	}
 
 	/* FLAGS */
-	flags_size = *ptr;
-	++ptr;
-	if (PAST_END_OF_RECORD) {
-		return NULL;
-	}
-	flags = ptr;
-	ptr += flags_size;
+	ptr += dns_parse_string(ptr, &flags_size, &flags);
 	if (PAST_END_OF_RECORD) {
 		return NULL;
 	}
 
 	/* SERVICES */
-	services_size = *ptr;
-	++ptr;
-	if (PAST_END_OF_RECORD) {
-		return NULL;
-	}
-	services = ptr;
-	ptr += services_size;
+	ptr += dns_parse_string(ptr, &services_size, &services);
 	if (PAST_END_OF_RECORD) {
 		return NULL;
 	}
 
 	/* REGEXP */
-	regexp_size = *ptr;
-	++ptr;
-	if (PAST_END_OF_RECORD) {
-		return NULL;
-	}
-	regexp = ptr;
-	ptr += regexp_size;
+	ptr += dns_parse_string(ptr, &regexp_size, &regexp);
 	if (PAST_END_OF_RECORD) {
 		return NULL;
 	}
diff --git a/main/dns_srv.c b/main/dns_srv.c
index 7895073defef947367b61b74eff9228119e5d112..a617ede4d7fcffc6abdd7b1ac0003bb967ce9bba 100644
--- a/main/dns_srv.c
+++ b/main/dns_srv.c
@@ -41,59 +41,36 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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)
+struct ast_dns_record *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;
-	}
-
+	ptr = dns_find_record(data, size, query->result->answer, query->result->answer_size);
 	ast_assert(ptr != NULL);
 
 	end_of_record = ptr + size;
 
 	/* PRIORITY */
-	priority = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8);
-	ptr += 2;
-
+	ptr += dns_parse_short((unsigned char *) ptr, &priority);
 	if (ptr >= end_of_record) {
 		return NULL;
 	}
 
 	/* WEIGHT */
-	weight = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8);
-	ptr += 2;
-
+	ptr += dns_parse_short((unsigned char *) ptr, &weight);
 	if (ptr >= end_of_record) {
 		return NULL;
 	}
 
 	/* PORT */
-	port = ((unsigned char)(ptr[1]) << 0) | ((unsigned char)(ptr[0]) << 8);
-	ptr += 2;
-
+	ptr += dns_parse_short((unsigned char *) ptr, &port);
 	if (ptr >= end_of_record) {
 		return NULL;
 	}
@@ -129,7 +106,7 @@ struct ast_dns_record *ast_dns_srv_alloc(struct ast_dns_query *query, const char
 /* 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)
+void dns_srv_sort(struct ast_dns_result *result)
 {
 	struct ast_dns_record *current;
 	struct dns_records newlist = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
diff --git a/main/dns_test.c b/main/dns_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d61692590de260da91757cd2c65af36f068844d
--- /dev/null
+++ b/main/dns_test.c
@@ -0,0 +1,265 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * Includes code and algorithms from the Zapata library.
+ *
+ * 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
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "asterisk/dns_core.h"
+#include "asterisk/dns_test.h"
+#include "asterisk/utils.h"
+
+#ifdef TEST_FRAMEWORK
+
+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,
+};
+
+/*!
+ * \brief Generate a DNS header and write it to a buffer
+ *
+ * The DNS header is the first part of a DNS request or response. In our
+ * case, the only part of the header that a test can affect is the number
+ * of answers. The rest of the DNS header is based on hard-coded values.
+ *
+ * There is no buffer size passed to this function since we provide
+ * the data ourselves and have sized the buffer to be way larger
+ * than necessary for the tests.
+ *
+ * \param num_records The number of DNS records in this DNS response
+ * \param buf The buffer to write the header into
+ * \retval The number of bytes written to the buffer
+ */
+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,
+	/* NAPTR type */
+	0x00, 0x23,
+	/* IN class */
+	0x00, 0x01,
+};
+
+/*!
+ * \brief Generate a DNS question and write it to a buffer
+ *
+ * The DNS question is the second part of a DNS request or response.
+ * All DNS questions in this file are for the same domain and thus
+ * the DNS question is a hard-coded value.
+ *
+ * There is no buffer size passed to this function since we provide
+ * the data ourselves and have sized the buffer to be way larger
+ * than necessary for the tests.
+ *
+ * \param buf The buffer to write the question into
+ * \retval The number of bytes written to the buffer
+ */
+static int generate_dns_question(char *buf)
+{
+	memcpy(buf, DNS_QUESTION, ARRAY_LEN(DNS_QUESTION));
+	return ARRAY_LEN(DNS_QUESTION);
+}
+
+const char NAPTR_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,
+};
+
+/*!
+ * \brief Generate a DNS answer and write it to a buffer
+ *
+ * The DNS answer is the third (and in our case final) part of a
+ * DNS response. The DNS answer generated here is only partial.
+ * The record-specific data is generated by a separate function.
+ * DNS answers in our tests may have variable TTLs, but the rest
+ * is hard-coded.
+ *
+ * There is no buffer size passed to this function since we provide
+ * the data ourselves and have sized the buffer to be way larger
+ * than necessary for the tests.
+ *
+ * \param buf The buffer to write the answer into
+ * \retval The number of bytes written to the buffer
+ */
+static int generate_dns_answer(int ttl, char *buf)
+{
+	int net_ttl = htonl(ttl);
+
+	memcpy(buf, NAPTR_ANSWER, ARRAY_LEN(NAPTR_ANSWER));
+	/* Overwrite TTL if one is provided */
+	if (ttl) {
+		memcpy(&buf[6], &net_ttl, sizeof(int));
+	}
+
+	return ARRAY_LEN(NAPTR_ANSWER);
+}
+
+/*!
+ * \brief Write a DNS string to a buffer
+ *
+ * This writes the DNS string to the buffer and returns the total
+ * number of bytes written to the buffer.
+ *
+ * There is no buffer size passed to this function since we provide
+ * the data ourselves and have sized the buffer to be way larger
+ * than necessary for the tests.
+ *
+ * \param string The string to write
+ * \param buf The buffer to write the string into
+ * \return The number of bytes written to the buffer
+ */
+int ast_dns_test_write_string(const struct ast_dns_test_string *string, char *buf)
+{
+	uint8_t len = string->len;
+	size_t actual_len = strlen(string->val);
+	buf[0] = len;
+	/*
+	 * We use the actual length of the string instead of
+	 * the stated value since sometimes we're going to lie about
+	 * the length of the string
+	 */
+	if (actual_len) {
+		memcpy(&buf[1], string->val, strlen(string->val));
+	}
+
+	return actual_len + 1;
+}
+
+/*!
+ * \brief Write a DNS domain to a buffer
+ *
+ * A DNS domain consists of a series of labels separated
+ * by dots. Each of these labels gets written as a DNS
+ * string. A DNS domain ends with a NULL label, which is
+ * essentially a zero-length DNS string.
+ *
+ *
+ * There is no buffer size passed to this function since we provide
+ * the data ourselves and have sized the buffer to be way larger
+ * than necessary for the tests.
+ *
+ * \param string The DNS domain to write
+ * \param buf The buffer to write the domain into
+ * \return The number of bytes written to the buffer
+ */
+int ast_dns_test_write_domain(const char *string, char *buf)
+{
+	char *copy = ast_strdupa(string);
+	char *part;
+	char *ptr = buf;
+	static const struct ast_dns_test_string null_label = {
+		.len = 0,
+		.val = "",
+	};
+
+	while (1) {
+		struct ast_dns_test_string dns_str;
+		part = strsep(&copy, ".");
+		if (ast_strlen_zero(part)) {
+			break;
+		}
+		dns_str.len = strlen(part);
+		dns_str.val = part;
+
+		ptr += ast_dns_test_write_string(&dns_str, ptr);
+	}
+	ptr += ast_dns_test_write_string(&null_label, ptr);
+
+	return ptr - buf;
+}
+
+int ast_dns_test_generate_result(struct ast_dns_query *query, void *records, size_t num_records,
+		size_t record_size, record_fn generate, char *buffer)
+{
+	char *ptr = buffer;
+	char *record_iter;
+
+	ptr += generate_dns_header(num_records, ptr);
+	ptr += generate_dns_question(ptr);
+
+	for (record_iter = records; record_iter < (char *) records + num_records * record_size; record_iter += record_size) {
+		unsigned short rdlength;
+		unsigned short net_rdlength;
+
+		/* XXX Do we even want to override TTL? */
+		ptr += generate_dns_answer(0, ptr);
+		rdlength = generate(record_iter, ptr + 2);
+		net_rdlength = htons(rdlength);
+		memcpy(ptr, &net_rdlength, 2);
+		ptr += 2;
+		ptr += rdlength;
+	}
+
+	return ptr - buffer;
+}
+
+#else /* TEST_FRAMEWORK */
+
+int ast_dns_test_write_string(struct ast_dns_test_string *string, char *buf)
+{
+	return 0;
+}
+
+int ast_dns_test_write_domain(const char *string, char *buf)
+{
+	return 0;
+}
+
+int ast_dns_test_generate_result(struct ast_dns_query *query, void *records, size_t num_records,
+		size_t record_size, record_fn generate, char *buffer)
+{
+	return 0;
+}
+
+#endif
diff --git a/tests/test_dns_naptr.c b/tests/test_dns_naptr.c
index 1aaac674b7e5245921a6d4682223edba11c591f7..c7c7eb3645c112e873e76ea92c45edf222d6c767 100644
--- a/tests/test_dns_naptr.c
+++ b/tests/test_dns_naptr.c
@@ -30,217 +30,15 @@
 #include "asterisk/dns_core.h"
 #include "asterisk/dns_resolver.h"
 #include "asterisk/dns_naptr.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,
-};
-
-/*!
- * \brief Generate a DNS header and write it to a buffer
- *
- * The DNS header is the first part of a DNS request or response. In our
- * case, the only part of the header that a test can affect is the number
- * of answers. The rest of the DNS header is based on hard-coded values.
- *
- * There is no buffer size passed to this function since we provide
- * the data ourselves and have sized the buffer to be way larger
- * than necessary for the tests.
- *
- * \param num_records The number of DNS records in this DNS response
- * \param buf The buffer to write the header into
- * \retval The number of bytes written to the buffer
- */
-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,
-	/* NAPTR type */
-	0x00, 0x23,
-	/* IN class */
-	0x00, 0x01,
-};
-
-/*!
- * \brief Generate a DNS question and write it to a buffer
- *
- * The DNS question is the second part of a DNS request or response.
- * All DNS questions in this file are for the same domain and thus
- * the DNS question is a hard-coded value.
- *
- * There is no buffer size passed to this function since we provide
- * the data ourselves and have sized the buffer to be way larger
- * than necessary for the tests.
- *
- * \param buf The buffer to write the question into
- * \retval The number of bytes written to the buffer
- */
-static int generate_dns_question(char *buf)
-{
-	memcpy(buf, DNS_QUESTION, ARRAY_LEN(DNS_QUESTION));
-	return ARRAY_LEN(DNS_QUESTION);
-}
-
-const char NAPTR_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,
-};
-
-/*!
- * \brief Generate a DNS answer and write it to a buffer
- *
- * The DNS answer is the third (and in our case final) part of a
- * DNS response. The DNS answer generated here is only partial.
- * The record-specific data is generated by a separate function.
- * DNS answers in our tests may have variable TTLs, but the rest
- * is hard-coded.
- *
- * There is no buffer size passed to this function since we provide
- * the data ourselves and have sized the buffer to be way larger
- * than necessary for the tests.
- *
- * \param buf The buffer to write the answer into
- * \retval The number of bytes written to the buffer
- */
-static int generate_dns_answer(int ttl, char *buf)
-{
-	int net_ttl = htonl(ttl);
-
-	memcpy(buf, NAPTR_ANSWER, ARRAY_LEN(NAPTR_ANSWER));
-	/* Overwrite TTL if one is provided */
-	if (ttl) {
-		memcpy(&buf[6], &net_ttl, sizeof(int));
-	}
-
-	return ARRAY_LEN(NAPTR_ANSWER);
-}
-
-/*!
- * \brief Representation of a string in DNS
- *
- * In DNS, a string has a byte to indicate the length,
- * followed by a series of bytes representing the string.
- * DNS does not NULL-terminate its strings.
- */
-struct dns_string {
-	uint8_t len;
-	const char *val;
-};
-
-/*!
- * \brief Write a DNS string to a buffer
- *
- * This writes the DNS string to the buffer and returns the total
- * number of bytes written to the buffer.
- *
- * There is no buffer size passed to this function since we provide
- * the data ourselves and have sized the buffer to be way larger
- * than necessary for the tests.
- *
- * \param string The string to write
- * \param buf The buffer to write the string into
- * \return The number of bytes written to the buffer
- */
-static int write_dns_string(const struct dns_string *string, char *buf)
-{
-	uint8_t len = string->len;
-	buf[0] = len;
-	/*
-	 * We use the actual length of the string instead of
-	 * the stated value since sometimes we're going to lie about
-	 * the length of the string
-	 */
-	if (strlen(string->val)) {
-		memcpy(&buf[1], string->val, strlen(string->val));
-	}
-
-	return strlen(string->val) + 1;
-}
-
-/*!
- * \brief Write a DNS domain to a buffer
- *
- * A DNS domain consists of a series of labels separated
- * by dots. Each of these labels gets written as a DNS
- * string. A DNS domain ends with a NULL label, which is
- * essentially a zero-length DNS string.
- *
- *
- * There is no buffer size passed to this function since we provide
- * the data ourselves and have sized the buffer to be way larger
- * than necessary for the tests.
- *
- * \param string The DNS domain to write
- * \param buf The buffer to write the domain into
- * \return The number of bytes written to the buffer
- */
-static int write_dns_domain(const char *string, char *buf)
-{
-	char *copy = ast_strdupa(string);
-	char *part;
-	char *ptr = buf;
-	static const struct dns_string null_label = {
-		.len = 0,
-		.val = "",
-	};
-
-	while (1) {
-		struct dns_string dns_str;
-		part = strsep(&copy, ".");
-		if (ast_strlen_zero(part)) {
-			break;
-		}
-		dns_str.len = strlen(part);
-		dns_str.val = part;
-
-		ptr += write_dns_string(&dns_str, ptr);
-	}
-	ptr += write_dns_string(&null_label, ptr);
-
-	return ptr - buf;
-}
+#include "asterisk/dns_test.h"
 
 struct naptr_record {
 	uint16_t order;
 	uint16_t preference;
-	struct dns_string flags;
-	struct dns_string services;
-	struct dns_string regexp;
-	const char * replacement;
+	struct ast_dns_test_string flags;
+	struct ast_dns_test_string services;
+	struct ast_dns_test_string regexp;
+	const char *replacement;
 };
 
 /*!
@@ -257,8 +55,9 @@ struct naptr_record {
  * \param buf The buffer to write the record into
  * \return The number of bytes written to the buffer
  */
-static int generate_naptr_record(struct naptr_record *record, char *buf)
+static int generate_naptr_record(void *dns_record, char *buf)
 {
+	struct naptr_record *record = dns_record;
 	uint16_t net_order = htons(record->order);
 	uint16_t net_preference = htons(record->preference);
 	char *ptr = buf;
@@ -269,10 +68,10 @@ static int generate_naptr_record(struct naptr_record *record, char *buf)
 	memcpy(ptr, &net_preference, sizeof(net_preference));
 	ptr += sizeof(net_preference);
 
-	ptr += write_dns_string(&record->flags, ptr);
-	ptr += write_dns_string(&record->services, ptr);
-	ptr += write_dns_string(&record->regexp, ptr);
-	ptr += write_dns_domain(record->replacement, ptr);
+	ptr += ast_dns_test_write_string(&record->flags, ptr);
+	ptr += ast_dns_test_write_string(&record->services, ptr);
+	ptr += ast_dns_test_write_string(&record->regexp, ptr);
+	ptr += ast_dns_test_write_domain(record->replacement, ptr);
 
 	return ptr - buf;
 }
@@ -312,31 +111,19 @@ static void *naptr_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);
+	int ans_size;
 
-	for (i = 0; i < num_test_records; ++i) {
-		unsigned short rdlength;
-		unsigned short net_rdlength;
-
-		ptr += generate_dns_answer(0, ptr);
-		rdlength = generate_naptr_record(&test_records[i], ptr + 2);
-		net_rdlength = htons(rdlength);
-		memcpy(ptr, &net_rdlength, 2);
-		ptr += 2;
-		ptr += rdlength;
-	}
+	ans_size = ast_dns_test_generate_result(query, test_records, num_test_records,
+			sizeof(struct naptr_record), generate_naptr_record, ans_buffer);
 
-	ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "goose.feathers", ans_buffer, ptr - ans_buffer);
+	ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "goose.feathers", ans_buffer, ans_size);
 
 	for (i = 0; i < num_test_records; ++i) {
 		char record[128];
-		ptr = record;
+		int naptr_size;
 
-		ptr += generate_naptr_record(&test_records[i], ptr);
-		ast_dns_resolver_add_record(query, ns_t_naptr, ns_c_in, 12345, record, ptr - record);
+		naptr_size = generate_naptr_record(&test_records[i], record);
+		ast_dns_resolver_add_record(query, ns_t_naptr, ns_c_in, 12345, record, naptr_size);
 	}
 
 	ast_dns_resolver_completed(query);
diff --git a/tests/test_dns_srv.c b/tests/test_dns_srv.c
index de79be1d94791b1868e875b3ef3138665ae50863..c5145bb2ecb7d6407bf0a8229e1eb4907585e6c6 100644
--- a/tests/test_dns_srv.c
+++ b/tests/test_dns_srv.c
@@ -31,104 +31,7 @@
 #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;
-}
+#include "asterisk/dns_test.h"
 
 struct srv_record {
 	uint16_t priority;
@@ -141,8 +44,9 @@ struct srv_record {
 	unsigned int ignore_host;
 };
 
-static int generate_srv_record(struct srv_record *record, char *buf)
+static int generate_srv_record(void *dns_record, char *buf)
 {
+	struct srv_record *record = dns_record;
 	uint16_t priority = htons(record->priority);
 	uint16_t weight = htons(record->weight);
 	uint16_t port = htons(record->port);
@@ -164,7 +68,7 @@ static int generate_srv_record(struct srv_record *record, char *buf)
 	}
 
 	if (!record->ignore_host) {
-		ptr += write_dns_domain(record->host, ptr);
+		ptr += ast_dns_test_write_domain(record->host, ptr);
 	}
 
 	return ptr - buf;
@@ -178,31 +82,19 @@ 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);
+	int ans_size;
 
-	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;
-	}
+	ans_size = ast_dns_test_generate_result(query, test_records, num_test_records,
+			sizeof(struct srv_record), generate_srv_record, ans_buffer);
 
-	ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "goose.feathers", ans_buffer, ptr - ans_buffer);
+	ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "goose.feathers", ans_buffer, ans_size);
 
 	for (i = 0; i < num_test_records; ++i) {
 		char record[128];
-		ptr = record;
+		int srv_size;
 
-		ptr += generate_srv_record(&test_records[i], ptr);
-		ast_dns_resolver_add_record(query, ns_t_srv, ns_c_in, 12345, record, ptr - record);
+		srv_size = generate_srv_record(&test_records[i], record);
+		ast_dns_resolver_add_record(query, ns_t_srv, ns_c_in, 12345, record, srv_size);
 	}
 
 	ast_dns_resolver_completed(query);