[asterisk-dev] Change in asterisk[master]: res_pjsip: Add external PJSIP resolver implementation using ...

Joshua Colp (Code Review) asteriskteam at digium.com
Mon Apr 13 10:33:43 CDT 2015


Joshua Colp has uploaded a new change for review.

  https://gerrit.asterisk.org/75

Change subject: res_pjsip: Add external PJSIP resolver implementation using core DNS API.
......................................................................

res_pjsip: Add external PJSIP resolver implementation using core DNS API.

This change adds the following:

1. A query set implementation. This is an API that allows queries to be executed in paralleland once all have completed a callback is invoked.
2. Unit tests for the query set implementation.
3. An external PJSIP resolver which uses the DNS core API to do NAPTR, SRV, AAAA, and A lookups.

For the resolver it will do NAPTR, SRV, and AAAA/A lookups in parallel. If NAPTR or SRV
are available it will then do more queries. And so on. Preference is NAPTR > SRV > AAAA/A,
with IPv6 preferred over IPv4. For transport it will prefer TLS > TCP > UDP if no explicit
transport has been provided. Configured transports on the system are taken into account to
eliminate resolved addresses which have no hope of completing.

ASTERISK-24947 #close
Reported by: Joshua Colp

Change-Id: I56cb03ce4f9d3d600776f36928e0b3e379b5d71e
---
M configure
M configure.ac
M include/asterisk/autoconfig.h.in
M include/asterisk/dns_core.h
M include/asterisk/dns_internal.h
M include/asterisk/dns_query_set.h
M main/dns_core.c
M main/dns_query_set.c
M main/dns_recurring.c
M res/res_pjsip.c
M res/res_pjsip/include/res_pjsip_private.h
A res/res_pjsip/pjsip_resolver.c
M res/res_pjsip_session.c
A tests/test_dns_query_set.c
14 files changed, 1,453 insertions(+), 54 deletions(-)


  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/75/75/1

diff --git a/configure b/configure
index 9a2630d..c539ee9 100755
--- a/configure
+++ b/configure
@@ -1,5 +1,5 @@
 #! /bin/sh
-# From configure.ac Revision: 432815 .
+# From configure.ac Revision.
 # Guess values for system-dependent variables and create Makefiles.
 # Generated by GNU Autoconf 2.69 for asterisk trunk.
 #
@@ -908,6 +908,10 @@
 PORTAUDIO_DIR
 PORTAUDIO_INCLUDE
 PORTAUDIO_LIB
+PBX_PJSIP_EXTERNAL_RESOLVER
+PJSIP_EXTERNAL_RESOLVER_DIR
+PJSIP_EXTERNAL_RESOLVER_INCLUDE
+PJSIP_EXTERNAL_RESOLVER_LIB
 PBX_PJ_SSL_CERT_LOAD_FROM_FILES2
 PJ_SSL_CERT_LOAD_FROM_FILES2_DIR
 PJ_SSL_CERT_LOAD_FROM_FILES2_INCLUDE
@@ -10303,6 +10307,18 @@
 PJ_SSL_CERT_LOAD_FROM_FILES2_DIR=${PJPROJECT_DIR}
 
 PBX_PJ_SSL_CERT_LOAD_FROM_FILES2=0
+
+
+
+
+
+
+
+PJSIP_EXTERNAL_RESOLVER_DESCRIP="PJSIP External Resolver Support"
+PJSIP_EXTERNAL_RESOLVER_OPTION=pjsip
+PJSIP_EXTERNAL_RESOLVER_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_EXTERNAL_RESOLVER=0
 
 
 
@@ -24470,6 +24486,110 @@
 
 
 
+if test "x${PBX_PJSIP_EXTERNAL_RESOLVER}" != "x1" -a "${USE_PJSIP_EXTERNAL_RESOLVER}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_EXTERNAL_RESOLVER=DIR has been specified, use it.
+   if test "x${PJSIP_EXTERNAL_RESOLVER_DIR}" != "x"; then
+      if test -d ${PJSIP_EXTERNAL_RESOLVER_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_EXTERNAL_RESOLVER_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_EXTERNAL_RESOLVER_DIR}"
+      fi
+   fi
+   pbxfuncname="pjsip_endpt_set_ext_resolver"
+   if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
+      AST_PJSIP_EXTERNAL_RESOLVER_FOUND=yes
+   else
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$as_ac_Lib=yes"
+else
+  eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+  AST_PJSIP_EXTERNAL_RESOLVER_FOUND=yes
+else
+  AST_PJSIP_EXTERNAL_RESOLVER_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+   fi
+
+   # now check for the header.
+   if test "${AST_PJSIP_EXTERNAL_RESOLVER_FOUND}" = "yes"; then
+      PJSIP_EXTERNAL_RESOLVER_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIBS"
+      # if --with-PJSIP_EXTERNAL_RESOLVER=DIR has been specified, use it.
+      if test "x${PJSIP_EXTERNAL_RESOLVER_DIR}" != "x"; then
+         PJSIP_EXTERNAL_RESOLVER_INCLUDE="-I${PJSIP_EXTERNAL_RESOLVER_DIR}/include"
+      fi
+      PJSIP_EXTERNAL_RESOLVER_INCLUDE="${PJSIP_EXTERNAL_RESOLVER_INCLUDE} $PJPROJECT_CFLAGS"
+      if test "xpjsip.h" = "x" ; then	# no header, assume found
+         PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND="1"
+      else				# check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_EXTERNAL_RESOLVER_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND=1
+else
+  PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+      fi
+      if test "x${PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND}" = "x0" ; then
+         PJSIP_EXTERNAL_RESOLVER_LIB=""
+         PJSIP_EXTERNAL_RESOLVER_INCLUDE=""
+      else
+         if test "x${pbxfuncname}" = "x" ; then		# only checking headers -> no library
+            PJSIP_EXTERNAL_RESOLVER_LIB=""
+         fi
+         PBX_PJSIP_EXTERNAL_RESOLVER=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_EXTERNAL_RESOLVER 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
+
 
 if test "x${PBX_POPT}" != "x1" -a "${USE_POPT}" != "no"; then
    pbxlibdir=""
diff --git a/configure.ac b/configure.ac
index afbb5af..8a37075 100644
--- a/configure.ac
+++ b/configure.ac
@@ -458,6 +458,7 @@
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_REPLACE_MEDIA_STREAM], [PJSIP Media Stream Replacement Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_GET_DEST_INFO], [pjsip_get_dest_info support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_from_files2 support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EXTERNAL_RESOLVER], [PJSIP External Resolver Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio])
 AST_EXT_LIB_SETUP([PRI], [ISDN PRI], [pri])
 AST_EXT_LIB_SETUP_OPTIONAL([PRI_SETUP_ACK_INBAND], [ISDN PRI progress inband ie in SETUP ACK], [PRI], [pri])
@@ -2124,6 +2125,7 @@
 
 AST_EXT_LIB_CHECK([PJSIP_GET_DEST_INFO], [pjsip], [pjsip_get_dest_info], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
 AST_EXT_LIB_CHECK([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj], [pj_ssl_cert_load_from_files2], [pjlib.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
+AST_EXT_LIB_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip], [pjsip_endpt_set_ext_resolver], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
 
 AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h])
 
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 8c7ead4..474fb8c 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -578,6 +578,10 @@
 /* Define if your system has the PJPROJECT libraries. */
 #undef HAVE_PJPROJECT
 
+/* Define to 1 if PJPROJECT has the PJSIP External Resolver Support feature.
+   */
+#undef HAVE_PJSIP_EXTERNAL_RESOLVER
+
 /* Define to 1 if PJPROJECT has the pjsip_get_dest_info support feature. */
 #undef HAVE_PJSIP_GET_DEST_INFO
 
diff --git a/include/asterisk/dns_core.h b/include/asterisk/dns_core.h
index 1f67bb8..fe67e34 100644
--- a/include/asterisk/dns_core.h
+++ b/include/asterisk/dns_core.h
@@ -205,6 +205,15 @@
 const char *ast_dns_record_get_data(const struct ast_dns_record *record);
 
 /*!
+ * \brief Retrieve the size of the raw DNS record
+ *
+ * \param record The DNS record
+ *
+ * \return the size of the raw DNS record
+ */
+size_t ast_dns_record_get_data_size(const struct ast_dns_record *record);
+
+/*!
  * \brief Get the next DNS record
  *
  * \param record The current DNS record
diff --git a/include/asterisk/dns_internal.h b/include/asterisk/dns_internal.h
index d518f90..be8794b 100644
--- a/include/asterisk/dns_internal.h
+++ b/include/asterisk/dns_internal.h
@@ -23,6 +23,12 @@
  * \author Joshua Colp <jcolp at digium.com>
  */
 
+/*! \brief For AST_VECTOR */
+#include "asterisk/vector.h"
+
+/*! \brief For ast_dns_query_set_callback */
+#include "asterisk/dns_query_set.h"
+
 /*! \brief Generic DNS record information */
 struct ast_dns_record {
 	/*! \brief Resource record type */
@@ -151,6 +157,30 @@
 	char name[0];
 };
 
+/*! \brief A DNS query set query, which includes its state */
+struct dns_query_set_query {
+	/*! \brief Whether the query started successfully or not */
+	unsigned int started;
+	/*! \brief THe query itself */
+	struct ast_dns_query *query;
+};
+
+/*! \brief A set of DNS queries */
+struct ast_dns_query_set {
+	/*! \brief DNS queries */
+	AST_VECTOR(, struct dns_query_set_query) queries;
+	/* \brief Whether the query set is in progress or not */
+	int in_progress;
+	/*! \brief The total number of completed queries */
+	int queries_completed;
+	/*! \brief The total number of cancelled queries */
+	int queries_cancelled;
+	/*! \brief Callback to invoke upon completion */
+	ast_dns_query_set_callback callback;
+	/*! \brief User-specific data */
+	void *user_data;
+};
+
 /*! \brief An active DNS query */
 struct ast_dns_query_active {
 	/*! \brief The underlying DNS query */
@@ -241,3 +271,25 @@
  * \return The number of bytes consumed while parsing
  */
 int dns_parse_string(char *cur, uint8_t *size, char **val);
+
+/*!
+ * \brief Allocate a DNS query (but do not start resolution)
+ *
+ * \param name The name of what to resolve
+ * \param rr_type Resource record type
+ * \param rr_class Resource record class
+ * \param callback The callback to invoke upon completion
+ * \param data User data to make available on the query
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note The result passed to the callback does not need to be freed
+ *
+ * \note The user data MUST be an ao2 object
+ *
+ * \note This function increments the reference count of the user data, it does NOT steal
+ *
+ * \note The query must be released upon completion or cancellation using ao2_ref
+ */
+struct ast_dns_query *dns_query_alloc(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data);
diff --git a/include/asterisk/dns_query_set.h b/include/asterisk/dns_query_set.h
index c89fdfd..fac732a 100644
--- a/include/asterisk/dns_query_set.h
+++ b/include/asterisk/dns_query_set.h
@@ -43,6 +43,8 @@
  *
  * \retval non-NULL success
  * \retval NULL failure
+ *
+ * \note The query set must be released upon cancellation or completion using ao2_ref
  */
 struct ast_dns_query_set *ast_dns_query_set_create(void);
 
@@ -76,6 +78,8 @@
  *
  * \retval non-NULL success
  * \retval NULL failure
+ *
+ * \note The returned query is only valid for the lifetime of the query set itself
  */
 struct ast_dns_query *ast_dns_query_set_get(const struct ast_dns_query_set *query_set, unsigned int index);
 
@@ -106,28 +110,24 @@
  *
  * \param query_set The query set
  *
+ * \retval 0 success
+ * \retval -1 failure
+ *
  * \note This function will return when all queries have been completed
  */
-void ast_query_set_resolve(struct ast_dns_query_set *query_set);
+int ast_query_set_resolve(struct ast_dns_query_set *query_set);
 
 /*!
  * \brief Cancel an asynchronous DNS query set resolution
  *
  * \param query_set The DNS query set
  *
- * \retval 0 success
- * \retval -1 failure
+ * \retval 0 success (all queries have been cancelled)
+ * \retval -1 failure (some queries could not be cancelled)
  *
  * \note If successfully cancelled the callback will not be invoked
  */
 int ast_dns_query_set_resolve_cancel(struct ast_dns_query_set *query_set);
-
-/*!
- * \brief Free a query set
- *
- * \param query_set A DNS query set
- */
-void ast_dns_query_set_free(struct ast_dns_query_set *query_set);
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
diff --git a/main/dns_core.c b/main/dns_core.c
index 53ea1d0..69de9b3 100644
--- a/main/dns_core.c
+++ b/main/dns_core.c
@@ -40,6 +40,7 @@
 #include "asterisk/dns_srv.h"
 #include "asterisk/dns_tlsa.h"
 #include "asterisk/dns_recurring.h"
+#include "asterisk/dns_query_set.h"
 #include "asterisk/dns_resolver.h"
 #include "asterisk/dns_internal.h"
 
@@ -163,6 +164,11 @@
 	return record->data_ptr;
 }
 
+size_t ast_dns_record_get_data_size(const struct ast_dns_record *record)
+{
+	return record->data_len;
+}
+
 const struct ast_dns_record *ast_dns_record_get_next(const struct ast_dns_record *record)
 {
 	return AST_LIST_NEXT(record, list);
@@ -186,9 +192,9 @@
 	ast_dns_result_free(query->result);
 }
 
-struct ast_dns_query_active *ast_dns_resolve_async(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data)
+struct ast_dns_query *dns_query_alloc(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data)
 {
-	struct ast_dns_query_active *active;
+	struct ast_dns_query *query;
 
 	if (ast_strlen_zero(name)) {
 		ast_log(LOG_WARNING, "Could not perform asynchronous resolution, no name provided\n");
@@ -215,30 +221,42 @@
 		return NULL;
 	}
 
+	query = ao2_alloc_options(sizeof(*query) + strlen(name) + 1, dns_query_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!query) {
+		return NULL;
+	}
+
+	query->callback = callback;
+	query->user_data = ao2_bump(data);
+	query->rr_type = rr_type;
+	query->rr_class = rr_class;
+	strcpy(query->name, name); /* SAFE */
+
+	AST_RWLIST_RDLOCK(&resolvers);
+	query->resolver = AST_RWLIST_FIRST(&resolvers);
+	AST_RWLIST_UNLOCK(&resolvers);
+
+	if (!query->resolver) {
+		ast_log(LOG_ERROR, "Attempted to do a DNS query for '%s' of class '%d' and type '%d' but no resolver is available\n",
+			name, rr_class, rr_type);
+		ao2_ref(query, -1);
+		return NULL;
+	}
+
+	return query;
+}
+
+struct ast_dns_query_active *ast_dns_resolve_async(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data)
+{
+	struct ast_dns_query_active *active;
+
 	active = ao2_alloc_options(sizeof(*active), dns_query_active_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
 	if (!active) {
 		return NULL;
 	}
 
-	active->query = ao2_alloc_options(sizeof(*active->query) + strlen(name) + 1, dns_query_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	active->query = dns_query_alloc(name, rr_type, rr_class, callback, data);
 	if (!active->query) {
-		ao2_ref(active, -1);
-		return NULL;
-	}
-
-	active->query->callback = callback;
-	active->query->user_data = ao2_bump(data);
-	active->query->rr_type = rr_type;
-	active->query->rr_class = rr_class;
-	strcpy(active->query->name, name); /* SAFE */
-
-	AST_RWLIST_RDLOCK(&resolvers);
-	active->query->resolver = AST_RWLIST_FIRST(&resolvers);
-	AST_RWLIST_UNLOCK(&resolvers);
-
-	if (!active->query->resolver) {
-		ast_log(LOG_ERROR, "Attempted to do a DNS query for '%s' of class '%d' and type '%d' but no resolver is available\n",
-			name, rr_class, rr_type);
 		ao2_ref(active, -1);
 		return NULL;
 	}
diff --git a/main/dns_query_set.c b/main/dns_query_set.c
index 45626d1..6af7ab7 100644
--- a/main/dns_query_set.c
+++ b/main/dns_query_set.c
@@ -33,39 +33,114 @@
 
 #include "asterisk/vector.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
 #include "asterisk/dns_core.h"
 #include "asterisk/dns_query_set.h"
+#include "asterisk/dns_internal.h"
+#include "asterisk/dns_resolver.h"
 
-/*! \brief A set of DNS queries */
-struct ast_dns_query_set {
-	/*! \brief DNS queries */
-	AST_VECTOR(, struct ast_dns_query *) queries;
-	/*! \brief The total number of completed queries */
-	unsigned int queries_completed;
-	/*! \brief Callback to invoke upon completion */
-	ast_dns_query_set_callback callback;
-	/*! \brief User-specific data */
-	void *user_data;
-};
+/*! \brief The default number of expected queries to be added to the query set */
+#define DNS_QUERY_SET_EXPECTED_QUERY_COUNT 5
+
+/*! \brief Release all queries held in a query set */
+static void dns_query_set_release(struct ast_dns_query_set *query_set)
+{
+	int idx;
+
+	for (idx = 0; idx < AST_VECTOR_SIZE(&query_set->queries); ++idx) {
+		struct dns_query_set_query *query = AST_VECTOR_GET_ADDR(&query_set->queries, idx);
+
+		ao2_ref(query->query, -1);
+	}
+
+	AST_VECTOR_FREE(&query_set->queries);
+}
+
+/*! \brief Destructor for DNS query set */
+static void dns_query_set_destroy(void *data)
+{
+	struct ast_dns_query_set *query_set = data;
+
+	dns_query_set_release(query_set);
+	ao2_cleanup(query_set->user_data);
+}
 
 struct ast_dns_query_set *ast_dns_query_set_create(void)
 {
-	return NULL;
+	struct ast_dns_query_set *query_set;
+
+	query_set = ao2_alloc_options(sizeof(*query_set), dns_query_set_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!query_set) {
+		return NULL;
+	}
+
+	if (AST_VECTOR_INIT(&query_set->queries, DNS_QUERY_SET_EXPECTED_QUERY_COUNT)) {
+		ao2_ref(query_set, -1);
+		return NULL;
+	}
+
+	return query_set;
+}
+
+/*! \brief Callback invoked upon completion of a DNS query */
+static void dns_query_set_callback(const struct ast_dns_query *query)
+{
+	struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
+
+	if (ast_atomic_fetchadd_int(&query_set->queries_completed, +1) != (AST_VECTOR_SIZE(&query_set->queries) - 1)) {
+		return;
+	}
+
+	/* All queries have been completed, invoke final callback */
+	if (query_set->queries_cancelled != AST_VECTOR_SIZE(&query_set->queries)) {
+		query_set->callback(query_set);
+	}
+
+	ao2_cleanup(query_set->user_data);
+	query_set->user_data = NULL;
+
+	dns_query_set_release(query_set);
 }
 
 int ast_dns_query_set_add(struct ast_dns_query_set *query_set, const char *name, int rr_type, int rr_class)
 {
-	return -1;
+	struct dns_query_set_query query = {
+		.started = 0,
+	};
+
+	if (query_set->in_progress) {
+		return -1;
+	}
+
+	query.query = dns_query_alloc(name, rr_type, rr_class, dns_query_set_callback, query_set);
+	if (!query.query) {
+		return -1;
+	}
+
+	AST_VECTOR_APPEND(&query_set->queries, query);
+
+	return 0;
 }
 
 size_t ast_dns_query_set_num_queries(const struct ast_dns_query_set *query_set)
 {
-	return 0;
+	return AST_VECTOR_SIZE(&query_set->queries);
 }
 
 struct ast_dns_query *ast_dns_query_set_get(const struct ast_dns_query_set *query_set, unsigned int index)
 {
-	return NULL;
+	/* Only once all queries have been completed can results be retrieved */
+	if (query_set->queries_completed != AST_VECTOR_SIZE(&query_set->queries)) {
+		return NULL;
+	}
+
+	/* If the index exceeds the number of queries... no query for you */
+	if (index >= AST_VECTOR_SIZE(&query_set->queries)) {
+		return NULL;
+	}
+
+	return AST_VECTOR_GET_ADDR(&query_set->queries, index)->query;
 }
 
 void *ast_dns_query_set_get_data(const struct ast_dns_query_set *query_set)
@@ -75,19 +150,101 @@
 
 void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dns_query_set_callback callback, void *data)
 {
+	int idx;
+
+	if (query_set->in_progress) {
+		return;
+	}
+
+	query_set->in_progress = 1;
 	query_set->callback = callback;
 	query_set->user_data = ao2_bump(data);
+
+	for (idx = 0; idx < AST_VECTOR_SIZE(&query_set->queries); ++idx) {
+		struct dns_query_set_query *query = AST_VECTOR_GET_ADDR(&query_set->queries, idx);
+
+		if (!query->query->resolver->resolve(query->query)) {
+			query->started = 1;
+			continue;
+		}
+
+		dns_query_set_callback(query->query);
+	}
 }
 
-void ast_query_set_resolve(struct ast_dns_query_set *query_set)
+/*! \brief Structure used for signaling back for synchronous resolution completion */
+struct dns_synchronous_resolve {
+	/*! \brief Lock used for signaling */
+	ast_mutex_t lock;
+	/*! \brief Condition used for signaling */
+	ast_cond_t cond;
+	/*! \brief Whether the query has completed */
+	unsigned int completed;
+};
+
+/*! \brief Destructor for synchronous resolution structure */
+static void dns_synchronous_resolve_destroy(void *data)
 {
+	struct dns_synchronous_resolve *synchronous = data;
+
+	ast_mutex_destroy(&synchronous->lock);
+	ast_cond_destroy(&synchronous->cond);
+}
+
+/*! \brief Callback used to implement synchronous resolution */
+static void dns_synchronous_resolve_callback(const struct ast_dns_query_set *query_set)
+{
+	struct dns_synchronous_resolve *synchronous = ast_dns_query_set_get_data(query_set);
+
+	ast_mutex_lock(&synchronous->lock);
+	synchronous->completed = 1;
+	ast_cond_signal(&synchronous->cond);
+	ast_mutex_unlock(&synchronous->lock);
+}
+
+int ast_query_set_resolve(struct ast_dns_query_set *query_set)
+{
+	struct dns_synchronous_resolve *synchronous;
+
+	synchronous = ao2_alloc_options(sizeof(*synchronous), dns_synchronous_resolve_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!synchronous) {
+		return -1;
+	}
+
+	ast_mutex_init(&synchronous->lock);
+	ast_cond_init(&synchronous->cond, NULL);
+
+	ast_dns_query_set_resolve_async(query_set, dns_synchronous_resolve_callback, synchronous);
+
+	/* Wait for resolution to complete */
+	ast_mutex_lock(&synchronous->lock);
+	while (!synchronous->completed) {
+		ast_cond_wait(&synchronous->cond, &synchronous->lock);
+	}
+	ast_mutex_unlock(&synchronous->lock);
+
+	ao2_ref(synchronous, -1);
+
+	return 0;
 }
 
 int ast_dns_query_set_resolve_cancel(struct ast_dns_query_set *query_set)
 {
-	return -1;
-}
+	int idx;
+	size_t query_count = AST_VECTOR_SIZE(&query_set->queries);
 
-void ast_dns_query_set_free(struct ast_dns_query_set *query_set)
-{
-}
+	for (idx = 0; idx < AST_VECTOR_SIZE(&query_set->queries); ++idx) {
+		struct dns_query_set_query *query = AST_VECTOR_GET_ADDR(&query_set->queries, idx);
+
+		if (query->started) {
+			if (!query->query->resolver->cancel(query->query)) {
+				query_set->queries_cancelled++;
+				dns_query_set_callback(query->query);
+			}
+		} else {
+			query_set->queries_cancelled++;
+		}
+	}
+
+	return (query_set->queries_cancelled == query_count) ? 0 : -1;
+}
\ No newline at end of file
diff --git a/main/dns_recurring.c b/main/dns_recurring.c
index 3ebbab0..64fdfb9 100644
--- a/main/dns_recurring.c
+++ b/main/dns_recurring.c
@@ -33,10 +33,12 @@
 
 #include "asterisk/astobj2.h"
 #include "asterisk/linkedlists.h"
+#include "asterisk/vector.h"
 #include "asterisk/sched.h"
 #include "asterisk/strings.h"
 #include "asterisk/dns_core.h"
 #include "asterisk/dns_recurring.h"
+#include "asterisk/dns_query_set.h"
 #include "asterisk/dns_internal.h"
 
 #include <arpa/nameser.h>
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index fcd8516..d04b09b 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -3480,8 +3480,6 @@
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
-	ast_sip_initialize_dns();
-
 	pjsip_tsx_layer_init_module(ast_pjsip_endpoint);
 	pjsip_ua_init_module(ast_pjsip_endpoint, NULL);
 
@@ -3514,6 +3512,9 @@
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
+	ast_sip_initialize_resolver();
+	ast_sip_initialize_dns();
+
 	if (ast_sip_initialize_distributor()) {
 		ast_log(LOG_ERROR, "Failed to register distributor module. Aborting load\n");
 		ast_res_pjsip_destroy_configuration();
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index bf428d5..a8b9411 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -233,6 +233,12 @@
 
 /*!
  * \internal
+ * \brief Initialize our own resolver support
+ */
+void ast_sip_initialize_resolver(void);
+
+/*!
+ * \internal
  * \brief Initialize global configuration
  *
  * \retval 0 Success
diff --git a/res/res_pjsip/pjsip_resolver.c b/res/res_pjsip/pjsip_resolver.c
new file mode 100644
index 0000000..8b853c3
--- /dev/null
+++ b/res/res_pjsip/pjsip_resolver.c
@@ -0,0 +1,662 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Joshua Colp <jcolp at 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.
+ */
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjlib-util/errno.h>
+
+#include <arpa/nameser.h>
+
+#include "asterisk/astobj2.h"
+#include "asterisk/dns_core.h"
+#include "asterisk/dns_query_set.h"
+#include "asterisk/dns_srv.h"
+#include "asterisk/dns_naptr.h"
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+
+#ifdef HAVE_PJSIP_EXTERNAL_RESOLVER
+
+/*! \brief Structure which contains transport+port information for an active query */
+struct sip_target {
+	/*! \brief The transport to be used */
+	pjsip_transport_type_e transport;
+	/*! \brief The port */
+	int port;
+};
+
+/*! \brief The vector used for current targets */
+AST_VECTOR(targets, struct sip_target);
+
+/*! \brief Structure which keeps track of resolution */
+struct sip_resolve {
+	/*! \brief Addresses currently being resolved, indexed based on index of queries in query set */
+	struct targets resolving;
+	/*! \brief Active queries */
+	struct ast_dns_query_set *queries;
+	/*! \brief Current viable server addresses */
+	pjsip_server_addresses addresses;
+	/*! \brief Callback to invoke upon completion */
+	pjsip_resolver_callback *callback;
+	/*! \brief User provided data */
+	void *token;
+};
+
+/*! \brief Our own defined transports, reduces the size of sip_available_transports */
+enum sip_resolver_transport {
+	SIP_RESOLVER_TRANSPORT_UDP,
+	SIP_RESOLVER_TRANSPORT_TCP,
+	SIP_RESOLVER_TRANSPORT_TLS,
+	SIP_RESOLVER_TRANSPORT_UDP6,
+	SIP_RESOLVER_TRANSPORT_TCP6,
+	SIP_RESOLVER_TRANSPORT_TLS6,
+};
+
+/*! \brief Available transports on the system */
+static int sip_available_transports[] = {
+	/* This is a list of transports with whether they are available as a valid transport
+	 * stored. We use our own identifier as to reduce the size of sip_available_transports.
+	 * As this array is only manipulated at startup it does not require a lock to protect
+	 * it.
+	 */
+	[SIP_RESOLVER_TRANSPORT_UDP] = 0,
+	[SIP_RESOLVER_TRANSPORT_TCP] = 0,
+	[SIP_RESOLVER_TRANSPORT_TLS] = 0,
+	[SIP_RESOLVER_TRANSPORT_UDP6] = 0,
+	[SIP_RESOLVER_TRANSPORT_TCP6] = 0,
+	[SIP_RESOLVER_TRANSPORT_TLS6] = 0,
+};
+
+/*!
+ * \internal
+ * \brief Destroy resolution data
+ *
+ * \param data The resolution data to destroy
+ *
+ * \return Nothing
+ */
+static void sip_resolve_destroy(void *data)
+{
+	struct sip_resolve *resolve = data;
+
+	AST_VECTOR_FREE(&resolve->resolving);
+	ao2_cleanup(resolve->queries);
+}
+
+/*!
+ * \internal
+ * \brief Check whether a transport is available or not
+ *
+ * \param transport The PJSIP transport type
+ *
+ * \return 1 success (transport is available)
+ * \return 0 failure (transport is not available)
+ */
+static int sip_transport_is_available(enum pjsip_transport_type_e transport)
+{
+	enum sip_resolver_transport resolver_transport;
+
+	if (transport == PJSIP_TRANSPORT_UDP) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_UDP;
+	} else if (transport == PJSIP_TRANSPORT_TCP) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TCP;
+	} else if (transport == PJSIP_TRANSPORT_TLS) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TLS;
+	} else if (transport == PJSIP_TRANSPORT_UDP6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_UDP6;
+	} else if (transport == PJSIP_TRANSPORT_TCP6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TCP6;
+	} else if (transport == PJSIP_TRANSPORT_TLS6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TLS6;
+	} else {
+		return 0;
+	}
+
+	return sip_available_transports[resolver_transport];
+}
+
+/*!
+ * \internal
+ * \brief Add a query to be resolved
+ *
+ * \param resolve The ongoing resolution
+ * \param name What to resolve
+ * \param rr_type The type of record to look up
+ * \param rr_class The type of class to look up
+ * \param transport The transport to use for any resulting records
+ * \param port The port to use for any resulting records - if not specified the default for the transport is used
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int sip_resolve_add(struct sip_resolve *resolve, const char *name, int rr_type, int rr_class, pjsip_transport_type_e transport, int port)
+{
+	struct sip_target target = {
+		.transport = transport,
+		.port = port,
+	};
+
+	if (!resolve->queries) {
+		resolve->queries = ast_dns_query_set_create();
+	}
+
+	if (!resolve->queries) {
+		return -1;
+	}
+
+	if (!port) {
+		target.port = pjsip_transport_get_default_port_for_type(transport);
+	}
+
+	if (AST_VECTOR_APPEND(&resolve->resolving, target)) {
+		return -1;
+	}
+
+	ast_debug(2, "[%p] Added target '%s' with record type '%d', transport '%s', and port '%d'\n", resolve, name, rr_type,
+		pjsip_transport_get_type_name(transport), target.port);
+
+	return ast_dns_query_set_add(resolve->queries, name, rr_type, rr_class);
+}
+
+/*!
+ * \internal
+ * \brief Task used to invoke the user specific callback
+ *
+ * \param data The complete resolution
+ *
+ * \return Nothing
+ */
+static int sip_resolve_invoke_user_callback(void *data)
+{
+	struct sip_resolve *resolve = data;
+	int idx;
+
+	for (idx = 0; idx < resolve->addresses.count; ++idx) {
+		/* This includes space for the IP address, [, ], :, and the port */
+		char addr[PJ_INET6_ADDRSTRLEN + 10];
+
+		ast_debug(2, "[%p] Address '%d' is %s with transport '%s'\n",
+			resolve, idx, pj_sockaddr_print(&resolve->addresses.entry[idx].addr, addr, sizeof(addr), 3),
+			pjsip_transport_get_type_name(resolve->addresses.entry[idx].type));
+	}
+
+	ast_debug(2, "[%p] Invoking user callback with '%d' addresses\n", resolve, resolve->addresses.count);
+	resolve->callback(resolve->addresses.count ? PJ_SUCCESS : PJLIB_UTIL_EDNSNOANSWERREC, resolve->token, &resolve->addresses);
+
+	ao2_ref(resolve, -1);
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Handle a NAPTR record according to RFC3263
+ *
+ * \param resolve The ongoing resolution
+ * \param record The NAPTR record itself
+ * \param service The service to look for
+ * \param transport The transport to use for resulting queries
+ *
+ * \retval 0 success
+ * \retval -1 failure (record not handled / supported)
+ */
+static int sip_resolve_handle_naptr(struct sip_resolve *resolve, const struct ast_dns_record *record,
+	const char *service, pjsip_transport_type_e transport)
+{
+	if (strcasecmp(ast_dns_naptr_get_service(record), service)) {
+		return -1;
+	}
+
+	if (!sip_transport_is_available(transport) &&
+		!sip_transport_is_available(transport + PJSIP_TRANSPORT_IPV6)) {
+		return -1;
+	}
+
+	if (strcasecmp(ast_dns_naptr_get_flags(record), "s")) {
+		ast_debug(2, "[%p] NAPTR service %s received with unsupported flags '%s'\n",
+			resolve, service, ast_dns_naptr_get_flags(record));
+		return -1;
+	}
+
+	if (ast_strlen_zero(ast_dns_naptr_get_replacement(record))) {
+		return -1;
+	}
+
+	return sip_resolve_add(resolve, ast_dns_naptr_get_replacement(record), ns_t_srv, ns_c_in,
+		transport, 0);
+}
+
+/*!
+ * \internal
+ * \brief Query set callback function, invoked when all queries have completed
+ *
+ * \param query_set The completed query set
+ *
+ * \return Nothing
+ */
+static void sip_resolve_callback(const struct ast_dns_query_set *query_set)
+{
+	struct sip_resolve *resolve = ast_dns_query_set_get_data(query_set);
+	struct ast_dns_query_set *queries = resolve->queries;
+	struct targets resolving;
+	int idx, address_count = 0;
+	unsigned short order = 0;
+	int strict_order = 0;
+
+	ast_debug(2, "[%p] All parallel queries completed\n", resolve);
+
+	resolve->queries = NULL;
+
+	/* This purposely steals the resolving list so we can add entries to the new one in the same loop and also have access
+	 * to the old.
+	 */
+	resolving = resolve->resolving;
+	AST_VECTOR_INIT(&resolve->resolving, 0);
+
+	/* The order of queries is what defines the preference order for the records within this specific query set.
+	 * The preference order overall is defined as a result of drilling down from other records. Each
+	 * completed query set starts placing records at the beginning, moving others that may have already been present.
+	 */
+	for (idx = 0; idx < ast_dns_query_set_num_queries(queries); ++idx) {
+		struct ast_dns_query *query = ast_dns_query_set_get(queries, idx);
+		struct ast_dns_result *result = ast_dns_query_get_result(query);
+		struct sip_target *target;
+		const struct ast_dns_record *record;
+
+		if (!result) {
+			ast_debug(2, "[%p] No result information for target '%s' of type '%d'\n", resolve,
+				ast_dns_query_get_name(query), ast_dns_query_get_rr_type(query));
+			continue;
+		}
+
+		target = AST_VECTOR_GET_ADDR(&resolving, idx);
+		for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
+
+			if (ast_dns_record_get_rr_type(record) == ns_t_a ||
+				ast_dns_record_get_rr_type(record) == ns_t_aaaa) {
+
+				/* If the maximum number of addresses has already been reached by this query set, skip subsequent
+				 * records as they have lower preference - any existing ones may get replaced/moved if another
+				 * query set is resolved after this one
+				 */
+				if (address_count == PJSIP_MAX_RESOLVED_ADDRESSES) {
+					continue;
+				}
+
+				/* Move any existing addresses so we can make room for this record, this may hurt your head slightly but
+				 * essentially it figures out the maximum number of previous addresses that can exist and caps the
+				 * the memmove operation to that
+				 */
+				memmove(&resolve->addresses.entry[address_count + 1], &resolve->addresses.entry[address_count],
+					sizeof(resolve->addresses.entry[0]) *
+					MIN(resolve->addresses.count, PJSIP_MAX_RESOLVED_ADDRESSES - address_count - 1));
+
+				resolve->addresses.entry[address_count].type = target->transport;
+
+				/* Populate address information for the new address entry */
+				if (ast_dns_record_get_rr_type(record) == ns_t_a) {
+					ast_debug(2, "[%p] A record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+					resolve->addresses.entry[address_count].addr_len = sizeof(pj_sockaddr_in);
+					pj_sockaddr_init(pj_AF_INET(), &resolve->addresses.entry[address_count].addr, NULL,
+						target->port);
+					resolve->addresses.entry[address_count].addr.ipv4.sin_addr = *(struct pj_in_addr*)ast_dns_record_get_data(record);
+				} else {
+					ast_debug(2, "[%p] AAAA record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+					resolve->addresses.entry[address_count].addr_len = sizeof(pj_sockaddr_in6);
+					pj_sockaddr_init(pj_AF_INET6(), &resolve->addresses.entry[address_count].addr, NULL,
+						target->port);
+					pj_memcpy(&resolve->addresses.entry[address_count].addr.ipv6.sin6_addr, ast_dns_record_get_data(record),
+						ast_dns_record_get_data_size(record));
+				}
+
+				address_count++;
+			} else if (ast_dns_record_get_rr_type(record) == ns_t_srv) {
+				/* SRV records just create new queries for AAAA+A, nothing fancy */
+				ast_debug(2, "[%p] SRV record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+
+				if (sip_transport_is_available(target->transport + PJSIP_TRANSPORT_IPV6)) {
+					sip_resolve_add(resolve, ast_dns_srv_get_host(record), ns_t_aaaa, ns_c_in, target->transport + PJSIP_TRANSPORT_IPV6,
+						ast_dns_srv_get_port(record));
+				}
+
+				if (sip_transport_is_available(target->transport)) {
+					sip_resolve_add(resolve, ast_dns_srv_get_host(record), ns_t_a, ns_c_in, target->transport,
+						ast_dns_srv_get_port(record));
+				}
+			} else if (ast_dns_record_get_rr_type(record) == ns_t_naptr) {
+				int added = -1;
+
+				ast_debug(2, "[%p] NAPTR record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+
+				if (strict_order && (ast_dns_naptr_get_order(record) != order)) {
+					ast_debug(2, "[%p] NAPTR record skipped because order '%hu' does not match strict order '%hu'\n",
+						resolve, ast_dns_naptr_get_order(record), order);
+					continue;
+				}
+
+				if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_UDP) {
+					added = sip_resolve_handle_naptr(resolve, record, "sip+d2u", PJSIP_TRANSPORT_UDP);
+				}
+				if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_TCP) {
+					added = sip_resolve_handle_naptr(resolve, record, "sip+d2t", PJSIP_TRANSPORT_TCP);
+				}
+				if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_TLS) {
+					added = sip_resolve_handle_naptr(resolve, record, "sips+d2t", PJSIP_TRANSPORT_TLS);
+				}
+
+				/* If this record was successfully handled then we need to limit ourselves to this order */
+				if (!added) {
+					strict_order = 1;
+					order = ast_dns_naptr_get_order(record);
+				}
+			}
+		}
+	}
+
+	/* Update the server addresses to include any new entries, but since it's limited to the maximum resolved
+	 * it must never exceed that
+	 */
+	resolve->addresses.count = MIN(resolve->addresses.count + address_count, PJSIP_MAX_RESOLVED_ADDRESSES);
+
+	/* Free the vector we stole as we are responsible for it */
+	AST_VECTOR_FREE(&resolving);
+
+	/* If additional queries were added start the resolution process again */
+	if (resolve->queries) {
+		ast_debug(2, "[%p] New queries added, performing parallel resolution again\n", resolve);
+		ast_dns_query_set_resolve_async(resolve->queries, sip_resolve_callback, resolve);
+		ao2_ref(queries, -1);
+		return;
+	}
+
+	ast_debug(2, "[%p] Resolution completed - %d viable targets\n", resolve, resolve->addresses.count);
+
+	/* Push a task to invoke the callback, we do this so it is guaranteed to run in a PJSIP thread */
+	ao2_ref(resolve, +1);
+	if (ast_sip_push_task(NULL, sip_resolve_invoke_user_callback, resolve)) {
+		ao2_ref(resolve, -1);
+	}
+
+	ao2_ref(queries, -1);
+}
+
+/*!
+ * \internal
+ * \brief Determine what address family a host may be if it is already an IP address
+ *
+ * \param host The host (which may be an IP address)
+ *
+ * \retval 6 The host is an IPv6 address
+ * \retval 4 The host is an IPv4 address
+ * \retval 0 The host is not an IP address
+ */
+static int sip_resolve_get_ip_addr_ver(const pj_str_t *host)
+{
+	pj_in_addr dummy;
+	pj_in6_addr dummy6;
+
+	if (pj_inet_aton(host, &dummy) > 0) {
+		return 4;
+	}
+
+	if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) {
+		return 6;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Perform SIP resolution of a host
+ *
+ * \param resolver Configured resolver instance
+ * \param pool Memory pool to allocate things from
+ * \param target The target we are resolving
+ * \param token User data to pass to the resolver callback
+ * \param cb User resolver callback to invoke upon resolution completion
+ */
+static void sip_resolve(pjsip_resolver_t *resolver, pj_pool_t *pool, const pjsip_host_info *target,
+	void *token, pjsip_resolver_callback *cb)
+{
+	int ip_addr_ver;
+	pjsip_transport_type_e type = target->type;
+	struct sip_resolve *resolve;
+	char host[NI_MAXHOST];
+	int res = 0;
+
+	ast_copy_pj_str(host, &target->addr.host, sizeof(host));
+
+	ast_debug(2, "Performing SIP DNS resolution of target '%s'\n", host);
+
+	/* If the provided target is already an address don't bother resolving */
+	ip_addr_ver = sip_resolve_get_ip_addr_ver(&target->addr.host);
+
+	/* Determine the transport to use if none has been explicitly specified */
+	if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+		/* If we've been told to use a secure or reliable transport restrict ourselves to that */
+#if PJ_HAS_TCP
+		if (target->flag & PJSIP_TRANSPORT_SECURE) {
+			type = PJSIP_TRANSPORT_TLS;
+		} else if (target->flag & PJSIP_TRANSPORT_RELIABLE) {
+			type = PJSIP_TRANSPORT_TCP;
+		} else
+#endif
+		/* According to the RFC otherwise if an explicit IP address OR an explicit port is specified
+		 * we use UDP
+		 */
+		if (ip_addr_ver || target->addr.port) {
+			type = PJSIP_TRANSPORT_UDP;
+		}
+
+		if (ip_addr_ver == 6) {
+			type = (pjsip_transport_type_e)((int) type + PJSIP_TRANSPORT_IPV6);
+		}
+	}
+
+	ast_debug(2, "Transport type for target '%s' is '%s'\n", host, pjsip_transport_get_type_name(type));
+
+	/* If it's already an address call the callback immediately */
+	if (ip_addr_ver) {
+		pjsip_server_addresses addresses = {
+			.entry[0].type = type,
+			.count = 1,
+		};
+
+		if (ip_addr_ver == 4) {
+			addresses.entry[0].addr_len = sizeof(pj_sockaddr_in);
+			pj_sockaddr_init(pj_AF_INET(), &addresses.entry[0].addr, NULL, 0);
+			pj_inet_aton(&target->addr.host, &addresses.entry[0].addr.ipv4.sin_addr);
+		} else {
+			addresses.entry[0].addr_len = sizeof(pj_sockaddr_in6);
+			pj_sockaddr_init(pj_AF_INET6(), &addresses.entry[0].addr, NULL, 0);
+			pj_inet_pton(pj_AF_INET6(), &target->addr.host, &addresses.entry[0].addr.ipv6.sin6_addr);
+		}
+
+		pj_sockaddr_set_port(&addresses.entry[0].addr, !target->addr.port ? pjsip_transport_get_default_port_for_type(type) : target->addr.port);
+
+		ast_debug(2, "Target '%s' is an IP address, skipping resolution\n", host);
+
+		cb(PJ_SUCCESS, token, &addresses);
+
+		return;
+	}
+
+	resolve = ao2_alloc_options(sizeof(*resolve), sip_resolve_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!resolve) {
+		cb(PJ_ENOMEM, token, NULL);
+		return;
+	}
+
+	resolve->callback = cb;
+	resolve->token = token;
+
+	if (AST_VECTOR_INIT(&resolve->resolving, 2)) {
+		ao2_ref(resolve, -1);
+		cb(PJ_ENOMEM, token, NULL);
+		return;
+	}
+
+	ast_debug(2, "[%p] Created resolution tracking for target '%s'\n", resolve, host);
+
+	/* If no port has been specified we can do NAPTR + SRV */
+	if (!target->addr.port) {
+		char srv[NI_MAXHOST];
+
+		res |= sip_resolve_add(resolve, host, ns_t_naptr, ns_c_in, type, 0);
+
+		if ((type == PJSIP_TRANSPORT_TLS || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+			(sip_transport_is_available(PJSIP_TRANSPORT_TLS) ||
+				sip_transport_is_available(PJSIP_TRANSPORT_TLS6))) {
+			snprintf(srv, sizeof(srv), "_sips._tcp.%s", host);
+			res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_TLS, 0);
+		}
+		if ((type == PJSIP_TRANSPORT_TCP || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+			(sip_transport_is_available(PJSIP_TRANSPORT_TCP) ||
+				sip_transport_is_available(PJSIP_TRANSPORT_TCP6))) {
+			snprintf(srv, sizeof(srv), "_sip._tcp.%s", host);
+			res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_TCP, 0);
+		}
+		if ((type == PJSIP_TRANSPORT_UDP || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+			(sip_transport_is_available(PJSIP_TRANSPORT_UDP) ||
+				sip_transport_is_available(PJSIP_TRANSPORT_UDP6))) {
+			snprintf(srv, sizeof(srv), "_sip._udp.%s", host);
+			res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_UDP, 0);
+		}
+	}
+
+	if ((type == PJSIP_TRANSPORT_UNSPECIFIED && sip_transport_is_available(PJSIP_TRANSPORT_UDP6)) ||
+		sip_transport_is_available(type + PJSIP_TRANSPORT_IPV6)) {
+		res |= sip_resolve_add(resolve, host, ns_t_aaaa, ns_c_in, (type == PJSIP_TRANSPORT_UNSPECIFIED ? PJSIP_TRANSPORT_UDP6 : type + PJSIP_TRANSPORT_IPV6), target->addr.port);
+	}
+
+	if ((type == PJSIP_TRANSPORT_UNSPECIFIED && sip_transport_is_available(PJSIP_TRANSPORT_UDP)) ||
+		sip_transport_is_available(type)) {
+		res |= sip_resolve_add(resolve, host, ns_t_a, ns_c_in, (type == PJSIP_TRANSPORT_UNSPECIFIED ? PJSIP_TRANSPORT_UDP : type), target->addr.port);
+	}
+
+	if (res) {
+		ao2_ref(resolve, -1);
+		cb(PJ_ENOMEM, token, NULL);
+		return;
+	}
+
+	ast_debug(2, "[%p] Starting initial resolution using parallel queries for target '%s'\n", resolve, host);
+	ast_dns_query_set_resolve_async(resolve->queries, sip_resolve_callback, resolve);
+
+	ao2_ref(resolve, -1);
+}
+
+/*!
+ * \internal
+ * \brief Determine if a specific transport is configured on the system
+ *
+ * \param pool A memory pool to allocate things from
+ * \param transport The type of transport to check
+ * \param name A friendly name to print in the verbose message
+ *
+ * \return Nothing
+ */
+static void sip_check_transport(pj_pool_t *pool, pjsip_transport_type_e transport, const char *name)
+{
+	pjsip_tpmgr_fla2_param prm;
+	enum sip_resolver_transport resolver_transport;
+
+	pjsip_tpmgr_fla2_param_default(&prm);
+	prm.tp_type = transport;
+
+	if (transport == PJSIP_TRANSPORT_UDP) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_UDP;
+	} else if (transport == PJSIP_TRANSPORT_TCP) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TCP;
+	} else if (transport == PJSIP_TRANSPORT_TLS) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TLS;
+	} else if (transport == PJSIP_TRANSPORT_UDP6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_UDP6;
+	} else if (transport == PJSIP_TRANSPORT_TCP6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TCP6;
+	} else if (transport == PJSIP_TRANSPORT_TLS6) {
+		resolver_transport = SIP_RESOLVER_TRANSPORT_TLS6;
+	} else {
+		ast_verb(2, "'%s' is an unsupported SIP transport\n", name);
+		return;
+	}
+
+	if (pjsip_tpmgr_find_local_addr2(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()),
+		pool, &prm) == PJ_SUCCESS) {
+		ast_verb(2, "'%s' is an available SIP transport\n", name);
+		sip_available_transports[resolver_transport] = 1;
+	} else {
+		ast_verb(2, "'%s' is not an available SIP transport, disabling resolver support for it\n",
+			name);
+	}
+}
+
+/*! \brief External resolver implementation for PJSIP */
+static pjsip_ext_resolver resolver = {
+	.resolve = sip_resolve,
+};
+
+/*!
+ * \internal
+ * \brief Task to determine available transports and set ourselves an external resolver
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int sip_replace_resolver(void *data)
+{
+	pj_pool_t *pool;
+
+
+	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Transport Availability", 256, 256);
+	if (!pool) {
+		return -1;
+	}
+
+	/* Determine what transports are available on the system */
+	sip_check_transport(pool, PJSIP_TRANSPORT_UDP, "UDP+IPv4");
+	sip_check_transport(pool, PJSIP_TRANSPORT_TCP, "TCP+IPv4");
+	sip_check_transport(pool, PJSIP_TRANSPORT_TLS, "TLS+IPv4");
+	sip_check_transport(pool, PJSIP_TRANSPORT_UDP6, "UDP+IPv6");
+	sip_check_transport(pool, PJSIP_TRANSPORT_TCP6, "TCP+IPv6");
+	sip_check_transport(pool, PJSIP_TRANSPORT_TLS6, "TLS+IPv6");
+
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+	/* Replace the PJSIP resolver with our own implementation */
+	pjsip_endpt_set_ext_resolver(ast_sip_get_pjsip_endpoint(), &resolver);
+	return 0;
+}
+
+void ast_sip_initialize_resolver(void)
+{
+	/* Replace the existing PJSIP resolver with our own implementation */
+	ast_sip_push_task_synchronous(NULL, sip_replace_resolver, NULL);
+}
+
+#else
+
+void ast_sip_initialize_resolver(void)
+{
+	/* External resolver support does not exist in the version of PJSIP in use */
+	ast_log(LOG_NOTICE, "The version of PJSIP in use does not support external resolvers, using PJSIP provided resolver\n");
+}
+
+#endif
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 87ce2b0..b330d22 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -1077,6 +1077,7 @@
 	}
 
 	handle_outgoing_request(session, tdata);
+
 	pjsip_inv_send_msg(session->inv_session, tdata);
 	return;
 }
diff --git a/tests/test_dns_query_set.c b/tests/test_dns_query_set.c
new file mode 100644
index 0000000..08829f5
--- /dev/null
+++ b/tests/test_dns_query_set.c
@@ -0,0 +1,365 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Joshua Colp <jcolp at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <arpa/nameser.h>
+#include <arpa/inet.h>
+
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/vector.h"
+#include "asterisk/dns_core.h"
+#include "asterisk/dns_resolver.h"
+#include "asterisk/dns_query_set.h"
+#include "asterisk/dns_internal.h"
+
+struct query_set_data {
+	/*! Boolean indicator if query set has completed */
+	int query_set_complete;
+	/*! Number of times resolve() method has been called */
+	int resolves;
+	/*! Number of times resolve() method is allowed to be called */
+	int resolves_allowed;
+	/*! Number of times cancel() method has been called */
+	int cancel;
+	/*! Number of times cancel() method is allowed to be called */
+	int cancel_allowed;
+	ast_mutex_t lock;
+	ast_cond_t cond;
+};
+
+static void query_set_data_destructor(void *obj)
+{
+	struct query_set_data *qsdata = obj;
+
+	ast_mutex_destroy(&qsdata->lock);
+	ast_cond_destroy(&qsdata->cond);
+}
+
+static struct query_set_data *query_set_data_alloc(void)
+{
+	struct query_set_data *qsdata;
+
+	qsdata = ao2_alloc(sizeof(*qsdata), query_set_data_destructor);
+	if (!qsdata) {
+		return NULL;
+	}
+
+	ast_mutex_init(&qsdata->lock);
+	ast_cond_init(&qsdata->cond, NULL);
+
+	return qsdata;
+}
+
+#define DNS_ANSWER "Yes sirree"
+#define DNS_ANSWER_SIZE strlen(DNS_ANSWER)
+
+/*!
+ * \brief Thread that performs asynchronous resolution.
+ *
+ * This thread uses the query's user data to determine how to
+ * perform the resolution. If the allowed number of resolutions
+ * has not been reached then this will succeed, otherwise the
+ * query is expected to have been canceled.
+ *
+ * \param dns_query The ast_dns_query that is being performed
+ * \return NULL
+ */
+static void *resolution_thread(void *dns_query)
+{
+	struct ast_dns_query *query = dns_query;
+	struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
+	struct query_set_data *qsdata = query_set->user_data;
+
+	ast_assert(qsdata != NULL);
+
+	ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
+	ast_dns_resolver_completed(query);
+
+	ao2_ref(query, -1);
+	return NULL;
+}
+
+/*!
+ * \brief Resolver's resolve() method
+ *
+ * \param query The query that is to be resolved
+ * \retval 0 Successfully created thread to perform the resolution
+ * \retval non-zero Failed to create resolution thread
+ */
+static int query_set_resolve(struct ast_dns_query *query)
+{
+	struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
+	struct query_set_data *qsdata = query_set->user_data;
+	pthread_t resolver_thread;
+
+	/* Only the queries which will not be canceled actually start a thread */
+	if (qsdata->resolves++ < qsdata->cancel_allowed) {
+		return 0;
+	}
+
+	return ast_pthread_create_detached(&resolver_thread, NULL, resolution_thread, ao2_bump(query));
+}
+
+/*!
+ * \brief Resolver's cancel() method
+ *
+ * \param query The query to cancel
+ * \return 0
+ */
+static int query_set_cancel(struct ast_dns_query *query)
+{
+	struct ast_dns_query_set *query_set = ast_dns_query_get_data(query);
+	struct query_set_data *qsdata = query_set->user_data;
+	int res = -1;
+
+	if (qsdata->cancel++ < qsdata->cancel_allowed) {
+		res = 0;
+	}
+
+	return res;
+}
+
+static struct ast_dns_resolver query_set_resolver = {
+	.name = "query_set",
+	.priority = 0,
+	.resolve = query_set_resolve,
+	.cancel = query_set_cancel,
+};
+
+/*!
+ * \brief Callback which is invoked upon query set completion
+ *
+ * \param query_set The query set
+ */
+static void query_set_callback(const struct ast_dns_query_set *query_set)
+{
+	struct query_set_data *qsdata = ast_dns_query_set_get_data(query_set);
+
+	ast_mutex_lock(&qsdata->lock);
+	qsdata->query_set_complete = 1;
+	ast_cond_signal(&qsdata->cond);
+	ast_mutex_unlock(&qsdata->lock);
+}
+
+/*!
+ * \brief Framework for running a query set DNS test
+ *
+ * This function serves as a common way of testing various numbers of queries in a
+ * query set and optional canceling of them.
+ *
+ * \param test The test being run
+ * \param resolve The number of queries that should be allowed to complete resolution
+ * \param cancel The number of queries that should be allowed to be canceled
+ */
+static enum ast_test_result_state query_set_test(struct ast_test *test, int resolve, int cancel)
+{
+	int total = resolve + cancel;
+	RAII_VAR(struct ast_dns_query_set *, query_set, NULL, ao2_cleanup);
+	RAII_VAR(struct query_set_data *, qsdata, NULL, ao2_cleanup);
+	enum ast_test_result_state res = AST_TEST_PASS;
+	int idx;
+	struct timespec timeout;
+
+	if (ast_dns_resolver_register(&query_set_resolver)) {
+		ast_test_status_update(test, "Failed to register query set DNS resolver\n");
+		return AST_TEST_FAIL;
+	}
+
+	qsdata = query_set_data_alloc();
+	if (!qsdata) {
+		ast_test_status_update(test, "Failed to allocate data necessary for query set test\n");
+		res = AST_TEST_FAIL;
+		goto cleanup;
+	}
+
+	query_set = ast_dns_query_set_create();
+	if (!query_set) {
+		ast_test_status_update(test, "Failed to create DNS query set\n");
+		res = AST_TEST_FAIL;
+		goto cleanup;
+	}
+
+	qsdata->resolves_allowed = resolve;
+	qsdata->cancel_allowed = cancel;
+
+	for (idx = 0; idx < total; ++idx) {
+		if (ast_dns_query_set_add(query_set, "asterisk.org", ns_t_a, ns_c_in)) {
+			ast_test_status_update(test, "Failed to add query to DNS query set\n");
+			res = AST_TEST_FAIL;
+			goto cleanup;
+		}
+	}
+
+	if (ast_dns_query_set_num_queries(query_set) != total) {
+		ast_test_status_update(test, "DNS query set does not contain the correct number of queries\n");
+		res = AST_TEST_FAIL;
+		goto cleanup;
+	}
+
+	ast_dns_query_set_resolve_async(query_set, query_set_callback, qsdata);
+
+	if (cancel && (cancel == total)) {
+		if (ast_dns_query_set_resolve_cancel(query_set)) {
+			ast_test_status_update(test, "Failed to cancel DNS query set when it should be cancellable\n");
+			res = AST_TEST_FAIL;
+		}
+
+		if (qsdata->query_set_complete) {
+			ast_test_status_update(test, "Query set callback was invoked despite all queries being cancelled\n");
+			res = AST_TEST_FAIL;
+		}
+
+		goto cleanup;
+	} else if (cancel) {
+		if (!ast_dns_query_set_resolve_cancel(query_set)) {
+			ast_test_status_update(test, "Successfully cancelled DNS query set when it should not be possible\n");
+			res = AST_TEST_FAIL;
+			goto cleanup;
+		}
+	}
+
+	clock_gettime(CLOCK_REALTIME, &timeout);
+	timeout.tv_sec += 10;
+
+	ast_mutex_lock(&qsdata->lock);
+	while (!qsdata->query_set_complete) {
+		if (ast_cond_timedwait(&qsdata->cond, &qsdata->lock, &timeout) == ETIMEDOUT) {
+			break;
+		}
+	}
+	ast_mutex_unlock(&qsdata->lock);
+
+	if (!qsdata->query_set_complete) {
+		ast_test_status_update(test, "Query set did not complete when it should have\n");
+		res = AST_TEST_FAIL;
+		goto cleanup;
+	}
+
+	for (idx = 0; idx < ast_dns_query_set_num_queries(query_set); ++idx) {
+		const struct ast_dns_query *query = ast_dns_query_set_get(query_set, idx);
+
+		if (strcmp(ast_dns_query_get_name(query), "asterisk.org")) {
+			ast_test_status_update(test, "Query did not have expected name\n");
+			res = AST_TEST_FAIL;
+		}
+		if (ast_dns_query_get_rr_type(query) != ns_t_a) {
+			ast_test_status_update(test, "Query did not have expected type\n");
+			res = AST_TEST_FAIL;
+		}
+		if (ast_dns_query_get_rr_class(query) != ns_c_in) {
+			ast_test_status_update(test, "Query did not have expected class\n");
+			res = AST_TEST_FAIL;
+		}
+	}
+
+cleanup:
+	ast_dns_resolver_unregister(&query_set_resolver);
+	return res;
+}
+
+AST_TEST_DEFINE(query_set)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "query_set";
+		info->category = "/main/dns/query_set/";
+		info->summary = "Test nominal asynchronous DNS query set\n";
+		info->description =
+			"This tests nominal query set in the following ways:\n"
+			"\t* Multiple queries are added to a query set\n"
+			"\t* The mock resolver is configured to respond to all queries\n"
+			"\t* Asynchronous resolution of the query set is started\n"
+			"\t* The mock resolver responds to all queries\n"
+			"\t* We ensure that the query set callback is invoked upon completion\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return query_set_test(test, 4, 0);
+}
+
+AST_TEST_DEFINE(query_set_nominal_cancel)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "query_set_nominal_cancel";
+		info->category = "/main/dns/query_set/";
+		info->summary = "Test nominal asynchronous DNS query set cancellation\n";
+		info->description =
+			"This tests nominal query set cancellation in the following ways:\n"
+			"\t* Multiple queries are added to a query set\n"
+			"\t* The mock resolver is configured to NOT respond to any queries\n"
+			"\t* Asynchronous resolution of the query set is started\n"
+			"\t* The query set is canceled and is confirmed to return with success\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return query_set_test(test, 0, 4);
+}
+
+AST_TEST_DEFINE(query_set_off_nominal_cancel)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "query_set_off_nominal_cancel";
+		info->category = "/main/dns/query_set/";
+		info->summary = "Test off-nominal asynchronous DNS query set cancellation\n";
+		info->description =
+			"This tests nominal query set cancellation in the following ways:\n"
+			"\t* Multiple queries are added to a query set\n"
+			"\t* The mock resolver is configured to respond to half the queries\n"
+			"\t* Asynchronous resolution of the query set is started\n"
+			"\t* The query set is canceled and is confirmed to return failure\n"
+			"\t* The query set callback is confirmed to run, since it could not be fully canceled\n";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return query_set_test(test, 2, 2);
+}
+
+static int unload_module(void)
+{
+	AST_TEST_UNREGISTER(query_set);
+	AST_TEST_UNREGISTER(query_set_nominal_cancel);
+	AST_TEST_UNREGISTER(query_set_off_nominal_cancel);
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	AST_TEST_REGISTER(query_set);
+	AST_TEST_REGISTER(query_set_nominal_cancel);
+	AST_TEST_REGISTER(query_set_off_nominal_cancel);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "DNS query set tests");

-- 
To view, visit https://gerrit.asterisk.org/75
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I56cb03ce4f9d3d600776f36928e0b3e379b5d71e
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Joshua Colp <jcolp at digium.com>



More information about the asterisk-dev mailing list