mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	Merge "res_pjsip: Add external PJSIP resolver implementation using core DNS API."
This commit is contained in:
		
							
								
								
									
										17
									
								
								CHANGES
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGES
									
									
									
									
									
								
							| @@ -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
									
									
								
							
							
						
						
									
										122
									
								
								configure
									
									
									
									
										vendored
									
									
								
							| @@ -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="" | ||||
|   | ||||
| @@ -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]) | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|  * | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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; | ||||
| } | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										669
									
								
								res/res_pjsip/pjsip_resolver.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										669
									
								
								res/res_pjsip/pjsip_resolver.c
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										365
									
								
								tests/test_dns_query_set.c
									
									
									
									
									
										Normal 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"); | ||||
		Reference in New Issue
	
	Block a user