dns: Add core DNS API + unit tests and res_resolver_unbound module + unit tests.

This change adds an abstracted core DNS API which resembles the API described
here[1]. The API provides a pluggable mechanism for resolvers and also a
consistent view for records. Both synchronous and asynchronous queries are
supported.

This change also adds a res_resolver_unbound module which uses the libunbound
library to provide resolution.

Unit tests have also been written for all of the above to confirm the API and
functionality.

ASTERISK-24834 #close
Reported by: Matt Jordan

ASTERISK-24836 #close
Reported by: Matt Jordan

Review: https://reviewboard.asterisk.org/r/4474/
Review: https://reviewboard.asterisk.org/r/4512/

[1] https://wiki.asterisk.org/wiki/display/AST/Asterisk+DNS+API


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@433370 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Joshua Colp 2015-03-25 12:32:26 +00:00
parent 4c2fc5b811
commit abf3e40902
23 changed files with 5435 additions and 1 deletions

View File

@ -65,6 +65,7 @@ OPENSSL=@PBX_OPENSSL@
SUPPSERV=@PBX_SUPPSERV@
SYSLOG=@PBX_SYSLOG@
TONEZONE=@PBX_TONEZONE@
UNBOUND=@PBX_UNBOUND@
UNIXODBC=@PBX_UNIXODBC@
VORBIS=@PBX_VORBIS@
VPB=@PBX_VPB@

View File

@ -0,0 +1,24 @@
; Unbound DNS Resolver Configuration
;
; This file serves as a reference for the configurable options within the
; unbound DNS resolver.
[general]
;hosts = /etc/hosts ; Full path to a hosts file which contains a mapping of
; ; hostnames to addresses. If "system" is specified then
; ; the system specific hosts file will be used. (default: system)
;resolv = /etc/resolv.conf ; Full path to a resolv.conf which contains the nameservers
; ; to use for resolution. If "system" is specified then the
; ; system specific resolv.conf file will be used. (default: system)
;nameserver = 127.0.0.1 ; An explicit nameserver to use for queries. If this option
; ; is specified multiple times the first configured one will
; ; be treated as the primary with each subsequent one being
; ; a backup. If the resolv options is also specified the
; ; nameservers from it will be tried after all nameserver
; ; options.
;debug = 99 ; The debug level to run the unbound resolver at. While
; ; there is no explicit range the higher the number the more
; ; debug is output.
;ta_file = /etc/asterisk/dnssec_keys ; Full path to a trusted anchors key file. These keys are
; ; used to verify DNSSEC signed results.

145
configure vendored
View File

@ -1,5 +1,5 @@
#! /bin/sh
# From configure.ac Revision: 432282 .
# From configure.ac Revision: 432815 .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for asterisk trunk.
#
@ -731,6 +731,10 @@ PBX_UNIXODBC
UNIXODBC_DIR
UNIXODBC_INCLUDE
UNIXODBC_LIB
PBX_UNBOUND
UNBOUND_DIR
UNBOUND_INCLUDE
UNBOUND_LIB
PBX_TONEZONE
TONEZONE_DIR
TONEZONE_INCLUDE
@ -1357,6 +1361,7 @@ with_termcap
with_timerfd
with_tinfo
with_tonezone
with_unbound
with_unixodbc
with_vorbis
with_vpb
@ -2097,6 +2102,7 @@ Optional Packages:
--with-timerfd=PATH use timerfd files in PATH
--with-tinfo=PATH use Term Info files in PATH
--with-tonezone=PATH use tonezone files in PATH
--with-unbound=PATH use unbound files in PATH
--with-unixodbc=PATH use unixODBC files in PATH
--with-vorbis=PATH use Vorbis files in PATH
--with-vpb=PATH use Voicetronix API files in PATH
@ -11299,6 +11305,38 @@ fi
UNBOUND_DESCRIP="unbound"
UNBOUND_OPTION="unbound"
PBX_UNBOUND=0
# Check whether --with-unbound was given.
if test "${with_unbound+set}" = set; then :
withval=$with_unbound;
case ${withval} in
n|no)
USE_UNBOUND=no
# -1 is a magic value used by menuselect to know that the package
# was disabled, other than 'not found'
PBX_UNBOUND=-1
;;
y|ye|yes)
ac_mandatory_list="${ac_mandatory_list} UNBOUND"
;;
*)
UNBOUND_DIR="${withval}"
ac_mandatory_list="${ac_mandatory_list} UNBOUND"
;;
esac
fi
UNIXODBC_DESCRIP="unixODBC"
UNIXODBC_OPTION="unixodbc"
PBX_UNIXODBC=0
@ -22704,6 +22742,111 @@ fi
if test "x${PBX_UNBOUND}" != "x1" -a "${USE_UNBOUND}" != "no"; then
pbxlibdir=""
# if --with-UNBOUND=DIR has been specified, use it.
if test "x${UNBOUND_DIR}" != "x"; then
if test -d ${UNBOUND_DIR}/lib; then
pbxlibdir="-L${UNBOUND_DIR}/lib"
else
pbxlibdir="-L${UNBOUND_DIR}"
fi
fi
pbxfuncname="ub_ctx_create"
if test "x${pbxfuncname}" = "x" ; then # empty lib, assume only headers
AST_UNBOUND_FOUND=yes
else
ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
CFLAGS="${CFLAGS} "
as_ac_Lib=`$as_echo "ac_cv_lib_unbound_${pbxfuncname}" | $as_tr_sh`
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lunbound" >&5
$as_echo_n "checking for ${pbxfuncname} in -lunbound... " >&6; }
if eval \${$as_ac_Lib+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lunbound ${pbxlibdir} $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_UNBOUND_FOUND=yes
else
AST_UNBOUND_FOUND=no
fi
CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
fi
# now check for the header.
if test "${AST_UNBOUND_FOUND}" = "yes"; then
UNBOUND_LIB="${pbxlibdir} -lunbound "
# if --with-UNBOUND=DIR has been specified, use it.
if test "x${UNBOUND_DIR}" != "x"; then
UNBOUND_INCLUDE="-I${UNBOUND_DIR}/include"
fi
UNBOUND_INCLUDE="${UNBOUND_INCLUDE} "
if test "xunbound.h" = "x" ; then # no header, assume found
UNBOUND_HEADER_FOUND="1"
else # check for the header
ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
CPPFLAGS="${CPPFLAGS} ${UNBOUND_INCLUDE}"
ac_fn_c_check_header_mongrel "$LINENO" "unbound.h" "ac_cv_header_unbound_h" "$ac_includes_default"
if test "x$ac_cv_header_unbound_h" = xyes; then :
UNBOUND_HEADER_FOUND=1
else
UNBOUND_HEADER_FOUND=0
fi
CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
fi
if test "x${UNBOUND_HEADER_FOUND}" = "x0" ; then
UNBOUND_LIB=""
UNBOUND_INCLUDE=""
else
if test "x${pbxfuncname}" = "x" ; then # only checking headers -> no library
UNBOUND_LIB=""
fi
PBX_UNBOUND=1
cat >>confdefs.h <<_ACEOF
#define HAVE_UNBOUND 1
_ACEOF
fi
fi
fi
if test "x${PBX_UNIXODBC}" != "x1" -a "${USE_UNIXODBC}" != "no"; then
pbxlibdir=""
# if --with-UNIXODBC=DIR has been specified, use it.

View File

@ -507,6 +507,7 @@ AST_EXT_LIB_SETUP([TERMCAP], [Termcap], [termcap])
AST_EXT_LIB_SETUP([TIMERFD], [timerfd], [timerfd])
AST_EXT_LIB_SETUP([TINFO], [Term Info], [tinfo])
AST_EXT_LIB_SETUP([TONEZONE], [tonezone], [tonezone])
AST_EXT_LIB_SETUP([UNBOUND], [unbound], [unbound])
AST_EXT_LIB_SETUP([UNIXODBC], [unixODBC], [unixodbc])
AST_EXT_LIB_SETUP([VORBIS], [Vorbis], [vorbis])
AST_EXT_LIB_SETUP([VPB], [Voicetronix API], [vpb])
@ -2032,6 +2033,8 @@ AST_EXT_TOOL_CHECK([NETSNMP], [net-snmp-config], , [--agent-libs],
AST_EXT_LIB_CHECK([NEWT], [newt], [newtBell], [newt.h])
AST_EXT_LIB_CHECK([UNBOUND], [unbound], [ub_ctx_create], [unbound.h], [])
AST_EXT_LIB_CHECK([UNIXODBC], [odbc], [SQLConnect], [sql.h], [])
AST_EXT_LIB_CHECK([OGG], [ogg], [ogg_sync_init], [])

View File

@ -1065,6 +1065,9 @@
/* Define to 1 if you have the `truncl' function. */
#undef HAVE_TRUNCL
/* Define to 1 if you have the unbound library. */
#undef HAVE_UNBOUND
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H

267
include/asterisk/dns_core.h Normal file
View File

@ -0,0 +1,267 @@
/*
* 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.
*/
/*! \file
* \brief Core DNS API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_CORE_H
#define _ASTERISK_DNS_CORE_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*! \brief Opaque structure for an active DNS query */
struct ast_dns_query_active;
/*! \brief Opaque structure for a DNS query */
struct ast_dns_query;
/*!
* \brief Get the name queried in a DNS query
*
* \param query The DNS query
*
* \return the name queried
*/
const char *ast_dns_query_get_name(const struct ast_dns_query *query);
/*!
* \brief Get the record resource type of a DNS query
*
* \param query The DNS query
*
* \return the record resource type
*/
int ast_dns_query_get_rr_type(const struct ast_dns_query *query);
/*!
* \brief Get the record resource class of a DNS query
*
* \param query The DNS query
*
* \return the record resource class
*/
int ast_dns_query_get_rr_class(const struct ast_dns_query *query);
/*!
* \brief Get the user specific data of a DNS query
*
* \param query The DNS query
*
* \return the user specific data
*
* \note The reference count of the data is NOT incremented on return
*/
void *ast_dns_query_get_data(const struct ast_dns_query *query);
/*! \brief Opaque structure for a DNS query result, guaranteed to be immutable */
struct ast_dns_result;
/*!
* \brief Get the result information for a DNS query
*
* \param query The DNS query
*
* \return the DNS result information
*
* \note The result is NOT ao2 allocated
*/
struct ast_dns_result *ast_dns_query_get_result(const struct ast_dns_query *query);
/*!
* \brief Get whether the result is secure or not
*
* \param result The DNS result
*
* \return whether the result is secure or not
*/
unsigned int ast_dns_result_get_secure(const struct ast_dns_result *result);
/*!
* \brief Get whether the result is bogus or not
*
* \param result The DNS result
*
* \return whether the result is bogus or not
*/
unsigned int ast_dns_result_get_bogus(const struct ast_dns_result *result);
/*!
* \brief Get the error rcode of a DN result
*
* \param query The DNS result
*
* \return the DNS rcode
*/
unsigned int ast_dns_result_get_rcode(const struct ast_dns_result *result);
/*!
* \brief Get the canonical name of the result
*
* \param result The DNS result
*
* \return the canonical name
*/
const char *ast_dns_result_get_canonical(const struct ast_dns_result *result);
/*!
* \brief Get the first record of a DNS Result
*
* \param result The DNS result
*
* \return first DNS record
*/
const struct ast_dns_record *ast_dns_result_get_records(const struct ast_dns_result *result);
/*!
* \brief Get the raw DNS answer from a DNS result
*
* \param result The DNS result
*
* \return The DNS result
*/
const char *ast_dns_result_get_answer(const struct ast_dns_result *result);
/*!
* \brief Retrieve the lowest TTL from a result
*
* \param result The DNS result
*
* \return the lowest TTL
*
* \note If no records exist this function will return a TTL of 0
*/
int ast_dns_result_get_lowest_ttl(const struct ast_dns_result *result);
/*!
* \brief Free the DNS result information
*
* \param result The DNS result
*/
void ast_dns_result_free(struct ast_dns_result *result);
/*! \brief Opaque structure for a DNS record */
struct ast_dns_record;
/*!
* \brief Callback invoked when a query completes
*
* \param query The DNS query that was invoked
*/
typedef void (*ast_dns_resolve_callback)(const struct ast_dns_query *query);
/*!
* \brief Get the resource record type of a DNS record
*
* \param record The DNS record
*
* \return the resource record type
*/
int ast_dns_record_get_rr_type(const struct ast_dns_record *record);
/*!
* \brief Get the resource record class of a DNS record
*
* \param record The DNS record
*
* \return the resource record class
*/
int ast_dns_record_get_rr_class(const struct ast_dns_record *record);
/*!
* \brief Get the TTL of a DNS record
*
* \param record The DNS record
*
* \return the TTL
*/
int ast_dns_record_get_ttl(const struct ast_dns_record *record);
/*!
* \brief Retrieve the raw DNS record
*
* \param record The DNS record
*
* \return the raw DNS record
*/
const char *ast_dns_record_get_data(const struct ast_dns_record *record);
/*!
* \brief Get the next DNS record
*
* \param record The current DNS record
*
* \return the next DNS record
*/
const struct ast_dns_record *ast_dns_record_get_next(const struct ast_dns_record *record);
/*!
* \brief Asynchronously resolve a DNS query
*
* \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 - query has been sent for resolution
* \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 active query must be released upon completion or cancellation using ao2_ref
*/
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);
/*!
* \brief Cancel an asynchronous DNS resolution
*
* \param active The active DNS query returned from ast_dns_resolve_async
*
* \retval 0 success
* \retval -1 failure
*
* \note If successfully cancelled the callback will not be invoked
*/
int ast_dns_resolve_cancel(struct ast_dns_query_active *active);
/*!
* \brief Synchronously resolve a DNS query
*
* \param name The name of what to resolve
* \param rr_type Resource record type
* \param rr_class Resource record class
* \param result A pointer to hold the DNS result
*
* \retval 0 success - query was completed and result is available
* \retval -1 failure
*/
int ast_dns_resolve(const char *name, int rr_type, int rr_class, struct ast_dns_result **result);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_CORE_H */

View File

@ -0,0 +1,145 @@
/*
* 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.
*/
/*! \file
*
* \brief Internal DNS structure definitions
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*! \brief Generic DNS record information */
struct ast_dns_record {
/*! \brief Resource record type */
int rr_type;
/*! \brief Resource record class */
int rr_class;
/*! \brief Time-to-live of the record */
int ttl;
/*! \brief The size of the raw DNS record */
size_t data_len;
/*! \brief Linked list information */
AST_LIST_ENTRY(ast_dns_record) list;
/*! \brief The raw DNS record */
char data[0];
};
/*! \brief An SRV record */
struct ast_dns_srv_record {
/*! \brief Generic DNS record information */
struct ast_dns_record generic;
/*! \brief The hostname in the SRV record */
const char *host;
/*! \brief The priority of the SRV record */
unsigned short priority;
/*! \brief The weight of the SRV record */
unsigned short weight;
/*! \brief The port in the SRV record */
unsigned short port;
};
/*! \brief A NAPTR record */
struct ast_dns_naptr_record {
/*! \brief Generic DNS record information */
struct ast_dns_record generic;
/*! \brief The flags from the NAPTR record */
const char *flags;
/*! \brief The service from the NAPTR record */
const char *service;
/*! \brief The regular expression from the NAPTR record */
const char *regexp;
/*! \brief The replacement from the NAPTR record */
const char *replacement;
/*! \brief The order for the NAPTR record */
unsigned short order;
/*! \brief The preference of the NAPTR record */
unsigned short preference;
};
/*! \brief The result of a DNS query */
struct ast_dns_result {
/*! \brief Whether the result is secure */
unsigned int secure;
/*! \brief Whether the result is bogus */
unsigned int bogus;
/*! \brief Optional rcode, set if an error occurred */
unsigned int rcode;
/*! \brief Records returned */
AST_LIST_HEAD_NOLOCK(, ast_dns_record) records;
/*! \brief The canonical name */
const char *canonical;
/*! \brief The raw DNS answer */
const char *answer;
/*! \brief Buffer for dynamic data */
char buf[0];
};
/*! \brief A DNS query */
struct ast_dns_query {
/*! \brief Callback to invoke upon completion */
ast_dns_resolve_callback callback;
/*! \brief User-specific data */
void *user_data;
/*! \brief The resolver in use for this query */
struct ast_dns_resolver *resolver;
/*! \brief Resolver-specific data */
void *resolver_data;
/*! \brief Result of the DNS query */
struct ast_dns_result *result;
/*! \brief Resource record type */
int rr_type;
/*! \brief Resource record class */
int rr_class;
/*! \brief The name of what is being resolved */
char name[0];
};
/*! \brief A recurring DNS query */
struct ast_dns_query_recurring {
/*! \brief Callback to invoke upon completion */
ast_dns_resolve_callback callback;
/*! \brief User-specific data */
void *user_data;
/*! \brief Current active query */
struct ast_dns_query_active *active;
/*! \brief The recurring query has been cancelled */
unsigned int cancelled;
/*! \brief Scheduled timer for next resolution */
int timer;
/*! \brief Resource record type */
int rr_type;
/*! \brief Resource record class */
int rr_class;
/*! \brief The name of what is being resolved */
char name[0];
};
/*! \brief An active DNS query */
struct ast_dns_query_active {
/*! \brief The underlying DNS query */
struct ast_dns_query *query;
};
struct ast_sched_context;
/*!
* \brief Retrieve the DNS scheduler context
*
* \return scheduler context
*/
struct ast_sched_context *ast_dns_get_sched(void);

View File

@ -0,0 +1,89 @@
/*
* 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.
*/
/*! \file
* \brief DNS NAPTR Record Parsing API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_NAPTR_H
#define _ASTERISK_DNS_NAPTR_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*!
* \brief Get the flags from a NAPTR record
*
* \param record The DNS record
*
* \return the flags
*/
const char *ast_dns_naptr_get_flags(const struct ast_dns_record *record);
/*!
* \brief Get the service from a NAPTR record
*
* \param record The DNS record
*
* \return the service
*/
const char *ast_dns_naptr_get_service(const struct ast_dns_record *record);
/*!
* \brief Get the regular expression from a NAPTR record
*
* \param record The DNS record
*
* \return the regular expression
*/
const char *ast_dns_naptr_get_regexp(const struct ast_dns_record *record);
/*!
* \brief Get the replacement value from a NAPTR record
*
* \param record The DNS record
*
* \return the replacement value
*/
const char *ast_dns_naptr_get_replacement(const struct ast_dns_record *record);
/*!
* \brief Get the order from a NAPTR record
*
* \param record The DNS record
*
* \return the order
*/
unsigned short ast_dns_naptr_get_order(const struct ast_dns_record *record);
/*!
* \brief Get the preference from a NAPTR record
*
* \param record The DNS record
*
* \return the preference
*/
unsigned short ast_dns_naptr_get_preference(const struct ast_dns_record *record);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_NAPTR_H */

View File

@ -0,0 +1,136 @@
/*
* 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.
*/
/*! \file
* \brief DNS Query Set API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_QUERY_SET_H
#define _ASTERISK_DNS_QUERY_SET_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*! \brief Opaque structure for a set of DNS queries */
struct ast_dns_query_set;
/*!
* \brief Callback invoked when a query set completes
*
* \param query_set The DNS query set that was invoked
*/
typedef void (*ast_dns_query_set_callback)(const struct ast_dns_query_set *query_set);
/*!
* \brief Create a query set to hold queries
*
* \retval non-NULL success
* \retval NULL failure
*/
struct ast_dns_query_set *ast_dns_query_set_create(void);
/*!
* \brief Add a query to a query set
*
* \param query_set A DNS query set
* \param name The name of what to resolve
* \param rr_type Resource record type
* \param rr_class Resource record class
*
* \retval 0 success
* \retval -1 failure
*/
int ast_dns_query_set_add(struct ast_dns_query_set *query_set, const char *name, int rr_type, int rr_class);
/*!
* \brief Retrieve the number of queries in a query set
*
* \param query_set A DNS query set
*
* \return the number of queries
*/
size_t ast_dns_query_set_num_queries(const struct ast_dns_query_set *query_set);
/*!
* \brief Retrieve a query from a query set
*
* \param query_set A DNS query set
* \param index The index of the query to retrieve
*
* \retval non-NULL success
* \retval NULL failure
*/
struct ast_dns_query *ast_dns_query_set_get(const struct ast_dns_query_set *query_set, unsigned int index);
/*!
* \brief Retrieve user specific data from a query set
*
* \param query_set A DNS query set
*
* \return user specific data
*/
void *ast_dns_query_set_get_data(const struct ast_dns_query_set *query_set);
/*!
* \brief Asynchronously resolve queries in a query set
*
* \param query_set The query set
* \param callback The callback to invoke upon completion
* \param data User data to make available on the query set
*
* \note The callback will be invoked when all queries have completed
*
* \note The user data passed in to this function must be ao2 allocated
*/
void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dns_query_set_callback callback, void *data);
/*!
* \brief Synchronously resolve queries in a query set
*
* \param query_set The query set
*
* \note This function will return when all queries have been completed
*/
void 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
*
* \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
#endif /* _ASTERISK_DNS_QUERY_SET_H */

View File

@ -0,0 +1,78 @@
/*
* 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.
*/
/*! \file
* \brief DNS Recurring Resolution API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_RECURRING_H
#define _ASTERISK_DNS_RECURRING_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*! \brief Opaque structure for a recurring DNS query */
struct ast_dns_query_recurring;
/*!
* \brief Asynchronously resolve a DNS query, and continue resolving it according to the lowest TTL available
*
* \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 - query has been sent for resolution
* \retval NULL failure
*
* \note The user data passed in to this function must be ao2 allocated
*
* \note This query will continue to happen according to the lowest TTL unless cancelled using ast_dns_resolve_recurring_cancel
*
* \note It is NOT possible for the callback to be invoked concurrently for the query multiple times
*
* \note The query will occur when the TTL expires, not before. This means that there is a period of time where the previous
* information can be considered stale.
*
* \note If the TTL is determined to be 0 (the record specifies 0, or no records exist) this will cease doing a recurring query.
* It is the responsibility of the caller to resume querying at an interval they determine.
*/
struct ast_dns_query_recurring *ast_dns_resolve_recurring(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data);
/*!
* \brief Cancel an asynchronous recurring DNS resolution
*
* \param query The DNS query returned from ast_dns_resolve_recurring
*
* \retval 0 success - any active query has been cancelled and the query will no longer occur
* \retval -1 failure - an active query was in progress and could not be cancelled
*
* \note If successfully cancelled the callback will not be invoked
*
* \note This function does NOT drop your reference to the recurring query, this should be dropped using ao2_ref
*/
int ast_dns_resolve_recurring_cancel(struct ast_dns_query_recurring *recurring);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_RECURRING_H */

View File

@ -0,0 +1,142 @@
/*
* 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.
*/
/*! \file
* \brief DNS Resolver API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_RESOLVER_H
#define _ASTERISK_DNS_RESOLVER_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*! \brief DNS resolver implementation */
struct ast_dns_resolver {
/*! \brief The name of the resolver implementation */
const char *name;
/*! \brief Priority for this resolver if multiple exist, lower being higher priority */
unsigned int priority;
/*!
* \brief Perform resolution of a DNS query
*
* \note The reference count of the query should be increased and released
* upon the query completing or being successfully cancelled
*/
int (*resolve)(struct ast_dns_query *query);
/*! \brief Cancel resolution of a DNS query */
int (*cancel)(struct ast_dns_query *query);
/*! \brief Linked list information */
AST_RWLIST_ENTRY(ast_dns_resolver) next;
};
/*!
* \brief Set resolver specific data on a query
*
* \param query The DNS query
* \param data The resolver specific data
*
* \note The resolver data MUST be an ao2 object
*
* \note This function increments the reference count of the resolver data, it does NOT steal
*
* \note Once resolver specific data has been set it can not be changed
*
* \retval 0 success
* \retval -1 failure, resolver data is already set
*/
int ast_dns_resolver_set_data(struct ast_dns_query *query, void *data);
/*!
* \brief Retrieve resolver specific data
*
* \param query The DNS query
*
* \return the resolver specific data
*
* \note The reference count of the resolver data is NOT incremented on return
*/
void *ast_dns_resolver_get_data(const struct ast_dns_query *query);
/*!
* \brief Set result information for a DNS query
*
* \param query The DNS query
* \param result Whether the result is secured or not
* \param bogus Whether the result is bogus or not
* \param rcode Optional response code
* \param canonical The canonical name
* \param answer The raw DNS answer
* \param answer_size The size of the raw DNS answer
*
* \retval 0 success
* \retval -1 failure
*/
int ast_dns_resolver_set_result(struct ast_dns_query *query, unsigned int secure, unsigned int bogus,
unsigned int rcode, const char *canonical, const char *answer, size_t answer_size);
/*!
* \brief Add a DNS record to the result of a DNS query
*
* \param query The DNS query
* \param rr_type Resource record type
* \param rr_class Resource record class
* \param ttl TTL of the record
* \param data The raw DNS record
* \param size The size of the raw DNS record
*
* \retval 0 success
* \retval -1 failure
*/
int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr_class, int ttl, const char *data, const size_t size);
/*!
* \brief Mark a DNS query as having been completed
*
* \param query The DNS query
*/
void ast_dns_resolver_completed(struct ast_dns_query *query);
/*!
* \brief Register a DNS resolver
*
* \param resolver A DNS resolver implementation
*
* \retval 0 success
* \retval -1 failure
*/
int ast_dns_resolver_register(struct ast_dns_resolver *resolver);
/*!
* \brief Unregister a DNS resolver
*
* \param resolver A DNS resolver implementation
*/
void ast_dns_resolver_unregister(struct ast_dns_resolver *resolver);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_RESOLVER_H */

View File

@ -0,0 +1,71 @@
/*
* 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.
*/
/*! \file
* \brief DNS SRV Record Parsing API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_SRV_H
#define _ASTERISK_DNS_SRV_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*!
* \brief Get the hostname from an SRV record
*
* \param record The DNS record
*
* \return the hostname
*/
const char *ast_dns_srv_get_host(const struct ast_dns_record *record);
/*!
* \brief Get the priority from an SRV record
*
* \param record The DNS record
*
* \return the priority
*/
unsigned short ast_dns_srv_get_priority(const struct ast_dns_record *record);
/*!
* \brief Get the weight from an SRV record
*
* \param record The DNS record
*
* \return the weight
*/
unsigned short ast_dns_srv_get_weight(const struct ast_dns_record *record);
/*!
* \brief Get the port from an SRV record
*
* \param record The DNS record
*
* \return the port
*/
unsigned short ast_dns_srv_get_port(const struct ast_dns_record *record);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_SRV_H */

View File

@ -0,0 +1,72 @@
/*
* 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.
*/
/*! \file
* \brief DNS TLSA Record Parsing API
* \author Joshua Colp <jcolp@digium.com>
*/
#ifndef _ASTERISK_DNS_TLSA_H
#define _ASTERISK_DNS_TLSA_H
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/*!
* \brief Get the certificate usage field from a TLSA record
*
* \param record The DNS record
*
* \return the certificate usage field
*/
unsigned int ast_dns_tlsa_get_usage(const struct ast_dns_record *record);
/*!
* \brief Get the selector field from a TLSA record
*
* \param record The DNS record
*
* \return the selector field
*/
unsigned int ast_dns_tlsa_get_selector(const struct ast_dns_record *record);
/*!
* \brief Get the matching type field from a TLSA record
*
* \param record The DNS record
*
* \return the matching type field
*/
unsigned int ast_dns_tlsa_get_matching_type(const struct ast_dns_record *record);
/*!
* \brief Get the certificate association data from a TLSA record
*
* \param record The DNS record
*
* \return the certificate association data
*/
const char *ast_dns_tlsa_get_association_data(const struct ast_dns_record *record);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* _ASTERISK_DNS_TLSA_H */

566
main/dns_core.c Normal file
View File

@ -0,0 +1,566 @@
/*
* 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.
*/
/*! \file
*
* \brief Core DNS Functionality
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/linkedlists.h"
#include "asterisk/vector.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
#include "asterisk/sched.h"
#include "asterisk/dns_core.h"
#include "asterisk/dns_srv.h"
#include "asterisk/dns_tlsa.h"
#include "asterisk/dns_recurring.h"
#include "asterisk/dns_resolver.h"
#include "asterisk/dns_internal.h"
#include <arpa/nameser.h>
AST_RWLIST_HEAD_STATIC(resolvers, ast_dns_resolver);
static struct ast_sched_context *sched;
struct ast_sched_context *ast_dns_get_sched(void)
{
return sched;
}
const char *ast_dns_query_get_name(const struct ast_dns_query *query)
{
return query->name;
}
int ast_dns_query_get_rr_type(const struct ast_dns_query *query)
{
return query->rr_type;
}
int ast_dns_query_get_rr_class(const struct ast_dns_query *query)
{
return query->rr_class;
}
void *ast_dns_query_get_data(const struct ast_dns_query *query)
{
return query->user_data;
}
struct ast_dns_result *ast_dns_query_get_result(const struct ast_dns_query *query)
{
return query->result;
}
unsigned int ast_dns_result_get_secure(const struct ast_dns_result *result)
{
return result->secure;
}
unsigned int ast_dns_result_get_bogus(const struct ast_dns_result *result)
{
return result->bogus;
}
unsigned int ast_dns_result_get_rcode(const struct ast_dns_result *result)
{
return result->rcode;
}
const char *ast_dns_result_get_canonical(const struct ast_dns_result *result)
{
return result->canonical;
}
const struct ast_dns_record *ast_dns_result_get_records(const struct ast_dns_result *result)
{
return AST_LIST_FIRST(&result->records);
}
const char *ast_dns_result_get_answer(const struct ast_dns_result *result)
{
return result->answer;
}
int ast_dns_result_get_lowest_ttl(const struct ast_dns_result *result)
{
int ttl = 0;
const struct ast_dns_record *record;
if (ast_dns_result_get_rcode(result) == ns_r_nxdomain) {
return 0;
}
for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
if (!ttl || (ast_dns_record_get_ttl(record) && (ast_dns_record_get_ttl(record) < ttl))) {
ttl = ast_dns_record_get_ttl(record);
}
}
return ttl;
}
void ast_dns_result_free(struct ast_dns_result *result)
{
struct ast_dns_record *record;
if (!result) {
return;
}
while ((record = AST_LIST_REMOVE_HEAD(&result->records, list))) {
ast_free(record);
}
ast_free(result);
}
int ast_dns_record_get_rr_type(const struct ast_dns_record *record)
{
return record->rr_type;
}
int ast_dns_record_get_rr_class(const struct ast_dns_record *record)
{
return record->rr_class;
}
int ast_dns_record_get_ttl(const struct ast_dns_record *record)
{
return record->ttl;
}
const char *ast_dns_record_get_data(const struct ast_dns_record *record)
{
return record->data;
}
const struct ast_dns_record *ast_dns_record_get_next(const struct ast_dns_record *record)
{
return AST_LIST_NEXT(record, list);
}
/*! \brief Destructor for an active DNS query */
static void dns_query_active_destroy(void *data)
{
struct ast_dns_query_active *active = data;
ao2_cleanup(active->query);
}
/*! \brief \brief Destructor for a DNS query */
static void dns_query_destroy(void *data)
{
struct ast_dns_query *query = data;
ao2_cleanup(query->user_data);
ao2_cleanup(query->resolver_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_active *active;
if (ast_strlen_zero(name)) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution, no name provided\n");
return NULL;
} else if (rr_type > ns_t_max) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution of '%s', resource record type '%d' exceeds maximum\n",
name, rr_type);
return NULL;
} else if (rr_type < 0) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution of '%s', invalid resource record type '%d'\n",
name, rr_type);
return NULL;
} else if (rr_class > ns_c_max) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution of '%s', resource record class '%d' exceeds maximum\n",
name, rr_class);
return NULL;
} else if (rr_class < 0) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution of '%s', invalid resource class '%d'\n",
name, rr_class);
return NULL;
} else if (!callback) {
ast_log(LOG_WARNING, "Could not perform asynchronous resolution of '%s', no callback provided\n",
name);
return NULL;
}
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);
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);
ao2_ref(active, -1);
return NULL;
}
return active;
}
int ast_dns_resolve_cancel(struct ast_dns_query_active *active)
{
return active->query->resolver->cancel(active->query);
}
/*! \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 The result from the query */
struct ast_dns_result *result;
};
/*! \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);
/* This purposely does not unref result as it has been passed to the caller */
}
/*! \brief Callback used to implement synchronous resolution */
static void dns_synchronous_resolve_callback(const struct ast_dns_query *query)
{
struct dns_synchronous_resolve *synchronous = ast_dns_query_get_data(query);
synchronous->result = query->result;
((struct ast_dns_query *)query)->result = NULL;
ast_mutex_lock(&synchronous->lock);
synchronous->completed = 1;
ast_cond_signal(&synchronous->cond);
ast_mutex_unlock(&synchronous->lock);
}
int ast_dns_resolve(const char *name, int rr_type, int rr_class, struct ast_dns_result **result)
{
struct dns_synchronous_resolve *synchronous;
struct ast_dns_query_active *active;
if (ast_strlen_zero(name)) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution, no name provided\n");
return -1;
} else if (rr_type > ns_t_max) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution of '%s', resource record type '%d' exceeds maximum\n",
name, rr_type);
return -1;
} else if (rr_type < 0) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution of '%s', invalid resource record type '%d'\n",
name, rr_type);
return -1;
} else if (rr_class > ns_c_max) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution of '%s', resource record class '%d' exceeds maximum\n",
name, rr_class);
return -1;
} else if (rr_class < 0) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution of '%s', invalid resource class '%d'\n",
name, rr_class);
return -1;
} else if (!result) {
ast_log(LOG_WARNING, "Could not perform synchronous resolution of '%s', no result pointer provided for storing results\n",
name);
return -1;
}
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);
active = ast_dns_resolve_async(name, rr_type, rr_class, dns_synchronous_resolve_callback, synchronous);
if (active) {
/* 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(active, -1);
}
*result = synchronous->result;
ao2_ref(synchronous, -1);
return *result ? 0 : -1;
}
int ast_dns_resolver_set_data(struct ast_dns_query *query, void *data)
{
if (query->resolver_data) {
return -1;
}
query->resolver_data = ao2_bump(data);
return 0;
}
void *ast_dns_resolver_get_data(const struct ast_dns_query *query)
{
return query->resolver_data;
}
int ast_dns_resolver_set_result(struct ast_dns_query *query, unsigned int secure, unsigned int bogus,
unsigned int rcode, const char *canonical, const char *answer, size_t answer_size)
{
char *buf_ptr;
if (secure && bogus) {
ast_debug(2, "Query '%p': Could not set result information, it can not be both secure and bogus\n",
query);
return -1;
}
if (ast_strlen_zero(canonical)) {
ast_debug(2, "Query '%p': Could not set result information since no canonical name was provided\n",
query);
return -1;
}
if (!answer || answer_size == 0) {
ast_debug(2, "Query '%p': Could not set result information since no DNS answer was provided\n",
query);
return -1;
}
ast_dns_result_free(query->result);
query->result = ast_calloc(1, sizeof(*query->result) + strlen(canonical) + 1 + answer_size);
if (!query->result) {
return -1;
}
query->result->secure = secure;
query->result->bogus = bogus;
query->result->rcode = rcode;
buf_ptr = query->result->buf;
strcpy(buf_ptr, canonical); /* SAFE */
query->result->canonical = buf_ptr;
buf_ptr += strlen(canonical) + 1;
memcpy(buf_ptr, answer, answer_size); /* SAFE */
query->result->answer = buf_ptr;
return 0;
}
int ast_dns_resolver_add_record(struct ast_dns_query *query, int rr_type, int rr_class, int ttl, const char *data, const size_t size)
{
struct ast_dns_record *record;
if (rr_type < 0) {
ast_debug(2, "Query '%p': Could not add record, invalid resource record type '%d'\n",
query, rr_type);
return -1;
} else if (rr_type > ns_t_max) {
ast_debug(2, "Query '%p': Could not add record, resource record type '%d' exceeds maximum\n",
query, rr_type);
return -1;
} else if (rr_class < 0) {
ast_debug(2, "Query '%p': Could not add record, invalid resource record class '%d'\n",
query, rr_class);
return -1;
} else if (rr_class > ns_c_max) {
ast_debug(2, "Query '%p': Could not add record, resource record class '%d' exceeds maximum\n",
query, rr_class);
return -1;
} else if (ttl < 0) {
ast_debug(2, "Query '%p': Could not add record, invalid TTL '%d'\n",
query, ttl);
return -1;
} else if (!data || !size) {
ast_debug(2, "Query '%p': Could not add record, no data specified\n",
query);
return -1;
} else if (!query->result) {
ast_debug(2, "Query '%p': No result was set on the query, thus records can not be added\n",
query);
return -1;
}
record = ast_calloc(1, sizeof(*record) + size);
if (!record) {
return -1;
}
record->rr_type = rr_type;
record->rr_class = rr_class;
record->ttl = ttl;
memcpy(record->data, data, size);
record->data_len = size;
AST_LIST_INSERT_TAIL(&query->result->records, record, list);
return 0;
}
void ast_dns_resolver_completed(struct ast_dns_query *query)
{
query->callback(query);
}
static void dns_shutdown(void)
{
if (sched) {
ast_sched_context_destroy(sched);
sched = NULL;
}
}
int ast_dns_resolver_register(struct ast_dns_resolver *resolver)
{
struct ast_dns_resolver *iter;
int inserted = 0;
if (!resolver) {
return -1;
} else if (ast_strlen_zero(resolver->name)) {
ast_log(LOG_ERROR, "Registration of DNS resolver failed as it does not have a name\n");
return -1;
} else if (!resolver->resolve) {
ast_log(LOG_ERROR, "DNS resolver '%s' does not implement the resolve callback which is required\n",
resolver->name);
return -1;
} else if (!resolver->cancel) {
ast_log(LOG_ERROR, "DNS resolver '%s' does not implement the cancel callback which is required\n",
resolver->name);
return -1;
}
AST_RWLIST_WRLOCK(&resolvers);
/* On the first registration of a resolver start a scheduler for recurring queries */
if (AST_LIST_EMPTY(&resolvers) && !sched) {
sched = ast_sched_context_create();
if (!sched) {
ast_log(LOG_ERROR, "DNS resolver '%s' could not be registered: Failed to create scheduler for recurring DNS queries\n",
resolver->name);
AST_RWLIST_UNLOCK(&resolvers);
return -1;
}
if (ast_sched_start_thread(sched)) {
ast_log(LOG_ERROR, "DNS resolver '%s' could not be registered: Failed to start thread for recurring DNS queries\n",
resolver->name);
dns_shutdown();
AST_RWLIST_UNLOCK(&resolvers);
return -1;
}
ast_register_cleanup(dns_shutdown);
}
AST_LIST_TRAVERSE(&resolvers, iter, next) {
if (!strcmp(iter->name, resolver->name)) {
ast_log(LOG_ERROR, "A DNS resolver with the name '%s' is already registered\n", resolver->name);
AST_RWLIST_UNLOCK(&resolvers);
return -1;
}
}
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&resolvers, iter, next) {
if (iter->priority > resolver->priority) {
AST_RWLIST_INSERT_BEFORE_CURRENT(resolver, next);
inserted = 1;
break;
}
}
AST_RWLIST_TRAVERSE_SAFE_END;
if (!inserted) {
AST_RWLIST_INSERT_TAIL(&resolvers, resolver, next);
}
AST_RWLIST_UNLOCK(&resolvers);
ast_verb(2, "Registered DNS resolver '%s' with priority '%d'\n", resolver->name, resolver->priority);
return 0;
}
void ast_dns_resolver_unregister(struct ast_dns_resolver *resolver)
{
struct ast_dns_resolver *iter;
if (!resolver) {
return;
}
AST_RWLIST_WRLOCK(&resolvers);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&resolvers, iter, next) {
if (resolver == iter) {
AST_RWLIST_REMOVE_CURRENT(next);
break;
}
}
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&resolvers);
ast_verb(2, "Unregistered DNS resolver '%s'\n", resolver->name);
}

65
main/dns_naptr.c Normal file
View File

@ -0,0 +1,65 @@
/*
* 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.
*/
/*! \file
*
* \brief DNS NAPTR Record Support
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/dns_core.h"
#include "asterisk/dns_naptr.h"
const char *ast_dns_naptr_get_flags(const struct ast_dns_record *record)
{
return NULL;
}
const char *ast_dns_naptr_get_service(const struct ast_dns_record *record)
{
return NULL;
}
const char *ast_dns_naptr_get_regexp(const struct ast_dns_record *record)
{
return NULL;
}
const char *ast_dns_naptr_get_replacement(const struct ast_dns_record *record)
{
return NULL;
}
unsigned short ast_dns_naptr_get_order(const struct ast_dns_record *record)
{
return 0;
}
unsigned short ast_dns_naptr_get_preference(const struct ast_dns_record *record)
{
return 0;
}

93
main/dns_query_set.c Normal file
View File

@ -0,0 +1,93 @@
/*
* 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.
*/
/*! \file
*
* \brief DNS Query Set API
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/vector.h"
#include "asterisk/astobj2.h"
#include "asterisk/dns_core.h"
#include "asterisk/dns_query_set.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;
};
struct ast_dns_query_set *ast_dns_query_set_create(void)
{
return NULL;
}
int ast_dns_query_set_add(struct ast_dns_query_set *query_set, const char *name, int rr_type, int rr_class)
{
return -1;
}
size_t ast_dns_query_set_num_queries(const struct ast_dns_query_set *query_set)
{
return 0;
}
struct ast_dns_query *ast_dns_query_set_get(const struct ast_dns_query_set *query_set, unsigned int index)
{
return NULL;
}
void *ast_dns_query_set_get_data(const struct ast_dns_query_set *query_set)
{
return query_set->user_data;
}
void ast_dns_query_set_resolve_async(struct ast_dns_query_set *query_set, ast_dns_query_set_callback callback, void *data)
{
query_set->callback = callback;
query_set->user_data = ao2_bump(data);
}
void ast_query_set_resolve(struct ast_dns_query_set *query_set)
{
}
int ast_dns_query_set_resolve_cancel(struct ast_dns_query_set *query_set)
{
return -1;
}
void ast_dns_query_set_free(struct ast_dns_query_set *query_set)
{
}

149
main/dns_recurring.c Normal file
View File

@ -0,0 +1,149 @@
/*
* 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.
*/
/*! \file
*
* \brief DNS Recurring Query Support
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj2.h"
#include "asterisk/linkedlists.h"
#include "asterisk/sched.h"
#include "asterisk/strings.h"
#include "asterisk/dns_core.h"
#include "asterisk/dns_recurring.h"
#include "asterisk/dns_internal.h"
#include <arpa/nameser.h>
/*! \brief Destructor for a DNS query */
static void dns_query_recurring_destroy(void *data)
{
struct ast_dns_query_recurring *recurring = data;
ao2_cleanup(recurring->user_data);
}
static void dns_query_recurring_resolution_callback(const struct ast_dns_query *query);
/*! \brief Scheduled recurring query callback */
static int dns_query_recurring_scheduled_callback(const void *data)
{
struct ast_dns_query_recurring *recurring = (struct ast_dns_query_recurring *)data;
ao2_lock(recurring);
recurring->timer = -1;
if (!recurring->cancelled) {
recurring->active = ast_dns_resolve_async(recurring->name, recurring->rr_type, recurring->rr_class, dns_query_recurring_resolution_callback,
recurring);
}
ao2_unlock(recurring);
ao2_ref(recurring, -1);
return 0;
}
/*! \brief Query resolution callback */
static void dns_query_recurring_resolution_callback(const struct ast_dns_query *query)
{
struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
/* Replace the user data so the actual callback sees what it provided */
((struct ast_dns_query*)query)->user_data = ao2_bump(recurring->user_data);
recurring->callback(query);
ao2_lock(recurring);
/* So.. if something has not externally cancelled this we can reschedule based on the TTL */
if (!recurring->cancelled) {
const struct ast_dns_result *result = ast_dns_query_get_result(query);
int ttl = MIN(ast_dns_result_get_lowest_ttl(result), INT_MAX / 1000);
if (ttl) {
recurring->timer = ast_sched_add(ast_dns_get_sched(), ttl * 1000, dns_query_recurring_scheduled_callback, ao2_bump(recurring));
if (recurring->timer < 0) {
/* It is impossible for this to be the last reference as this callback function holds a reference itself */
ao2_ref(recurring, -1);
}
}
}
ao2_replace(recurring->active, NULL);
ao2_unlock(recurring);
/* Since we stole the reference from the query we need to drop it ourselves */
ao2_ref(recurring, -1);
}
struct ast_dns_query_recurring *ast_dns_resolve_recurring(const char *name, int rr_type, int rr_class, ast_dns_resolve_callback callback, void *data)
{
struct ast_dns_query_recurring *recurring;
if (ast_strlen_zero(name) || !callback || !ast_dns_get_sched()) {
return NULL;
}
recurring = ao2_alloc(sizeof(*recurring) + strlen(name) + 1, dns_query_recurring_destroy);
if (!recurring) {
return NULL;
}
recurring->callback = callback;
recurring->user_data = ao2_bump(data);
recurring->timer = -1;
recurring->rr_type = rr_type;
recurring->rr_class = rr_class;
strcpy(recurring->name, name); /* SAFE */
recurring->active = ast_dns_resolve_async(name, rr_type, rr_class, dns_query_recurring_resolution_callback, recurring);
if (!recurring->active) {
ao2_ref(recurring, -1);
return NULL;
}
return recurring;
}
int ast_dns_resolve_recurring_cancel(struct ast_dns_query_recurring *recurring)
{
int res = 0;
ao2_lock(recurring);
recurring->cancelled = 1;
AST_SCHED_DEL_UNREF(ast_dns_get_sched(), recurring->timer, ao2_ref(recurring, -1));
if (recurring->active) {
res = ast_dns_resolve_cancel(recurring->active);
ao2_replace(recurring->active, NULL);
}
ao2_unlock(recurring);
return res;
}

55
main/dns_srv.c Normal file
View File

@ -0,0 +1,55 @@
/*
* 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.
*/
/*! \file
*
* \brief DNS SRV Record Support
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/dns_core.h"
#include "asterisk/dns_srv.h"
const char *ast_dns_srv_get_host(const struct ast_dns_record *record)
{
return NULL;
}
unsigned short ast_dns_srv_get_priority(const struct ast_dns_record *record)
{
return 0;
}
unsigned short ast_dns_srv_get_weight(const struct ast_dns_record *record)
{
return 0;
}
unsigned short ast_dns_srv_get_port(const struct ast_dns_record *record)
{
return 0;
}

55
main/dns_tlsa.c Normal file
View File

@ -0,0 +1,55 @@
/*
* 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.
*/
/*! \file
*
* \brief DNS TLSA Record Support
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/dns_core.h"
#include "asterisk/dns_tlsa.h"
unsigned int ast_dns_tlsa_get_usage(const struct ast_dns_record *record)
{
return 0;
}
unsigned int ast_dns_tlsa_get_selector(const struct ast_dns_record *record)
{
return 0;
}
unsigned int ast_dns_tlsa_get_matching_type(const struct ast_dns_record *record)
{
return 0;
}
const char *ast_dns_tlsa_get_association_data(const struct ast_dns_record *record)
{
return NULL;
}

View File

@ -299,6 +299,9 @@ CRYPTO_LIB=@CRYPTO_LIB@
TONEZONE_INCLUDE=@TONEZONE_INCLUDE@
TONEZONE_LIB=@TONEZONE_LIB@
UNBOUND_INCLUDE=@UNBOUND_INCLUDE@
UNBOUND_LIB=@UNBOUND_LIB@
UNIXODBC_INCLUDE=@UNIXODBC_INCLUDE@
UNIXODBC_LIB=@UNIXODBC_LIB@

1271
res/res_resolver_unbound.c Normal file

File diff suppressed because it is too large Load Diff

1355
tests/test_dns.c Normal file

File diff suppressed because it is too large Load Diff

648
tests/test_dns_recurring.c Normal file
View File

@ -0,0 +1,648 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Mark Michelson
*
* Mark Michelson <mmichelson@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*** MODULEINFO
<depend>TEST_FRAMEWORK</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include "asterisk/test.h"
#include "asterisk/module.h"
#include "asterisk/dns_core.h"
#include "asterisk/dns_resolver.h"
#include "asterisk/dns_recurring.h"
#include "asterisk/dns_internal.h"
struct recurring_data {
/*! TTL to place in first returned result */
int ttl1;
/*! TTL to place in second returned result */
int ttl2;
/*! Boolean indicator if query has completed */
int query_complete;
/*! Number of times recurring resolution has completed */
int complete_resolutions;
/*! Number of times resolve() method has been called */
int resolves;
/*! Indicates that the query is expected to be canceled */
int cancel_expected;
/*! Indicates that the query is ready to be canceled */
int cancel_ready;
/*! Indicates that the query has been canceled */
int canceled;
ast_mutex_t lock;
ast_cond_t cond;
};
static void recurring_data_destructor(void *obj)
{
struct recurring_data *rdata = obj;
ast_mutex_destroy(&rdata->lock);
ast_cond_destroy(&rdata->cond);
}
static struct recurring_data *recurring_data_alloc(void)
{
struct recurring_data *rdata;
rdata = ao2_alloc(sizeof(*rdata), recurring_data_destructor);
if (!rdata) {
return NULL;
}
ast_mutex_init(&rdata->lock);
ast_cond_init(&rdata->cond, NULL);
return rdata;
}
#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. The query may either be canceled or
* it may be completed with records.
*
* \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;
static const char *ADDR1 = "127.0.0.1";
static const size_t ADDR1_BUFSIZE = sizeof(struct in_addr);
char addr1_buf[ADDR1_BUFSIZE];
static const char *ADDR2 = "192.168.0.1";
static const size_t ADDR2_BUFSIZE = sizeof(struct in_addr);
char addr2_buf[ADDR2_BUFSIZE];
struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
struct recurring_data *rdata = recurring->user_data;
ast_assert(rdata != NULL);
/* Canceling is an interesting dance. This thread needs to signal that it is
* ready to be canceled. Then it needs to wait until the query is actually canceled.
*/
if (rdata->cancel_expected) {
ast_mutex_lock(&rdata->lock);
rdata->cancel_ready = 1;
ast_cond_signal(&rdata->cond);
while (!rdata->canceled) {
ast_cond_wait(&rdata->cond, &rdata->lock);
}
ast_mutex_unlock(&rdata->lock);
ast_dns_resolver_completed(query);
ao2_ref(query, -1);
return NULL;
}
/* When the query isn't canceled, we set the TTL of the results based on what
* we've been told to set it to
*/
ast_dns_resolver_set_result(query, 0, 0, ns_r_noerror, "asterisk.org", DNS_ANSWER, DNS_ANSWER_SIZE);
inet_pton(AF_INET, ADDR1, addr1_buf);
ast_dns_resolver_add_record(query, ns_t_a, ns_c_in, rdata->ttl1, addr1_buf, ADDR1_BUFSIZE);
inet_pton(AF_INET, ADDR2, addr2_buf);
ast_dns_resolver_add_record(query, ns_t_a, ns_c_in, rdata->ttl2, addr2_buf, ADDR2_BUFSIZE);
++rdata->complete_resolutions;
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 recurring_resolve(struct ast_dns_query *query)
{
struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
struct recurring_data *rdata = recurring->user_data;
pthread_t resolver_thread;
ast_assert(rdata != NULL);
++rdata->resolves;
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 recurring_cancel(struct ast_dns_query *query)
{
struct ast_dns_query_recurring *recurring = ast_dns_query_get_data(query);
struct recurring_data *rdata = recurring->user_data;
ast_mutex_lock(&rdata->lock);
rdata->canceled = 1;
ast_cond_signal(&rdata->cond);
ast_mutex_unlock(&rdata->lock);
return 0;
}
static struct ast_dns_resolver recurring_resolver = {
.name = "test_recurring",
.priority = 0,
.resolve = recurring_resolve,
.cancel = recurring_cancel,
};
/*!
* \brief Wait for a successful resolution to complete
*
* This is called whenever a successful DNS resolution occurs. This function
* serves to ensure that parameters are as we expect them to be.
*
* \param test The test being executed
* \param rdata DNS query user data
* \param expected_lapse The amount of time we expect to wait for the query to complete
* \param num_resolves The number of DNS resolutions that have been executed
* \param num_completed The number of DNS resolutions we expect to have completed successfully
* \param canceled Whether the query is expected to have been canceled
*/
static int wait_for_resolution(struct ast_test *test, struct recurring_data *rdata,
int expected_lapse, int num_resolves, int num_completed, int canceled)
{
struct timespec begin;
struct timespec end;
struct timespec timeout;
int secdiff;
clock_gettime(CLOCK_REALTIME, &begin);
timeout.tv_sec = begin.tv_sec + 20;
timeout.tv_nsec = begin.tv_nsec;
ast_mutex_lock(&rdata->lock);
while (!rdata->query_complete) {
if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
break;
}
}
ast_mutex_unlock(&rdata->lock);
if (!rdata->query_complete) {
ast_test_status_update(test, "Query timed out\n");
return -1;
}
rdata->query_complete = 0;
clock_gettime(CLOCK_REALTIME, &end);
secdiff = end.tv_sec - begin.tv_sec;
/* Give ourselves some wiggle room */
if (secdiff < expected_lapse - 2 || secdiff > expected_lapse + 2) {
ast_test_status_update(test, "Query did not complete in expected time\n");
return -1;
}
if (rdata->resolves != num_resolves || rdata->complete_resolutions != num_completed) {
ast_test_status_update(test, "Query has not undergone expected number of resolutions\n");
return -1;
}
if (rdata->canceled != canceled) {
ast_test_status_update(test, "Query was canceled unexpectedly\n");
return -1;
}
ast_test_status_update(test, "Query completed in expected time frame\n");
return 0;
}
static void async_callback(const struct ast_dns_query *query)
{
struct recurring_data *rdata = ast_dns_query_get_data(query);
ast_assert(rdata != NULL);
ast_mutex_lock(&rdata->lock);
rdata->query_complete = 1;
ast_cond_signal(&rdata->cond);
ast_mutex_unlock(&rdata->lock);
}
AST_TEST_DEFINE(recurring_query)
{
RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
enum ast_test_result_state res = AST_TEST_PASS;
int expected_lapse;
switch (cmd) {
case TEST_INIT:
info->name = "recurring_query";
info->category = "/main/dns/recurring/";
info->summary = "Test nominal asynchronous recurring DNS queries\n";
info->description =
"This tests nominal recurring queries in the following ways:\n"
"\t* An asynchronous query is sent to a mock resolver\n"
"\t* The mock resolver returns two records with different TTLs\n"
"\t* We ensure that the query re-occurs according to the lower of the TTLs\n"
"\t* The mock resolver returns two records, this time with different TTLs\n"
"\t from the first time the query was resolved\n"
"\t* We ensure that the query re-occurs according to the new lower TTL\n";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (ast_dns_resolver_register(&recurring_resolver)) {
ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
return AST_TEST_FAIL;
}
rdata = recurring_data_alloc();
if (!rdata) {
ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
res = AST_TEST_FAIL;
goto cleanup;
}
expected_lapse = 0;
rdata->ttl1 = 5;
rdata->ttl2 = 20;
recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
if (!recurring_query) {
ast_test_status_update(test, "Failed to create recurring DNS query\n");
res = AST_TEST_FAIL;
goto cleanup;
}
/* This should be near instantaneous */
if (wait_for_resolution(test, rdata, expected_lapse, 1, 1, 0)) {
res = AST_TEST_FAIL;
goto cleanup;
}
expected_lapse = rdata->ttl1;
rdata->ttl1 = 45;
rdata->ttl2 = 10;
/* This should take approximately 5 seconds */
if (wait_for_resolution(test, rdata, expected_lapse, 2, 2, 0)) {
res = AST_TEST_FAIL;
goto cleanup;
}
expected_lapse = rdata->ttl2;
/* This should take approximately 10 seconds */
if (wait_for_resolution(test, rdata, expected_lapse, 3, 3, 0)) {
res = AST_TEST_FAIL;
goto cleanup;
}
cleanup:
if (recurring_query) {
/* XXX I don't like calling this here since I'm not testing
* canceling recurring queries, but I'm forced into it here
*/
ast_dns_resolve_recurring_cancel(recurring_query);
}
ast_dns_resolver_unregister(&recurring_resolver);
return res;
}
static int fail_resolve(struct ast_dns_query *query)
{
return -1;
}
static int stub_cancel(struct ast_dns_query *query)
{
return 0;
}
static void stub_callback(const struct ast_dns_query *query)
{
return;
}
AST_TEST_DEFINE(recurring_query_off_nominal)
{
struct ast_dns_resolver terrible_resolver = {
.name = "Harold P. Warren's Filmography",
.priority = 0,
.resolve = fail_resolve,
.cancel = stub_cancel,
};
struct ast_dns_query_recurring *recurring;
struct dns_resolve_data {
const char *name;
int rr_type;
int rr_class;
ast_dns_resolve_callback callback;
} resolves [] = {
{ NULL, ns_t_a, ns_c_in, stub_callback },
{ "asterisk.org", -1, ns_c_in, stub_callback },
{ "asterisk.org", ns_t_max + 1, ns_c_in, stub_callback },
{ "asterisk.org", ns_t_a, -1, stub_callback },
{ "asterisk.org", ns_t_a, ns_c_max + 1, stub_callback },
{ "asterisk.org", ns_t_a, ns_c_in, NULL },
};
int i;
enum ast_test_result_state res = AST_TEST_PASS;
switch (cmd) {
case TEST_INIT:
info->name = "recurring_query_off_nominal";
info->category = "/main/dns/recurring/";
info->summary = "Test off-nominal recurring DNS resolution";
info->description =
"This test performs several off-nominal recurring DNS resolutions:\n"
"\t* Attempt resolution with NULL name\n",
"\t* Attempt resolution with invalid RR type\n",
"\t* Attempt resolution with invalid RR class\n",
"\t* Attempt resolution with NULL callback pointer\n",
"\t* Attempt resolution with resolver that returns an error\n";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (ast_dns_resolver_register(&recurring_resolver)) {
ast_test_status_update(test, "Failed to register test resolver\n");
return AST_TEST_FAIL;
}
for (i = 0; i < ARRAY_LEN(resolves); ++i) {
recurring = ast_dns_resolve_recurring(resolves[i].name, resolves[i].rr_type, resolves[i].rr_class,
resolves[i].callback, NULL);
if (recurring) {
ast_test_status_update(test, "Successfully performed recurring resolution with invalid data\n");
ast_dns_resolve_recurring_cancel(recurring);
ao2_ref(recurring, -1);
res = AST_TEST_FAIL;
}
}
ast_dns_resolver_unregister(&recurring_resolver);
if (ast_dns_resolver_register(&terrible_resolver)) {
ast_test_status_update(test, "Failed to register the DNS resolver\n");
return AST_TEST_FAIL;
}
recurring = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, stub_callback, NULL);
ast_dns_resolver_unregister(&terrible_resolver);
if (recurring) {
ast_test_status_update(test, "Successfully performed recurring resolution with invalid data\n");
ast_dns_resolve_recurring_cancel(recurring);
ao2_ref(recurring, -1);
return AST_TEST_FAIL;
}
return res;
}
AST_TEST_DEFINE(recurring_query_cancel_between)
{
RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
enum ast_test_result_state res = AST_TEST_PASS;
struct timespec timeout;
switch (cmd) {
case TEST_INIT:
info->name = "recurring_query_cancel_between";
info->category = "/main/dns/recurring/";
info->summary = "Test canceling a recurring DNS query during the downtime between queries\n";
info->description = "This test does the following:\n"
"\t* Issue a recurring DNS query.\n"
"\t* Once results have been returned, cancel the recurring query.\n"
"\t* Wait a while to ensure that no more queries are occurring.\n";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (ast_dns_resolver_register(&recurring_resolver)) {
ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
return AST_TEST_FAIL;
}
rdata = recurring_data_alloc();
if (!rdata) {
ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
res = AST_TEST_FAIL;
goto cleanup;
}
rdata->ttl1 = 5;
rdata->ttl2 = 20;
recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
if (!recurring_query) {
ast_test_status_update(test, "Unable to make recurring query\n");
res = AST_TEST_FAIL;
goto cleanup;
}
if (wait_for_resolution(test, rdata, 0, 1, 1, 0)) {
res = AST_TEST_FAIL;
goto cleanup;
}
if (ast_dns_resolve_recurring_cancel(recurring_query)) {
ast_test_status_update(test, "Failed to cancel recurring query\n");
res = AST_TEST_FAIL;
goto cleanup;
}
/* Query has been canceled, so let's wait to make sure that we don't get
* told another query has occurred.
*/
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 10;
ast_mutex_lock(&rdata->lock);
while (!rdata->query_complete) {
if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
break;
}
}
ast_mutex_unlock(&rdata->lock);
if (rdata->query_complete) {
ast_test_status_update(test, "Recurring query occurred after cancellation\n");
res = AST_TEST_FAIL;
goto cleanup;
}
cleanup:
ast_dns_resolver_unregister(&recurring_resolver);
return res;
}
AST_TEST_DEFINE(recurring_query_cancel_during)
{
RAII_VAR(struct ast_dns_query_recurring *, recurring_query, NULL, ao2_cleanup);
RAII_VAR(struct recurring_data *, rdata, NULL, ao2_cleanup);
enum ast_test_result_state res = AST_TEST_PASS;
struct timespec timeout;
switch (cmd) {
case TEST_INIT:
info->name = "recurring_query_cancel_during";
info->category = "/main/dns/recurring/";
info->summary = "Cancel a recurring DNS query while a query is actually happening\n";
info->description = "This test does the following:\n"
"\t* Initiate a recurring DNS query.\n"
"\t* Allow the initial query to complete, and a second query to start\n"
"\t* Cancel the recurring query while the second query is executing\n"
"\t* Ensure that the resolver's cancel() method was called\n"
"\t* Wait a while to make sure that recurring queries are no longer occurring\n";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
if (ast_dns_resolver_register(&recurring_resolver)) {
ast_test_status_update(test, "Failed to register recurring DNS resolver\n");
return AST_TEST_FAIL;
}
rdata = recurring_data_alloc();
if (!rdata) {
ast_test_status_update(test, "Failed to allocate data necessary for recurring test\n");
res = AST_TEST_FAIL;
goto cleanup;
}
rdata->ttl1 = 5;
rdata->ttl2 = 20;
recurring_query = ast_dns_resolve_recurring("asterisk.org", ns_t_a, ns_c_in, async_callback, rdata);
if (!recurring_query) {
ast_test_status_update(test, "Failed to make recurring DNS query\n");
res = AST_TEST_FAIL;
goto cleanup;
}
if (wait_for_resolution(test, rdata, 0, 1, 1, 0)) {
res = AST_TEST_FAIL;
goto cleanup;
}
/* Initial query has completed. Now let's make the next query expect a cancelation */
rdata->cancel_expected = 1;
/* Wait to be told that the query should be canceled */
ast_mutex_lock(&rdata->lock);
while (!rdata->cancel_ready) {
ast_cond_wait(&rdata->cond, &rdata->lock);
}
rdata->cancel_expected = 0;
ast_mutex_unlock(&rdata->lock);
if (ast_dns_resolve_recurring_cancel(recurring_query)) {
ast_test_status_update(test, "Failed to cancel recurring DNS query\n");
res = AST_TEST_FAIL;
goto cleanup;
}
/* Query has been canceled. We'll be told that the query in flight has completed. */
if (wait_for_resolution(test, rdata, 0, 2, 1, 1)) {
res = AST_TEST_FAIL;
goto cleanup;
}
/* Now ensure that no more queries get completed after cancellation. */
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 10;
ast_mutex_lock(&rdata->lock);
while (!rdata->query_complete) {
if (ast_cond_timedwait(&rdata->cond, &rdata->lock, &timeout) == ETIMEDOUT) {
break;
}
}
ast_mutex_unlock(&rdata->lock);
if (rdata->query_complete) {
ast_test_status_update(test, "Recurring query occurred after cancellation\n");
res = AST_TEST_FAIL;
goto cleanup;
}
cleanup:
ast_dns_resolver_unregister(&recurring_resolver);
return res;
}
static int unload_module(void)
{
AST_TEST_UNREGISTER(recurring_query);
AST_TEST_UNREGISTER(recurring_query_off_nominal);
AST_TEST_UNREGISTER(recurring_query_cancel_between);
AST_TEST_UNREGISTER(recurring_query_cancel_during);
return 0;
}
static int load_module(void)
{
AST_TEST_REGISTER(recurring_query);
AST_TEST_REGISTER(recurring_query_off_nominal);
AST_TEST_REGISTER(recurring_query_cancel_between);
AST_TEST_REGISTER(recurring_query_cancel_during);
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Recurring DNS query tests");