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 parallel and 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
This commit is contained in:
Joshua Colp 2015-04-13 10:47:01 -03:00
parent 60d1911482
commit a3cec44a0a
13 changed files with 1479 additions and 55 deletions

17
CHANGES
View File

@ -83,6 +83,10 @@ Core
dedicated thread per consumer in certain cases. The initial settings for
the thread pool can now be configured in 'stasis.conf'.
* A new core DNS API has been implemented which provides a common interface
for DNS functionality. Modules that use this functionality will require that
a DNS resolver module is loaded and available.
Functions
------------------
@ -110,6 +114,19 @@ res_musiconhold
over the channel-set musicclass. This allows separate hold-music from
application (e.g. Queue or Dial) specified music.
res_resolver_unbound
------------------
* Added a res_resolver_unbound module which uses the libunbound resolver library
to perform DNS resolution. This module requires the libunbound library to be
installed in order to be used.
res_pjsip
------------------
* A new SIP resolver using the core DNS API has been implemented. This relies on
external SIP resolver support in PJSIP which is only available as of PJSIP
2.4. If this support is unavailable the existing built-in PJSIP SIP resolver
will be used instead. The new SIP resolver provides NAPTR support, improved
SRV support, and AAAA record support.
CEL Backends
------------------

122
configure vendored
View File

@ -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 @@ PBX_PORTAUDIO
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
@ -10310,6 +10314,18 @@ 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
PORTAUDIO_DESCRIP="PortAudio"
PORTAUDIO_OPTION="portaudio"
PBX_PORTAUDIO=0
@ -24470,6 +24486,110 @@ fi
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=""

View File

@ -458,6 +458,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJ_TRANSACTION_GRP_LOCK], [PJSIP Transaction Group L
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 @@ CPPFLAGS="${saved_cppflags}"
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])

View File

@ -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

View File

@ -204,6 +204,15 @@ int ast_dns_record_get_ttl(const struct ast_dns_record *record);
*/
const char *ast_dns_record_get_data(const struct ast_dns_record *record);
/*!
* \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
*

View File

@ -23,6 +23,12 @@
* \author Joshua Colp <jcolp@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 @@ struct ast_dns_query_recurring {
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 @@ int dns_parse_short(unsigned char *cur, uint16_t *val);
* \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);

View File

@ -43,6 +43,8 @@ typedef void (*ast_dns_query_set_callback)(const struct ast_dns_query_set *query
*
* \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 @@ size_t ast_dns_query_set_num_queries(const struct ast_dns_query_set *query_set);
*
* \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,29 +110,25 @@ void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dn
*
* \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)
}
#endif

View File

@ -32,7 +32,6 @@
ASTERISK_REGISTER_FILE()
#include "asterisk/linkedlists.h"
#include "asterisk/vector.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
#include "asterisk/sched.h"
@ -163,6 +162,11 @@ const char *ast_dns_record_get_data(const struct ast_dns_record *record)
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 +190,9 @@ static void dns_query_destroy(void *data)
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,34 +219,46 @@ struct ast_dns_query_active *ast_dns_resolve_async(const char *name, int rr_type
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;
}
if (active->query->resolver->resolve(active->query)) {
ast_log(LOG_ERROR, "Resolver '%s' returned an error when resolving '%s' of class '%d' and type '%d'\n",
active->query->resolver->name, name, rr_class, rr_type);

View File

@ -33,39 +33,117 @@ ASTERISK_REGISTER_FILE()
#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,
};
ast_assert(!query_set->in_progress);
if (query_set->in_progress) {
ast_log(LOG_ERROR, "Attempted to add additional query to query set '%p' after resolution has started\n",
query_set);
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 +153,104 @@ void *ast_dns_query_set_get_data(const struct ast_dns_query_set *query_set)
void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dns_query_set_callback callback, void *data)
{
int idx;
ast_assert(!query_set->in_progress);
if (query_set->in_progress) {
ast_log(LOG_ERROR, "Attempted to start asynchronous resolution of query set '%p' when it has already started\n",
query_set);
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;
}

View File

@ -3480,8 +3480,6 @@ static int load_module(void)
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 @@ static int load_module(void)
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();

View File

@ -231,6 +231,12 @@ void ast_sip_destroy_system(void);
*/
void ast_sip_initialize_dns(void);
/*!
* \internal
* \brief Initialize our own resolver support
*/
void ast_sip_initialize_resolver(void);
/*!
* \internal
* \brief Initialize global configuration

View File

@ -0,0 +1,669 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Digium, Inc.
*
* Joshua Colp <jcolp@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)) {
ast_debug(2, "[%p] NAPTR service %s skipped as transport is unavailable\n",
resolve, service);
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, have_naptr = 0, have_srv = 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 replaces the results
* of the last.
*/
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 NAPTR or SRV records exist the subsequent results from them take preference */
if (have_naptr || have_srv) {
ast_debug(2, "[%p] %s record being skipped on target '%s' because NAPTR or SRV record exists\n",
resolve, ast_dns_record_get_rr_type(record) == ns_t_a ? "A" : "AAAA",
ast_dns_query_get_name(query));
continue;
}
/* PJSIP has a fixed maximum number of addresses that can exist, so limit ourselves to that */
if (address_count == PJSIP_MAX_RESOLVED_ADDRESSES) {
break;
}
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) {
if (have_naptr) {
ast_debug(2, "[%p] SRV record being skipped on target '%s' because NAPTR record exists\n",
resolve, ast_dns_query_get_name(query));
continue;
}
/* 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));
have_srv = 1;
}
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));
have_srv = 1;
}
} 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) {
have_naptr = 1;
strict_order = 1;
order = ast_dns_naptr_get_order(record);
}
}
}
}
/* Update the server addresses count, this is not limited as it can never exceed the max allowed */
resolve->addresses.count = address_count;
/* 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

365
tests/test_dns_query_set.c Normal file
View File

@ -0,0 +1,365 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Digium, Inc.
*
* Joshua Colp <jcolp@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");