summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CHANGES29
-rw-r--r--Makefile12
-rw-r--r--apps/app_dial.c85
-rw-r--r--apps/app_followme.c13
-rw-r--r--apps/app_queue.c96
-rw-r--r--channels/sig_pri.c113
-rw-r--r--configs/samples/pjsip.conf.sample6
-rwxr-xr-xconfigure122
-rw-r--r--configure.ac2
-rw-r--r--contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py25
-rw-r--r--contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py20
-rw-r--r--funcs/func_channel.c25
-rw-r--r--funcs/func_pjsip_contact.c10
-rw-r--r--include/asterisk/autoconfig.h.in4
-rw-r--r--include/asterisk/dns_core.h9
-rw-r--r--include/asterisk/dns_internal.h52
-rw-r--r--include/asterisk/dns_query_set.h20
-rw-r--r--include/asterisk/endpoints.h10
-rw-r--r--include/asterisk/global_datastores.h6
-rw-r--r--include/asterisk/max_forwards.h78
-rw-r--r--include/asterisk/res_pjsip.h58
-rw-r--r--include/asterisk/threadstorage.h5
-rw-r--r--main/bridge.c7
-rw-r--r--main/ccss.c4
-rw-r--r--main/channel.c3
-rw-r--r--main/dial.c15
-rw-r--r--main/dns_core.c52
-rw-r--r--main/dns_query_set.c201
-rw-r--r--main/endpoints.c8
-rw-r--r--main/features.c28
-rw-r--r--main/global_datastores.c56
-rw-r--r--main/max_forwards.c165
-rw-r--r--main/pbx.c10
-rw-r--r--res/res_fax.c16
-rw-r--r--res/res_pjsip.c184
-rw-r--r--res/res_pjsip/config_global.c21
-rw-r--r--res/res_pjsip/include/res_pjsip_private.h6
-rw-r--r--res/res_pjsip/location.c43
-rw-r--r--res/res_pjsip/pjsip_configuration.c123
-rw-r--r--res/res_pjsip/pjsip_options.c137
-rw-r--r--res/res_pjsip/pjsip_resolver.c669
-rw-r--r--res/res_pjsip_diversion.c5
-rw-r--r--res/res_pjsip_pubsub.c9
-rw-r--r--tests/test_dns_query_set.c365
45 files changed, 2480 insertions, 449 deletions
diff --git a/.gitignore b/.gitignore
index 063651091..c2406440c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,5 @@ defaults.h
makeopts
makeopts.embed_rules
menuselect-tree
+*.sha1
+*.pyc
diff --git a/CHANGES b/CHANGES
index 4237c82e2..94ed55905 100644
--- a/CHANGES
+++ b/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
------------------
@@ -139,6 +156,18 @@ res_pjsip
* A new CLI command has been added: "pjsip show settings", which shows
both the global and system configuration settings.
+ * A new aor option has been added: "qualify_timeout", which sets the timeout
+ in seconds for a qualify. The default is 3 seconds. This overrides the
+ hard coded 32 seconds in pjproject.
+
+ * Endpoint status will now change to "Unreachable" when all contacts are
+ unavailable. When any contact becomes available, the endpoint will status
+ will change back to "Reachable".
+
+ * A new global option has been added: "max_initial_qualify_time", which
+ sets the maximum amount of time from startup that qualifies should be
+ attempted on all contacts.
+
res_ari_channels
------------------
* Two new events, 'ChannelHold' and 'ChannelUnhold', have been added to the
diff --git a/Makefile b/Makefile
index c2e1fba78..15e21b6b8 100644
--- a/Makefile
+++ b/Makefile
@@ -160,8 +160,14 @@ DAHDI_UDEV_HOOK_DIR = /usr/share/dahdi/span_config.d
# The file /etc/asterisk.makeopts will also be included but can be overridden
# by the file in your home directory.
-GLOBAL_MAKEOPTS=$(wildcard /etc/asterisk.makeopts)
-USER_MAKEOPTS=$(wildcard ~/.asterisk.makeopts)
+ifeq ($(wildcard menuselect.makeopts),)
+ USER_MAKEOPTS=$(wildcard ~/.asterisk.makeopts)
+ GLOBAL_MAKEOPTS=$(wildcard /etc/asterisk.makeopts)
+else
+ USER_MAKEOPTS=
+ GLOBAL_MAKEOPTS=
+endif
+
MOD_SUBDIR_CFLAGS="-I$(ASTTOPDIR)/include"
OTHER_SUBDIR_CFLAGS="-I$(ASTTOPDIR)/include"
@@ -333,7 +339,7 @@ makeopts: configure
@exit 1
menuselect.makeopts: menuselect/menuselect menuselect-tree makeopts build_tools/menuselect-deps $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS)
-ifeq ($(filter %menuselect,$(MAKECMDGOALS)),)
+ifeq ($(filter %.menuselect,$(MAKECMDGOALS)),)
menuselect/menuselect --check-deps $@
menuselect/menuselect --check-deps $@ $(GLOBAL_MAKEOPTS) $(USER_MAKEOPTS)
endif
diff --git a/apps/app_dial.c b/apps/app_dial.c
index 0390cfe7f..895d4b883 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -58,7 +58,6 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/manager.h"
#include "asterisk/privacy.h"
#include "asterisk/stringfields.h"
-#include "asterisk/global_datastores.h"
#include "asterisk/dsp.h"
#include "asterisk/aoc.h"
#include "asterisk/ccss.h"
@@ -68,6 +67,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/stasis_channels.h"
#include "asterisk/bridge_after.h"
#include "asterisk/features_config.h"
+#include "asterisk/max_forwards.h"
/*** DOCUMENTATION
<application name="Dial" language="en_US">
@@ -881,6 +881,7 @@ static void do_forward(struct chanlist *o, struct cause_args *num,
ast_channel_lock_both(in, o->chan);
ast_channel_inherit_variables(in, o->chan);
ast_channel_datastore_inherit(in, o->chan);
+ ast_max_forwards_decrement(o->chan);
ast_channel_unlock(in);
ast_channel_unlock(o->chan);
/* When a call is forwarded, we don't want to track new interfaces
@@ -2074,7 +2075,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
);
struct ast_flags64 opts = { 0, };
char *opt_args[OPT_ARG_ARRAY_SIZE];
- struct ast_datastore *datastore = NULL;
int fulldial = 0, num_dialed = 0;
int ignore_cc = 0;
char device_name[AST_CHANNEL_NAME];
@@ -2101,6 +2101,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
* \note This will not have any malloced strings so do not free it.
*/
struct ast_party_caller caller;
+ int max_forwards;
/* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */
ast_channel_lock(chan);
@@ -2111,8 +2112,16 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
pbx_builtin_setvar_helper(chan, "ANSWEREDTIME", "");
pbx_builtin_setvar_helper(chan, "DIALEDTIME", "");
ast_channel_stage_snapshot_done(chan);
+ max_forwards = ast_max_forwards_get(chan);
ast_channel_unlock(chan);
+ if (max_forwards <= 0) {
+ ast_log(LOG_WARNING, "Cannot place outbound call from channel '%s'. Max forwards exceeded\n",
+ ast_channel_name(chan));
+ pbx_builtin_setvar_helper(chan, "DIALSTATUS", "BUSY");
+ return -1;
+ }
+
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "Dial requires an argument (technology/resource)\n");
pbx_builtin_setvar_helper(chan, "DIALSTATUS", pa.status);
@@ -2314,9 +2323,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
char *tech = strsep(&number, "/");
size_t tech_len;
size_t number_len;
- /* find if we already dialed this interface */
- struct ast_dialed_interface *di;
- AST_LIST_HEAD(,ast_dialed_interface) *dialed_interfaces;
num_dialed++;
if (ast_strlen_zero(number)) {
@@ -2360,7 +2366,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
/* Request the peer */
ast_channel_lock(chan);
- datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL);
/*
* Seed the chanlist's connected line information with previously
* acquired connected line info from the incoming channel. The
@@ -2370,61 +2375,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
ast_party_connected_line_copy(&tmp->connected, ast_channel_connected(chan));
ast_channel_unlock(chan);
- if (datastore)
- dialed_interfaces = datastore->data;
- else {
- if (!(datastore = ast_datastore_alloc(&dialed_interface_info, NULL))) {
- ast_log(LOG_WARNING, "Unable to create channel datastore for dialed interfaces. Aborting!\n");
- chanlist_free(tmp);
- goto out;
- }
- datastore->inheritance = DATASTORE_INHERIT_FOREVER;
-
- if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
- ast_datastore_free(datastore);
- chanlist_free(tmp);
- goto out;
- }
-
- datastore->data = dialed_interfaces;
- AST_LIST_HEAD_INIT(dialed_interfaces);
-
- ast_channel_lock(chan);
- ast_channel_datastore_add(chan, datastore);
- ast_channel_unlock(chan);
- }
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
- if (!strcasecmp(di->interface, tmp->interface)) {
- ast_log(LOG_WARNING, "Skipping dialing interface '%s' again since it has already been dialed\n",
- di->interface);
- break;
- }
- }
- AST_LIST_UNLOCK(dialed_interfaces);
- if (di) {
- fulldial++;
- chanlist_free(tmp);
- continue;
- }
-
- /* It is always ok to dial a Local interface. We only keep track of
- * which "real" interfaces have been dialed. The Local channel will
- * inherit this list so that if it ends up dialing a real interface,
- * it won't call one that has already been called. */
- if (strcasecmp(tmp->tech, "Local")) {
- if (!(di = ast_calloc(1, sizeof(*di) + strlen(tmp->interface)))) {
- chanlist_free(tmp);
- goto out;
- }
- strcpy(di->interface, tmp->interface);
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
- AST_LIST_UNLOCK(dialed_interfaces);
- }
-
tc = ast_request(tmp->tech, ast_channel_nativeformats(chan), NULL, chan, tmp->number, &cause);
if (!tc) {
/* If we can't, just go on to the next call */
@@ -2465,6 +2415,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
/* Inherit specially named variables from parent channel */
ast_channel_inherit_variables(chan, tc);
ast_channel_datastore_inherit(chan, tc);
+ ast_max_forwards_decrement(tc);
ast_channel_appl_set(tc, "AppDial");
ast_channel_data_set(tc, "(Outgoing Line)");
@@ -2680,18 +2631,6 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
peer = wait_for_answer(chan, &out_chans, &to, peerflags, opt_args, &pa, &num, &result,
dtmf_progress, ignore_cc, &forced_clid, &stored_clid);
- /* The ast_channel_datastore_remove() function could fail here if the
- * datastore was moved to another channel during a masquerade. If this is
- * the case, don't free the datastore here because later, when the channel
- * to which the datastore was moved hangs up, it will attempt to free this
- * datastore again, causing a crash
- */
- ast_channel_lock(chan);
- datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL); /* make sure we weren't cleaned up already */
- if (datastore && !ast_channel_datastore_remove(chan, datastore)) {
- ast_datastore_free(datastore);
- }
- ast_channel_unlock(chan);
if (!peer) {
if (result) {
res = result;
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 4a2e569df..5fd5d15ba 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -64,6 +64,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/dsp.h"
#include "asterisk/app.h"
#include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
/*** DOCUMENTATION
<application name="FollowMe" language="en_US">
@@ -1069,6 +1070,7 @@ static struct ast_channel *findmeexec(struct fm_args *tpargs, struct ast_channel
ast_connected_line_copy_from_caller(ast_channel_connected(outbound), ast_channel_caller(caller));
ast_channel_inherit_variables(caller, outbound);
ast_channel_datastore_inherit(caller, outbound);
+ ast_max_forwards_decrement(outbound);
ast_channel_language_set(outbound, ast_channel_language(caller));
ast_channel_req_accountcodes(outbound, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
ast_channel_musicclass_set(outbound, ast_channel_musicclass(caller));
@@ -1304,12 +1306,23 @@ static int app_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(options);
);
char *opt_args[FOLLOWMEFLAG_ARG_ARRAY_SIZE];
+ int max_forwards;
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "%s requires an argument (followmeid)\n", app);
return -1;
}
+ ast_channel_lock(chan);
+ max_forwards = ast_max_forwards_get(chan);
+ ast_channel_unlock(chan);
+
+ if (max_forwards <= 0) {
+ ast_log(LOG_WARNING, "Unable to execute FollowMe on channel %s. Max forwards exceeded\n",
+ ast_channel_name(chan));
+ return -1;
+ }
+
argstr = ast_strdupa((char *) data);
AST_STANDARD_APP_ARGS(args, argstr);
diff --git a/apps/app_queue.c b/apps/app_queue.c
index a82632d8e..0b8204c33 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -98,7 +98,6 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/stringfields.h"
#include "asterisk/astobj2.h"
#include "asterisk/strings.h"
-#include "asterisk/global_datastores.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/aoc.h"
#include "asterisk/callerid.h"
@@ -113,6 +112,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/mixmonitor.h"
#include "asterisk/core_unreal.h"
#include "asterisk/bridge_basic.h"
+#include "asterisk/max_forwards.h"
/*!
* \par Please read before modifying this file.
@@ -4301,6 +4301,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
/* Inherit specially named variables from parent channel */
ast_channel_inherit_variables(qe->chan, tmp->chan);
ast_channel_datastore_inherit(qe->chan, tmp->chan);
+ ast_max_forwards_decrement(tmp->chan);
/* Presense of ADSI CPE on outgoing channel follows ours */
ast_channel_adsicpe_set(tmp->chan, ast_channel_adsicpe(qe->chan));
@@ -4794,6 +4795,7 @@ static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callatte
ast_channel_lock_both(o->chan, in);
ast_channel_inherit_variables(in, o->chan);
ast_channel_datastore_inherit(in, o->chan);
+ ast_max_forwards_decrement(o->chan);
if (o->pending_connected_update) {
/*
@@ -6275,10 +6277,7 @@ static void setup_mixmonitor(struct queue_ent *qe, const char *filename)
*
* Here is the process of this function
* 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
- * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
- * iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
- * member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
- * during each iteration, we call calc_metric to determine which members should be rung when.
+ * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member.
* 3. Call ring_one to place a call to the appropriate member(s)
* 4. Call wait_for_answer to wait for an answer. If no one answers, return.
* 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
@@ -6331,13 +6330,8 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
int block_connected_line = 0;
int callcompletedinsl;
struct ao2_iterator memi;
- struct ast_datastore *datastore;
struct queue_end_bridge *queue_end_bridge = NULL;
- ast_channel_lock(qe->chan);
- datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
- ast_channel_unlock(qe->chan);
-
memset(&bridge_config, 0, sizeof(bridge_config));
tmpid[0] = 0;
time(&now);
@@ -6424,73 +6418,12 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
memi = ao2_iterator_init(qe->parent->members, 0);
while ((cur = ao2_iterator_next(&memi))) {
struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
- struct ast_dialed_interface *di;
- AST_LIST_HEAD(,ast_dialed_interface) *dialed_interfaces;
if (!tmp) {
ao2_ref(cur, -1);
ao2_iterator_destroy(&memi);
ao2_unlock(qe->parent);
goto out;
}
- if (!datastore) {
- if (!(datastore = ast_datastore_alloc(&dialed_interface_info, NULL))) {
- callattempt_free(tmp);
- ao2_ref(cur, -1);
- ao2_iterator_destroy(&memi);
- ao2_unlock(qe->parent);
- goto out;
- }
- datastore->inheritance = DATASTORE_INHERIT_FOREVER;
- if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
- callattempt_free(tmp);
- ao2_ref(cur, -1);
- ao2_iterator_destroy(&memi);
- ao2_unlock(qe->parent);
- goto out;
- }
- datastore->data = dialed_interfaces;
- AST_LIST_HEAD_INIT(dialed_interfaces);
-
- ast_channel_lock(qe->chan);
- ast_channel_datastore_add(qe->chan, datastore);
- ast_channel_unlock(qe->chan);
- } else
- dialed_interfaces = datastore->data;
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
- if (!strcasecmp(cur->interface, di->interface)) {
- ast_debug(1, "Skipping dialing interface '%s' since it has already been dialed\n",
- di->interface);
- break;
- }
- }
- AST_LIST_UNLOCK(dialed_interfaces);
-
- if (di) {
- callattempt_free(tmp);
- ao2_ref(cur, -1);
- continue;
- }
-
- /* It is always ok to dial a Local interface. We only keep track of
- * which "real" interfaces have been dialed. The Local channel will
- * inherit this list so that if it ends up dialing a real interface,
- * it won't call one that has already been called. */
- if (strncasecmp(cur->interface, "Local/", 6)) {
- if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
- callattempt_free(tmp);
- ao2_ref(cur, -1);
- ao2_iterator_destroy(&memi);
- ao2_unlock(qe->parent);
- goto out;
- }
- strcpy(di->interface, cur->interface);
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
- AST_LIST_UNLOCK(dialed_interfaces);
- }
/*
* Seed the callattempt's connected line information with previously
@@ -6549,16 +6482,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies,
ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT),
forwardsallowed, ringing);
- /* The ast_channel_datastore_remove() function could fail here if the
- * datastore was moved to another channel during a masquerade. If this is
- * the case, don't free the datastore here because later, when the channel
- * to which the datastore was moved hangs up, it will attempt to free this
- * datastore again, causing a crash
- */
- ast_channel_lock(qe->chan);
- if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) {
- ast_datastore_free(datastore);
- }
+
ast_channel_unlock(qe->chan);
ao2_lock(qe->parent);
if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_RRORDERED) {
@@ -7750,12 +7674,22 @@ static int queue_exec(struct ast_channel *chan, const char *data)
struct queue_ent qe = { 0 };
struct ast_flags opts = { 0, };
char *opt_args[OPT_ARG_ARRAY_SIZE];
+ int max_forwards;
if (ast_strlen_zero(data)) {
ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,macro[,gosub[,rule[,position]]]]]]]]]\n");
return -1;
}
+ ast_channel_lock(chan);
+ max_forwards = ast_max_forwards_get(chan);
+ ast_channel_unlock(chan);
+
+ if (max_forwards <= 0) {
+ ast_log(LOG_WARNING, "Channel '%s' cannot enter queue. Max forwards exceeded\n", ast_channel_name(chan));
+ return -1;
+ }
+
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
diff --git a/channels/sig_pri.c b/channels/sig_pri.c
index a7cc3d7a7..e4ad589c1 100644
--- a/channels/sig_pri.c
+++ b/channels/sig_pri.c
@@ -1376,10 +1376,9 @@ static void sig_pri_queue_unhold(struct sig_pri_span *pri, int chanpos)
static void pri_queue_control(struct sig_pri_span *pri, int chanpos, int subclass)
{
struct ast_frame f = {AST_FRAME_CONTROL, };
- struct sig_pri_chan *p = pri->pvts[chanpos];
if (sig_pri_callbacks.queue_control) {
- sig_pri_callbacks.queue_control(p->chan_pvt, subclass);
+ sig_pri_callbacks.queue_control(pri->pvts[chanpos]->chan_pvt, subclass);
}
f.subclass.integer = subclass;
@@ -1388,6 +1387,31 @@ static void pri_queue_control(struct sig_pri_span *pri, int chanpos, int subclas
/*!
* \internal
+ * \brief Queue a request to hangup control frame onto the owner channel.
+ *
+ * \param pri PRI span control structure.
+ * \param chanpos Channel position in the span.
+ *
+ * \note Assumes the pri->lock is already obtained.
+ * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained.
+ *
+ * \return Nothing
+ */
+static void sig_pri_queue_hangup(struct sig_pri_span *pri, int chanpos)
+{
+ if (sig_pri_callbacks.queue_control) {
+ sig_pri_callbacks.queue_control(pri->pvts[chanpos]->chan_pvt, AST_CONTROL_HANGUP);
+ }
+
+ sig_pri_lock_owner(pri, chanpos);
+ if (pri->pvts[chanpos]->owner) {
+ ast_queue_hangup(pri->pvts[chanpos]->owner);
+ ast_channel_unlock(pri->pvts[chanpos]->owner);
+ }
+}
+
+/*!
+ * \internal
* \brief Queue a PVT_CAUSE_CODE frame onto the owner channel.
* \since 11
*
@@ -4035,14 +4059,14 @@ static void sig_pri_send_aoce_termination_request(struct sig_pri_span *pri, int
}
if (!(decoded = ast_aoc_create(AST_AOC_REQUEST, 0, AST_AOC_REQUEST_E))) {
- ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ ast_queue_hangup(pvt->owner);
goto cleanup_termination_request;
}
ast_aoc_set_termination_request(decoded);
if (!(encoded = ast_aoc_encode(decoded, &encoded_size, pvt->owner))) {
- ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ ast_queue_hangup(pvt->owner);
goto cleanup_termination_request;
}
@@ -4051,7 +4075,7 @@ static void sig_pri_send_aoce_termination_request(struct sig_pri_span *pri, int
whentohangup.tv_sec = ms / 1000;
if (ast_queue_control_data(pvt->owner, AST_CONTROL_AOC, encoded, encoded_size)) {
- ast_softhangup_nolock(pvt->owner, AST_SOFTHANGUP_DEV);
+ ast_queue_hangup(pvt->owner);
goto cleanup_termination_request;
}
@@ -4295,43 +4319,6 @@ static void sig_pri_handle_cis_subcmds(struct sig_pri_span *pri, int event_id,
}
}
-#if defined(HAVE_PRI_AOC_EVENTS)
-/*!
- * \internal
- * \brief detect if AOC-S subcmd is present.
- * \since 1.8
- *
- * \param subcmds Subcommands to process if any. (Could be NULL).
- *
- * \note Knowing whether or not an AOC-E subcmd is present on certain
- * PRI hangup events is necessary to determine what method to use to hangup
- * the ast_channel. If an AOC-E subcmd just came in, then a new AOC-E was queued
- * on the ast_channel. If a soft hangup is used, the AOC-E msg will never make it
- * across the bridge, but if a AST_CONTROL_HANGUP frame is queued behind it
- * we can ensure the AOC-E frame makes it to it's destination before the hangup
- * frame is read.
- *
- *
- * \retval 0 AOC-E is not present in subcmd list
- * \retval 1 AOC-E is present in subcmd list
- */
-static int detect_aoc_e_subcmd(const struct pri_subcommands *subcmds)
-{
- int i;
-
- if (!subcmds) {
- return 0;
- }
- for (i = 0; i < subcmds->counter_subcmd; ++i) {
- const struct pri_subcommand *subcmd = &subcmds->subcmd[i];
- if (subcmd->cmd == PRI_SUBCMD_AOC_E) {
- return 1;
- }
- }
- return 0;
-}
-#endif /* defined(HAVE_PRI_AOC_EVENTS) */
-
/*!
* \internal
* \brief Handle the call associated PRI subcommand events.
@@ -6567,9 +6554,8 @@ static void *pri_dchannel(void *vpri)
pri->pvts[chanpos]->call = NULL;
}
}
- /* Force soft hangup if appropriate */
- if (pri->pvts[chanpos]->owner)
- ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV);
+ /* Force hangup if appropriate */
+ sig_pri_queue_hangup(pri, chanpos);
sig_pri_unlock_private(pri->pvts[chanpos]);
}
} else {
@@ -6581,8 +6567,8 @@ static void *pri_dchannel(void *vpri)
pri_destroycall(pri->pri, pri->pvts[x]->call);
pri->pvts[x]->call = NULL;
}
- if (pri->pvts[x]->owner)
- ast_channel_softhangup_internal_flag_add(pri->pvts[x]->owner, AST_SOFTHANGUP_DEV);
+ /* Force hangup if appropriate */
+ sig_pri_queue_hangup(pri, x);
sig_pri_unlock_private(pri->pvts[x]);
}
}
@@ -7154,17 +7140,7 @@ static void *pri_dchannel(void *vpri)
}
if (do_hangup) {
-#if defined(HAVE_PRI_AOC_EVENTS)
- if (detect_aoc_e_subcmd(e->hangup.subcmds)) {
- /* If a AOC-E msg was sent during the release, we must use a
- * AST_CONTROL_HANGUP frame to guarantee that frame gets read before hangup */
- pri_queue_control(pri, chanpos, AST_CONTROL_HANGUP);
- } else {
- ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV);
- }
-#else
- ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV);
-#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ sig_pri_queue_hangup(pri, chanpos);
}
} else {
/*
@@ -7314,16 +7290,11 @@ static void *pri_dchannel(void *vpri)
&& ast_channel_is_bridged(pri->pvts[chanpos]->owner)) {
sig_pri_send_aoce_termination_request(pri, chanpos,
pri_get_timer(pri->pri, PRI_TIMER_T305) / 2);
- } else if (detect_aoc_e_subcmd(e->hangup.subcmds)) {
- /* If a AOC-E msg was sent during the Disconnect, we must use a AST_CONTROL_HANGUP frame
- * to guarantee that frame gets read before hangup */
- pri_queue_control(pri, chanpos, AST_CONTROL_HANGUP);
- } else {
- ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV);
- }
-#else
- ast_channel_softhangup_internal_flag_add(pri->pvts[chanpos]->owner, AST_SOFTHANGUP_DEV);
+ } else
#endif /* defined(HAVE_PRI_AOC_EVENTS) */
+ {
+ sig_pri_queue_hangup(pri, chanpos);
+ }
}
ast_verb(3, "Span %d: Channel %d/%d got hangup request, cause %d\n",
pri->span, pri->pvts[chanpos]->logicalspan,
@@ -8619,16 +8590,18 @@ int sig_pri_indicate(struct sig_pri_chan *p, struct ast_channel *chan, int condi
if (p->pri->aoc_passthrough_flag & SIG_PRI_AOC_GRANT_E) {
sig_pri_aoc_e_from_ast(p, decoded);
}
- /* if hangup was delayed for this AOC-E msg, waiting_for_aoc
+ /*
+ * If hangup was delayed for this AOC-E msg, waiting_for_aoc
* will be set. A hangup is already occuring via a timeout during
* this delay. Instead of waiting for that timeout to occur, go ahead
- * and initiate the softhangup since the delay is no longer necessary */
+ * and initiate the hangup since the delay is no longer necessary.
+ */
if (p->waiting_for_aoce) {
p->waiting_for_aoce = 0;
ast_debug(1,
"Received final AOC-E msg, continue with hangup on %s\n",
ast_channel_name(chan));
- ast_softhangup_nolock(chan, AST_SOFTHANGUP_DEV);
+ ast_queue_hangup(chan);
}
break;
case AST_AOC_REQUEST:
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index d3bb518f1..0f95d19e0 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -812,6 +812,7 @@
; (default: "no")
;type= ; Must be of type aor (default: "")
;qualify_frequency=0 ; Interval at which to qualify an AoR (default: "0")
+;qualify_timeout=3.0 ; Qualify timeout in fractional seconds (default: "3.0")
;authenticate_qualify=no ; Authenticates a qualify request if needed
; (default: "no")
;outbound_proxy= ; Outbound proxy used when sending OPTIONS request
@@ -868,7 +869,10 @@
; The order by which endpoint identifiers are given priority.
; Identifier names are derived from res_pjsip_endpoint_identifier_*
; modules. (default: ip,username,anonymous)
-
+;max_initial_qualify_time=4 ; The maximum amount of time (in seconds) from
+ startup that qualifies should be attempted on all
+ contacts. If greater than the qualify_frequency
+ for an aor, qualify_frequency will be used instead.
; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
;==========================ACL SECTION OPTIONS=========================
diff --git a/configure b/configure
index 9a2630dc1..c539ee94b 100755
--- a/configure
+++ b/configure
@@ -1,5 +1,5 @@
#! /bin/sh
-# From configure.ac Revision: 432815 .
+# From configure.ac Revision.
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for asterisk trunk.
#
@@ -908,6 +908,10 @@ 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=""
diff --git a/configure.ac b/configure.ac
index afbb5afc1..8a3707543 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
new file mode 100644
index 000000000..9600c0461
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
@@ -0,0 +1,25 @@
+"""add pjsip qualify_timeout
+
+Revision ID: 461d7d691209
+Revises: 31cd4f4891ec
+Create Date: 2015-04-15 13:54:08.047851
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '461d7d691209'
+down_revision = '31cd4f4891ec'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+ op.add_column('ps_aors', sa.Column('qualify_timeout', sa.Integer))
+ op.add_column('ps_contacts', sa.Column('qualify_timeout', sa.Integer))
+ pass
+
+
+def downgrade():
+ op.drop_column('ps_aors', 'qualify_timeout')
+ op.drop_column('ps_contacts', 'qualify_timeout')
+ pass
diff --git a/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py b/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py
new file mode 100644
index 000000000..0ffd7848d
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py
@@ -0,0 +1,20 @@
+"""add pjsip max_initial_qualify_time
+
+Revision ID: a541e0b5e89
+Revises: 461d7d691209
+Create Date: 2015-04-15 14:37:36.424471
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'a541e0b5e89'
+down_revision = '461d7d691209'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('max_initial_qualify_time', sa.Integer))
+
+def downgrade():
+ op.drop_column('ps_globals', 'max_initial_qualify_time')
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 77e18aefa..b051d8924 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -46,6 +46,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/global_datastores.h"
#include "asterisk/bridge_basic.h"
#include "asterisk/bridge_after.h"
+#include "asterisk/max_forwards.h"
/*** DOCUMENTATION
<function name="CHANNELS" language="en_US">
@@ -391,6 +392,16 @@ ASTERISK_REGISTER_FILE()
<enum name="caller_url">
<para>R/0 Returns caller URL</para>
</enum>
+ <enum name="max_forwards">
+ <para>R/W Get or set the maximum number of call forwards for this channel.
+
+ This number describes the number of times a call may be forwarded by this channel
+ before the call fails. "Forwards" in this case refers to redirects by phones as well
+ as calls to local channels.
+
+ Note that this has no relation to the SIP Max-Forwards header.
+ </para>
+ </enum>
</enumlist>
</parameter>
</syntax>
@@ -583,6 +594,10 @@ static int func_channel_read(struct ast_channel *chan, const char *function,
}
}
ast_channel_unlock(chan);
+ } else if (!strcasecmp(data, "max_forwards")) {
+ ast_channel_lock(chan);
+ snprintf(buf, len, "%d", ast_max_forwards_get(chan));
+ ast_channel_unlock(chan);
} else if (!ast_channel_tech(chan) || !ast_channel_tech(chan)->func_channel_read || ast_channel_tech(chan)->func_channel_read(chan, function, data, buf, len)) {
ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", data);
ret = -1;
@@ -743,6 +758,16 @@ static int func_channel_write_real(struct ast_channel *chan, const char *functio
store->media = ast_true(value) ? 1 : 0;
}
ast_channel_unlock(chan);
+ } else if (!strcasecmp(data, "max_forwards")) {
+ int max_forwards;
+ if (sscanf(value, "%d", &max_forwards) != 1) {
+ ast_log(LOG_WARNING, "Unable to set max forwards to '%s'\n", value);
+ ret = -1;
+ } else {
+ ast_channel_lock(chan);
+ ret = ast_max_forwards_set(chan, max_forwards);
+ ast_channel_unlock(chan);
+ }
} else if (!ast_channel_tech(chan)->func_channel_write
|| ast_channel_tech(chan)->func_channel_write(chan, function, data, value)) {
ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n",
diff --git a/funcs/func_pjsip_contact.c b/funcs/func_pjsip_contact.c
index fc65ae922..e9737049d 100644
--- a/funcs/func_pjsip_contact.c
+++ b/funcs/func_pjsip_contact.c
@@ -147,15 +147,9 @@ static int pjsip_contact_function_read(struct ast_channel *chan,
contact_status = ast_sorcery_retrieve_by_id(pjsip_sorcery, CONTACT_STATUS, ast_sorcery_object_get_id(contact_obj));
if (!strcmp(args.field_name, "status")) {
- if (!contact_status) {
- ast_str_set(buf, len, "%s", "Unknown");
- } else if (contact_status->status == UNAVAILABLE) {
- ast_str_set(buf, len, "%s", "Unreachable");
- } else if (contact_status->status == AVAILABLE) {
- ast_str_set(buf, len, "%s", "Reachable");
- }
+ ast_str_set(buf, len, "%s", ast_sip_get_contact_status_label(contact_status->status));
} else if (!strcmp(args.field_name, "rtt")) {
- if (!contact_status) {
+ if (contact_status->status == UNKNOWN) {
ast_str_set(buf, len, "%s", "N/A");
} else {
ast_str_set(buf, len, "%" PRId64, contact_status->rtt);
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 8c7ead499..474fb8c31 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -578,6 +578,10 @@
/* Define if your system has the PJPROJECT libraries. */
#undef HAVE_PJPROJECT
+/* Define to 1 if PJPROJECT has the PJSIP External Resolver Support feature.
+ */
+#undef HAVE_PJSIP_EXTERNAL_RESOLVER
+
/* Define to 1 if PJPROJECT has the pjsip_get_dest_info support feature. */
#undef HAVE_PJSIP_GET_DEST_INFO
diff --git a/include/asterisk/dns_core.h b/include/asterisk/dns_core.h
index 1f67bb803..fe67e340d 100644
--- a/include/asterisk/dns_core.h
+++ b/include/asterisk/dns_core.h
@@ -205,6 +205,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
*
* \param record The current DNS record
diff --git a/include/asterisk/dns_internal.h b/include/asterisk/dns_internal.h
index d518f9066..be8794ba9 100644
--- a/include/asterisk/dns_internal.h
+++ b/include/asterisk/dns_internal.h
@@ -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);
diff --git a/include/asterisk/dns_query_set.h b/include/asterisk/dns_query_set.h
index c89fdfde7..fac732ae0 100644
--- a/include/asterisk/dns_query_set.h
+++ b/include/asterisk/dns_query_set.h
@@ -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
diff --git a/include/asterisk/endpoints.h b/include/asterisk/endpoints.h
index 663dd94d9..c9cb6b9de 100644
--- a/include/asterisk/endpoints.h
+++ b/include/asterisk/endpoints.h
@@ -160,6 +160,16 @@ const char *ast_endpoint_get_resource(const struct ast_endpoint *endpoint);
const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint);
/*!
+ * \brief Gets the state of the given endpoint.
+ *
+ * \param endpoint The endpoint.
+ * \return state.
+ * \return \c AST_ENDPOINT_UNKNOWN if endpoint is \c NULL.
+ * \since 13.4
+ */
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint);
+
+/*!
* \brief Updates the state of the given endpoint.
*
* \param endpoint Endpoint to modify.
diff --git a/include/asterisk/global_datastores.h b/include/asterisk/global_datastores.h
index 16267a894..2946ede84 100644
--- a/include/asterisk/global_datastores.h
+++ b/include/asterisk/global_datastores.h
@@ -26,14 +26,8 @@
#include "asterisk/channel.h"
-extern const struct ast_datastore_info dialed_interface_info;
extern const struct ast_datastore_info secure_call_info;
-struct ast_dialed_interface {
- AST_LIST_ENTRY(ast_dialed_interface) list;
- char interface[1];
-};
-
struct ast_secure_call_store {
unsigned int signaling:1;
unsigned int media:1;
diff --git a/include/asterisk/max_forwards.h b/include/asterisk/max_forwards.h
new file mode 100644
index 000000000..3130b4b64
--- /dev/null
+++ b/include/asterisk/max_forwards.h
@@ -0,0 +1,78 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * 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.
+ */
+
+#ifndef MAX_FORWARDS_H
+
+struct ast_channel;
+
+/*!
+ * \brief Set the starting max forwards for a particular channel.
+ *
+ * \pre chan is locked
+ *
+ * \param starting_count The value to set the max forwards to.
+ * \param chan The channel on which to set the max forwards.
+ * \retval 0 Success
+ * \retval 1 Failure
+ */
+int ast_max_forwards_set(struct ast_channel *chan, int starting_count);
+
+/*!
+ * \brief Get the current max forwards for a particular channel.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it and that value will
+ * be returned.
+ *
+ * \pre chan is locked
+ *
+ * \param chan The channel to get the max forwards for.
+ * \return The current max forwards count on the channel
+ */
+int ast_max_forwards_get(struct ast_channel *chan);
+
+/*!
+ * \brief Decrement the max forwards count for a particular channel.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it and that value will
+ * not be decremented.
+ *
+ * \pre chan is locked
+ *
+ * \chan The channel for which the max forwards value should be decremented
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_max_forwards_decrement(struct ast_channel *chan);
+
+/*!
+ * \brief Reset the max forwards on a channel to its starting value.
+ *
+ * If the channel has not had max forwards set on it, then the channel
+ * will have the default max forwards set on it.
+ *
+ * \pre chan is locked.
+ *
+ * \param chan The channel on which to reset the max forwards count.
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+int ast_max_forwards_reset(struct ast_channel *chan);
+
+#endif /* MAX_FORWARDS_H */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 2358a7281..12fc400d2 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -166,6 +166,8 @@ struct ast_sip_contact {
unsigned int qualify_frequency;
/*! If true authenticate the qualify if needed */
int authenticate_qualify;
+ /*! Qualify timeout. 0 is diabled. */
+ double qualify_timeout;
};
#define CONTACT_STATUS "contact_status"
@@ -175,7 +177,8 @@ struct ast_sip_contact {
*/
enum ast_sip_contact_status_type {
UNAVAILABLE,
- AVAILABLE
+ AVAILABLE,
+ UNKNOWN
};
/*!
@@ -192,6 +195,8 @@ struct ast_sip_contact_status {
struct timeval rtt_start;
/*! The round trip time in microseconds */
int64_t rtt;
+ /*! Last status for a contact (default - unavailable) */
+ enum ast_sip_contact_status_type last_status;
};
/*!
@@ -224,6 +229,8 @@ struct ast_sip_aor {
struct ao2_container *permanent_contacts;
/*! Determines whether SIP Path headers are supported */
unsigned int support_path;
+ /*! Qualify timeout. 0 is diabled. */
+ double qualify_timeout;
};
/*!
@@ -905,6 +912,15 @@ struct ao2_container *ast_sip_location_retrieve_aor_contacts(const struct ast_si
struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const char *aor_list);
/*!
+ * \brief Retrieve all contacts from a list of AORs
+ *
+ * \param aor_list A comma-separated list of AOR names
+ * \retval NULL if no contacts available
+ * \retval non-NULL container (which must be freed) if contacts available
+ */
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list);
+
+/*!
* \brief Retrieve the first bound contact AND the AOR chosen from a list of AORs
*
* \param aor_list A comma-separated list of AOR names
@@ -1260,6 +1276,30 @@ int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg,
void (*callback)(void *token, pjsip_event *e));
/*!
+ * \brief General purpose method for sending an Out-Of-Dialog SIP request
+ *
+ * This is a companion function for \ref ast_sip_create_request. The request
+ * created there can be passed to this function, though any request may be
+ * passed in.
+ *
+ * This will automatically set up handling outbound authentication challenges if
+ * they arrive.
+ *
+ * \param tdata The request to send
+ * \param endpoint Optional. If specified, the out-of-dialog request is sent to the endpoint.
+ * \param timeout. If non-zero, after the timeout the transaction will be terminated
+ * and the callback will be called with the PJSIP_EVENT_TIMER type.
+ * \param token Data to be passed to the callback upon receipt of out-of-dialog response.
+ * \param callback Callback to be called upon receipt of out-of-dialog response.
+ *
+ * \retval 0 Success
+ * \retval -1 Failure (out-of-dialog callback will not be called.)
+ */
+int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata,
+ struct ast_sip_endpoint *endpoint, int timeout, void *token,
+ void (*callback)(void *token, pjsip_event *e));
+
+/*!
* \brief General purpose method for creating a SIP response
*
* Its typical use would be to create responses for out of dialog
@@ -1956,4 +1996,20 @@ char *ast_sip_get_endpoint_identifier_order(void);
*/
unsigned int ast_sip_get_keep_alive_interval(void);
+/*!
+ * \brief Retrieve the system max initial qualify time.
+ *
+ * \retval the maximum initial qualify time.
+ */
+unsigned int ast_sip_get_max_initial_qualify_time(void);
+
+/*!
+ * \brief translate ast_sip_contact_status_type to character string.
+ *
+ * \retval the character string equivalent.
+ */
+
+const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status);
+const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status);
+
#endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/threadstorage.h b/include/asterisk/threadstorage.h
index 4d587a5c7..4e61f42d2 100644
--- a/include/asterisk/threadstorage.h
+++ b/include/asterisk/threadstorage.h
@@ -64,6 +64,9 @@ struct ast_threadstorage {
void __ast_threadstorage_object_add(void *key, size_t len, const char *file, const char *function, unsigned int line);
void __ast_threadstorage_object_remove(void *key);
void __ast_threadstorage_object_replace(void *key_old, void *key_new, size_t len);
+#define THREADSTORAGE_RAW_CLEANUP(v) {}
+#else
+#define THREADSTORAGE_RAW_CLEANUP NULL
#endif /* defined(DEBUG_THREADLOCALS) */
/*!
@@ -85,7 +88,7 @@ void __ast_threadstorage_object_replace(void *key_old, void *key_new, size_t len
#define AST_THREADSTORAGE_EXTERNAL(name) \
extern struct ast_threadstorage name
#define AST_THREADSTORAGE_RAW(name) \
- AST_THREADSTORAGE_CUSTOM_SCOPE(name, NULL, NULL,)
+ AST_THREADSTORAGE_CUSTOM_SCOPE(name, NULL, THREADSTORAGE_RAW_CLEANUP,)
/*!
* \brief Define a thread storage variable, with custom initialization and cleanup
diff --git a/main/bridge.c b/main/bridge.c
index b1c42ff58..64ef12db8 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -4474,6 +4474,12 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
chan_bridged = to_transferee_bridge ? to_transferee : to_transfer_target;
chan_unbridged = to_transferee_bridge ? to_transfer_target : to_transferee;
+ /*
+ * Race condition makes it possible for app to be NULL, so get the app prior to
+ * transferring with a fallback of "unknown".
+ */
+ app = ast_strdupa(ast_channel_appl(chan_unbridged) ?: "unknown");
+
{
int chan_count;
SCOPED_LOCK(lock, the_bridge, ast_bridge_lock, ast_bridge_unlock);
@@ -4515,7 +4521,6 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
goto end;
}
- app = ast_strdupa(ast_channel_appl(chan_unbridged));
if (bridge_channel_internal_queue_attended_transfer(transferee, chan_unbridged)) {
res = AST_BRIDGE_TRANSFER_FAIL;
goto end;
diff --git a/main/ccss.c b/main/ccss.c
index c1b3372dc..51edae745 100644
--- a/main/ccss.c
+++ b/main/ccss.c
@@ -2237,9 +2237,7 @@ static void call_destructor_with_no_monitor(const char * const monitor_type, voi
* Note that it is not necessarily erroneous to add the same
* device to the tree twice. If the same device is called by
* two different extension during the same call, then
- * that is a legitimate situation. Of course, I'm pretty sure
- * the dialed_interfaces global datastore will not allow that
- * to happen anyway.
+ * that is a legitimate situation.
*
* \param device_name The name of the device being added to the tree
* \param dialstring The dialstring used to dial the device being added
diff --git a/main/channel.c b/main/channel.c
index 4e418b6ea..db126db76 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -75,6 +75,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/bridge.h"
#include "asterisk/test.h"
#include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
/*** DOCUMENTATION
***/
@@ -5621,6 +5622,7 @@ static void call_forward_inherit(struct ast_channel *new_chan, struct ast_channe
ast_channel_lock_both(parent, new_chan);
ast_channel_inherit_variables(parent, new_chan);
ast_channel_datastore_inherit(parent, new_chan);
+ ast_max_forwards_decrement(new_chan);
ast_channel_unlock(new_chan);
ast_channel_unlock(parent);
}
@@ -5740,6 +5742,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, struct ast_format_c
ast_channel_lock_both(oh->parent_channel, chan);
ast_channel_inherit_variables(oh->parent_channel, chan);
ast_channel_datastore_inherit(oh->parent_channel, chan);
+ ast_max_forwards_decrement(chan);
ast_channel_unlock(oh->parent_channel);
ast_channel_unlock(chan);
}
diff --git a/main/dial.c b/main/dial.c
index f0cf12737..b935b6d8b 100644
--- a/main/dial.c
+++ b/main/dial.c
@@ -44,6 +44,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/app.h"
#include "asterisk/causes.h"
#include "asterisk/stasis_channels.h"
+#include "asterisk/max_forwards.h"
/*! \brief Main dialing structure. Contains global options, channels being dialed, and more! */
struct ast_dial {
@@ -299,6 +300,19 @@ static int begin_dial_prerun(struct ast_dial_channel *channel, struct ast_channe
.uniqueid2 = channel->assignedid2,
};
+ if (chan) {
+ int max_forwards;
+
+ ast_channel_lock(chan);
+ max_forwards = ast_max_forwards_get(chan);
+ ast_channel_unlock(chan);
+
+ if (max_forwards <= 0) {
+ ast_log(LOG_WARNING, "Cannot dial from channel '%s'. Max forwards exceeded\n",
+ ast_channel_name(chan));
+ }
+ }
+
/* Copy device string over */
ast_copy_string(numsubst, channel->device, sizeof(numsubst));
@@ -337,6 +351,7 @@ static int begin_dial_prerun(struct ast_dial_channel *channel, struct ast_channe
if (chan) {
ast_channel_inherit_variables(chan, channel->owner);
ast_channel_datastore_inherit(chan, channel->owner);
+ ast_max_forwards_decrement(channel->owner);
/* Copy over callerid information */
ast_party_redirecting_copy(ast_channel_redirecting(channel->owner), ast_channel_redirecting(chan));
diff --git a/main/dns_core.c b/main/dns_core.c
index e66c71d62..0b471db91 100644
--- a/main/dns_core.c
+++ b/main/dns_core.c
@@ -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,30 +219,42 @@ struct ast_dns_query_active *ast_dns_resolve_async(const char *name, int rr_type
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);
+ query = ao2_alloc_options(sizeof(*query) + strlen(name) + 1, dns_query_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!query) {
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 */
+ 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);
- active->query->resolver = AST_RWLIST_FIRST(&resolvers);
+ query->resolver = AST_RWLIST_FIRST(&resolvers);
AST_RWLIST_UNLOCK(&resolvers);
- if (!active->query->resolver) {
+ 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 = dns_query_alloc(name, rr_type, rr_class, callback, data);
+ if (!active->query) {
ao2_ref(active, -1);
return NULL;
}
diff --git a/main/dns_query_set.c b/main/dns_query_set.c
index 852fa3e53..c7a4eb18e 100644
--- a/main/dns_query_set.c
+++ b/main/dns_query_set.c
@@ -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);
}
-int ast_dns_query_set_resolve_cancel(struct ast_dns_query_set *query_set)
+/*! \brief Callback used to implement synchronous resolution */
+static void dns_synchronous_resolve_callback(const struct ast_dns_query_set *query_set)
{
- return -1;
+ 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);
}
-void ast_dns_query_set_free(struct ast_dns_query_set *query_set)
+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)
+{
+ int idx;
+ size_t query_count = AST_VECTOR_SIZE(&query_set->queries);
+
+ for (idx = 0; idx < AST_VECTOR_SIZE(&query_set->queries); ++idx) {
+ struct dns_query_set_query *query = AST_VECTOR_GET_ADDR(&query_set->queries, idx);
+
+ if (query->started) {
+ if (!query->query->resolver->cancel(query->query)) {
+ query_set->queries_cancelled++;
+ dns_query_set_callback(query->query);
+ }
+ } else {
+ query_set->queries_cancelled++;
+ }
+ }
+
+ return (query_set->queries_cancelled == query_count) ? 0 : -1;
+} \ No newline at end of file
diff --git a/main/endpoints.c b/main/endpoints.c
index c70170b41..df9d289c7 100644
--- a/main/endpoints.c
+++ b/main/endpoints.c
@@ -415,6 +415,14 @@ const char *ast_endpoint_get_id(const struct ast_endpoint *endpoint)
return endpoint->id;
}
+enum ast_endpoint_state ast_endpoint_get_state(const struct ast_endpoint *endpoint)
+{
+ if (!endpoint) {
+ return AST_ENDPOINT_UNKNOWN;
+ }
+ return endpoint->state;
+}
+
void ast_endpoint_set_state(struct ast_endpoint *endpoint,
enum ast_endpoint_state state)
{
diff --git a/main/features.c b/main/features.c
index 971fb4a02..4acd8aab2 100644
--- a/main/features.c
+++ b/main/features.c
@@ -78,6 +78,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/stasis.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/features_config.h"
+#include "asterisk/max_forwards.h"
/*** DOCUMENTATION
<application name="Bridge" language="en_US">
@@ -420,22 +421,6 @@ static void add_features_datastores(struct ast_channel *caller, struct ast_chann
add_features_datastore(callee, &config->features_callee, &config->features_caller);
}
-static void clear_dialed_interfaces(struct ast_channel *chan)
-{
- struct ast_datastore *di_datastore;
-
- ast_channel_lock(chan);
- if ((di_datastore = ast_channel_datastore_find(chan, &dialed_interface_info, NULL))) {
- if (option_debug) {
- ast_log(LOG_DEBUG, "Removing dialed interfaces datastore on %s since we're bridging\n", ast_channel_name(chan));
- }
- if (!ast_channel_datastore_remove(chan, di_datastore)) {
- ast_datastore_free(di_datastore);
- }
- }
- ast_channel_unlock(chan);
-}
-
static void bridge_config_set_limits_warning_values(struct ast_bridge_config *config, struct ast_bridge_features_limits *limits)
{
if (config->end_sound) {
@@ -572,20 +557,13 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
ast_channel_log("Pre-bridge PEER Channel info", peer);
#endif
- /*
- * If we are bridging a call, stop worrying about forwarding
- * loops. We presume that if a call is being bridged, that the
- * humans in charge know what they're doing. If they don't,
- * well, what can we do about that?
- */
- clear_dialed_interfaces(chan);
- clear_dialed_interfaces(peer);
-
res = 0;
ast_channel_lock(chan);
+ ast_max_forwards_reset(chan);
res |= ast_bridge_features_ds_append(chan, &config->features_caller);
ast_channel_unlock(chan);
ast_channel_lock(peer);
+ ast_max_forwards_reset(peer);
res |= ast_bridge_features_ds_append(peer, &config->features_callee);
ast_channel_unlock(peer);
diff --git a/main/global_datastores.c b/main/global_datastores.c
index dd1e0278e..8ba769d3d 100644
--- a/main/global_datastores.c
+++ b/main/global_datastores.c
@@ -32,62 +32,6 @@
ASTERISK_REGISTER_FILE()
#include "asterisk/global_datastores.h"
-#include "asterisk/linkedlists.h"
-
-static void dialed_interface_destroy(void *data)
-{
- struct ast_dialed_interface *di = NULL;
- AST_LIST_HEAD(, ast_dialed_interface) *dialed_interface_list = data;
-
- if (!dialed_interface_list) {
- return;
- }
-
- AST_LIST_LOCK(dialed_interface_list);
- while ((di = AST_LIST_REMOVE_HEAD(dialed_interface_list, list)))
- ast_free(di);
- AST_LIST_UNLOCK(dialed_interface_list);
-
- AST_LIST_HEAD_DESTROY(dialed_interface_list);
- ast_free(dialed_interface_list);
-}
-
-static void *dialed_interface_duplicate(void *data)
-{
- struct ast_dialed_interface *di = NULL;
- AST_LIST_HEAD(, ast_dialed_interface) *old_list;
- AST_LIST_HEAD(, ast_dialed_interface) *new_list = NULL;
-
- if(!(old_list = data)) {
- return NULL;
- }
-
- if(!(new_list = ast_calloc(1, sizeof(*new_list)))) {
- return NULL;
- }
-
- AST_LIST_HEAD_INIT(new_list);
- AST_LIST_LOCK(old_list);
- AST_LIST_TRAVERSE(old_list, di, list) {
- struct ast_dialed_interface *di2 = ast_calloc(1, sizeof(*di2) + strlen(di->interface));
- if(!di2) {
- AST_LIST_UNLOCK(old_list);
- dialed_interface_destroy(new_list);
- return NULL;
- }
- strcpy(di2->interface, di->interface);
- AST_LIST_INSERT_TAIL(new_list, di2, list);
- }
- AST_LIST_UNLOCK(old_list);
-
- return new_list;
-}
-
-const struct ast_datastore_info dialed_interface_info = {
- .type = "dialed-interface",
- .destroy = dialed_interface_destroy,
- .duplicate = dialed_interface_duplicate,
-};
static void secure_call_store_destroy(void *data)
{
diff --git a/main/max_forwards.c b/main/max_forwards.c
new file mode 100644
index 000000000..8f1d4eed1
--- /dev/null
+++ b/main/max_forwards.c
@@ -0,0 +1,165 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not mfrectly 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, mfstributed 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 "asterisk/max_forwards.h"
+#include "asterisk/channel.h"
+
+#define DEFAULT_MAX_FORWARDS 20
+
+/*!
+ * \brief Channel datastore data for max forwards
+ */
+struct max_forwards {
+ /*! The starting count. Used to allow resetting to the original value */
+ int starting_count;
+ /*! The current count. When this reaches 0, you're outta luck */
+ int current_count;
+};
+
+static struct max_forwards *max_forwards_alloc(int starting_count, int current_count)
+{
+ struct max_forwards *mf;
+
+ mf = ast_malloc(sizeof(*mf));
+ if (!mf) {
+ return NULL;
+ }
+
+ mf->starting_count = starting_count;
+ mf->current_count = current_count;
+
+ return mf;
+}
+
+static void *max_forwards_duplicate(void *data)
+{
+ struct max_forwards *mf = data;
+
+ return max_forwards_alloc(mf->starting_count, mf->current_count);
+}
+
+static void max_forwards_destroy(void *data)
+{
+ ast_free(data);
+}
+
+const struct ast_datastore_info max_forwards_info = {
+ .type = "mfaled-interface",
+ .duplicate = max_forwards_duplicate,
+ .destroy = max_forwards_destroy,
+};
+
+static struct ast_datastore *max_forwards_datastore_alloc(struct ast_channel *chan,
+ int starting_count)
+{
+ struct ast_datastore *mf_datastore;
+ struct max_forwards *mf;
+
+ mf_datastore = ast_datastore_alloc(&max_forwards_info, NULL);
+ if (!mf_datastore) {
+ return NULL;
+ }
+ mf_datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+
+ mf = max_forwards_alloc(starting_count, starting_count);
+ if (!mf) {
+ ast_datastore_free(mf_datastore);
+ return NULL;
+ }
+ mf_datastore->data = mf;
+
+ ast_channel_datastore_add(chan, mf_datastore);
+
+ return mf_datastore;
+}
+
+static struct ast_datastore *max_forwards_datastore_find_or_alloc(struct ast_channel *chan)
+{
+ struct ast_datastore *mf_datastore;
+
+ mf_datastore = ast_channel_datastore_find(chan, &max_forwards_info, NULL);
+ if (!mf_datastore) {
+ mf_datastore = max_forwards_datastore_alloc(chan, DEFAULT_MAX_FORWARDS);
+ }
+
+ return mf_datastore;
+}
+
+int ast_max_forwards_set(struct ast_channel *chan, int starting_count)
+{
+ struct ast_datastore *mf_datastore;
+ struct max_forwards *mf;
+
+ mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+ if (!mf_datastore) {
+ return -1;
+ }
+
+ mf = mf_datastore->data;
+ mf->starting_count = mf->current_count = starting_count;
+
+ return 0;
+}
+
+int ast_max_forwards_get(struct ast_channel *chan)
+{
+ struct ast_datastore *mf_datastore;
+ struct max_forwards *mf;
+
+ mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+ if (!mf_datastore) {
+ return -1;
+ }
+
+ mf = mf_datastore->data;
+ return mf->current_count;
+}
+
+int ast_max_forwards_decrement(struct ast_channel *chan)
+{
+ struct ast_datastore *mf_datastore;
+ struct max_forwards *mf;
+
+ mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+ if (!mf_datastore) {
+ return -1;
+ }
+
+ mf = mf_datastore->data;
+ --mf->current_count;
+
+ return 0;
+}
+
+int ast_max_forwards_reset(struct ast_channel *chan)
+{
+ struct ast_datastore *mf_datastore;
+ struct max_forwards *mf;
+
+ mf_datastore = max_forwards_datastore_find_or_alloc(chan);
+ if (!mf_datastore) {
+ return -1;
+ }
+
+ mf = mf_datastore->data;
+ mf->current_count = mf->starting_count;
+
+ return 0;
+}
diff --git a/main/pbx.c b/main/pbx.c
index 209de66d1..fee4191aa 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -10735,6 +10735,16 @@ void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *context
exten_iter = ast_hashtab_start_traversal(tmp->root_table);
while ((exten_item=ast_hashtab_next(exten_iter))) {
int end_traversal = 1;
+
+ /*
+ * If the extension could not be removed from the root_table due to
+ * a loaded PBX app, it can exist here but have its peer_table be
+ * destroyed due to a previous pass through this function.
+ */
+ if (!exten_item->peer_table) {
+ continue;
+ }
+
prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
while ((prio_item=ast_hashtab_next(prio_iter))) {
char extension[AST_MAX_EXTENSION];
diff --git a/res/res_fax.c b/res/res_fax.c
index c57f446ff..39cb3b369 100644
--- a/res/res_fax.c
+++ b/res/res_fax.c
@@ -3291,13 +3291,13 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
if (gateway->bridged) {
ast_set_read_format(chan, gateway->chan_read_format);
- ast_set_read_format(chan, gateway->chan_write_format);
+ ast_set_write_format(chan, gateway->chan_write_format);
ast_channel_unlock(chan);
peer = ast_channel_bridge_peer(chan);
if (peer) {
ast_set_read_format(peer, gateway->peer_read_format);
- ast_set_read_format(peer, gateway->peer_write_format);
+ ast_set_write_format(peer, gateway->peer_write_format);
ast_channel_make_compatible(chan, peer);
}
ast_channel_lock(chan);
@@ -3340,23 +3340,25 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
gateway->timeout_start = ast_tvnow();
}
+ ast_channel_unlock(chan);
+ ast_channel_lock_both(chan, peer);
+
/* we are bridged, change r/w formats to SLIN for v21 preamble
* detection and T.30 */
ao2_replace(gateway->chan_read_format, ast_channel_readformat(chan));
- ao2_replace(gateway->chan_write_format, ast_channel_readformat(chan));
+ ao2_replace(gateway->chan_write_format, ast_channel_writeformat(chan));
ao2_replace(gateway->peer_read_format, ast_channel_readformat(peer));
- ao2_replace(gateway->peer_write_format, ast_channel_readformat(peer));
+ ao2_replace(gateway->peer_write_format, ast_channel_writeformat(peer));
ast_set_read_format(chan, ast_format_slin);
ast_set_write_format(chan, ast_format_slin);
- ast_channel_unlock(chan);
ast_set_read_format(peer, ast_format_slin);
ast_set_write_format(peer, ast_format_slin);
- ast_channel_make_compatible(chan, peer);
- ast_channel_lock(chan);
+ ast_channel_unlock(peer);
+
gateway->bridged = 1;
}
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index fcd8516b6..2bc5abdd7 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -21,6 +21,8 @@
#include <pjsip.h>
/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */
#include <pjsip_simple.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/timer.h>
#include <pjlib.h>
#include "asterisk/res_pjsip.h"
@@ -1009,6 +1011,14 @@
If <literal>0</literal> never qualify. Time in seconds.
</para></description>
</configOption>
+ <configOption name="qualify_timeout" default="3.0">
+ <synopsis>Timeout for qualify</synopsis>
+ <description><para>
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If <literal>0</literal> no timeout. Time in fractional seconds.
+ </para></description>
+ </configOption>
<configOption name="outbound_proxy">
<synopsis>Outbound proxy used when sending OPTIONS request</synopsis>
<description><para>
@@ -1123,6 +1133,14 @@
If <literal>0</literal> never qualify. Time in seconds.
</para></description>
</configOption>
+ <configOption name="qualify_timeout" default="3.0">
+ <synopsis>Timeout for qualify</synopsis>
+ <description><para>
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If <literal>0</literal> no timeout. Time in fractional seconds.
+ </para></description>
+ </configOption>
<configOption name="authenticate_qualify" default="no">
<synopsis>Authenticates a qualify request if needed</synopsis>
<description><para>
@@ -1211,6 +1229,10 @@
<configOption name="keep_alive_interval" default="0">
<synopsis>The interval (in seconds) to send keepalives to active connection-oriented transports.</synopsis>
</configOption>
+ <configOption name="max_initial_qualify_time" default="0">
+ <synopsis>The maximum amount of time from startup that qualifies should be attempted on all contacts.
+ If greater than the qualify_frequency for an aor, qualify_frequency will be used instead.</synopsis>
+ </configOption>
<configOption name="type">
<synopsis>Must be of type 'global'.</synopsis>
</configOption>
@@ -2815,6 +2837,128 @@ static pj_bool_t does_method_match(const pj_str_t *message_method, const char *s
/*! Maximum number of challenges before assuming that we are in a loop */
#define MAX_RX_CHALLENGES 10
+#define TIMER_INACTIVE 0
+#define TIMEOUT_TIMER2 5
+
+struct tsx_data {
+ void *token;
+ void (*cb)(void*, pjsip_event*);
+ pjsip_transaction *tsx;
+ pj_timer_entry *timeout_timer;
+};
+
+static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event);
+
+pjsip_module send_tsx_module = {
+ .name = { "send_tsx_module", 23 },
+ .id = -1,
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .on_tsx_state = &send_tsx_on_tsx_state,
+};
+
+/*! \brief This is the pjsip_tsx_send_msg callback */
+static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct tsx_data *tsx_data;
+
+ if (event->type != PJSIP_EVENT_TSX_STATE) {
+ return;
+ }
+
+ tsx_data = (struct tsx_data*) tsx->mod_data[send_tsx_module.id];
+ if (tsx_data == NULL) {
+ return;
+ }
+
+ if (tsx->status_code < 200) {
+ return;
+ }
+
+ if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
+ ast_debug(1, "PJSIP tsx timer expired\n");
+ }
+
+ if (tsx_data->timeout_timer && tsx_data->timeout_timer->id != TIMER_INACTIVE) {
+ pj_mutex_lock(tsx->mutex_b);
+ pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, TIMER_INACTIVE);
+ pj_mutex_unlock(tsx->mutex_b);
+ }
+
+ /* Call the callback, if any, and prevent the callback from being called again
+ * by clearing the transaction's module_data.
+ */
+ tsx->mod_data[send_tsx_module.id] = NULL;
+
+ if (tsx_data->cb) {
+ (*tsx_data->cb)(tsx_data->token, event);
+ }
+}
+
+static void tsx_timer_callback(pj_timer_heap_t *theap, pj_timer_entry *entry)
+{
+ struct tsx_data *tsx_data = entry->user_data;
+
+ entry->id = TIMER_INACTIVE;
+ ast_debug(1, "Internal tsx timer expired\n");
+ pjsip_tsx_terminate(tsx_data->tsx, PJSIP_SC_TSX_TIMEOUT);
+}
+
+static pj_status_t endpt_send_transaction(pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata, int timeout, void *token,
+ pjsip_endpt_send_callback cb)
+{
+ pjsip_transaction *tsx;
+ struct tsx_data *tsx_data;
+ pj_status_t status;
+ pjsip_event event;
+
+ ast_assert(endpt && tdata);
+
+ status = pjsip_tsx_create_uac(&send_tsx_module, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ ast_log(LOG_ERROR, "Unable to create pjsip uac\n");
+ return status;
+ }
+
+ tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data);
+ tsx_data->token = token;
+ tsx_data->cb = cb;
+ tsx_data->tsx = tsx;
+ if (timeout > 0) {
+ tsx_data->timeout_timer = PJ_POOL_ALLOC_T(tsx->pool, pj_timer_entry);
+ } else {
+ tsx_data->timeout_timer = NULL;
+ }
+ tsx->mod_data[send_tsx_module.id] = tsx_data;
+
+ PJSIP_EVENT_INIT_TX_MSG(event, tdata);
+ pjsip_tx_data_set_transport(tdata, &tsx->tp_sel);
+
+ if (timeout > 0) {
+ pj_time_val timeout_timer_val = { timeout / 1000, timeout % 1000 };
+
+ pj_timer_entry_init(tsx_data->timeout_timer, TIMEOUT_TIMER2,
+ tsx_data, &tsx_timer_callback);
+ pj_mutex_lock(tsx->mutex_b);
+ pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, TIMER_INACTIVE);
+ pj_timer_heap_schedule(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, &timeout_timer_val);
+ tsx_data->timeout_timer->id = TIMEOUT_TIMER2;
+ pj_mutex_unlock(tsx->mutex_b);
+ }
+
+ status = (*tsx->state_handler)(tsx, &event);
+ pjsip_tx_data_dec_ref(tdata);
+ if (status != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Unable to send message\n");
+ return status;
+ }
+
+ return status;
+}
/*! \brief Structure to hold information about an outbound request */
struct send_request_data {
@@ -2874,7 +3018,7 @@ static void endpt_send_request_wrapper(void *token, pjsip_event *e)
}
static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
- pjsip_tx_data *tdata, pj_int32_t timeout, void *token, pjsip_endpt_send_callback cb)
+ pjsip_tx_data *tdata, int timeout, void *token, pjsip_endpt_send_callback cb)
{
struct send_request_wrapper *req_wrapper;
pj_status_t ret_val;
@@ -2890,7 +3034,7 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
req_wrapper->callback = cb;
ao2_ref(req_wrapper, +1);
- ret_val = pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, timeout,
+ ret_val = endpt_send_transaction(ast_sip_get_pjsip_endpoint(), tdata, timeout,
req_wrapper, endpt_send_request_wrapper);
if (ret_val != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
@@ -2930,6 +3074,10 @@ static void send_request_cb(void *token, pjsip_event *e)
int res;
switch(e->body.tsx_state.type) {
+ case PJSIP_EVENT_USER:
+ /* Map USER (transaction cancelled by timeout) to TIMER */
+ e->body.tsx_state.type = PJSIP_EVENT_TIMER;
+ break;
case PJSIP_EVENT_TRANSPORT_ERROR:
case PJSIP_EVENT_TIMER:
break;
@@ -2980,8 +3128,9 @@ static void send_request_cb(void *token, pjsip_event *e)
ao2_ref(req_data, -1);
}
-static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint,
- void *token, void (*callback)(void *token, pjsip_event *e))
+int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata,
+ struct ast_sip_endpoint *endpoint, int timeout, void *token,
+ void (*callback)(void *token, pjsip_event *e))
{
struct ast_sip_supplement *supplement;
struct send_request_data *req_data;
@@ -3007,7 +3156,7 @@ static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpo
ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL);
ao2_cleanup(contact);
- if (endpt_send_request(endpoint, tdata, -1, req_data, send_request_cb)
+ if (endpt_send_request(endpoint, tdata, timeout, req_data, send_request_cb)
!= PJ_SUCCESS) {
ao2_cleanup(req_data);
return -1;
@@ -3025,7 +3174,7 @@ int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg,
if (dlg) {
return send_in_dialog_request(tdata, dlg);
} else {
- return send_out_of_dialog_request(tdata, endpoint, token, callback);
+ return ast_sip_send_out_of_dialog_request(tdata, endpoint, -1, token, callback);
}
}
@@ -3480,8 +3629,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 +3661,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();
@@ -3543,8 +3693,25 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ if (internal_sip_register_service(&send_tsx_module)) {
+ ast_log(LOG_ERROR, "Failed to initialize send request module. Aborting load\n");
+ internal_sip_unregister_service(&supplement_module);
+ ast_sip_destroy_distributor();
+ ast_res_pjsip_destroy_configuration();
+ ast_sip_destroy_global_headers();
+ stop_monitor_thread();
+ ast_sip_destroy_system();
+ pj_pool_release(memory_pool);
+ memory_pool = NULL;
+ pjsip_endpt_destroy(ast_pjsip_endpoint);
+ ast_pjsip_endpoint = NULL;
+ pj_caching_pool_destroy(&caching_pool);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
if (internal_sip_initialize_outbound_authentication()) {
ast_log(LOG_ERROR, "Failed to initialize outbound authentication. Aborting load\n");
+ internal_sip_unregister_service(&send_tsx_module);
internal_sip_unregister_service(&supplement_module);
ast_sip_destroy_distributor();
ast_res_pjsip_destroy_configuration();
@@ -3588,6 +3755,7 @@ static int unload_pjsip(void *data)
ast_res_pjsip_destroy_configuration();
ast_sip_destroy_system();
ast_sip_destroy_global_headers();
+ internal_sip_unregister_service(&send_tsx_module);
internal_sip_unregister_service(&supplement_module);
if (monitor_thread) {
stop_monitor_thread();
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index 2aa15838f..42ba23487 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -33,6 +33,7 @@
#define DEFAULT_OUTBOUND_ENDPOINT "default_outbound_endpoint"
#define DEFAULT_DEBUG "no"
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
+#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
static char default_useragent[256];
@@ -50,6 +51,8 @@ struct global_config {
unsigned int max_forwards;
/* The interval at which to send keep alive messages to active connection-oriented transports */
unsigned int keep_alive_interval;
+ /* The maximum time for all contacts to be qualified at startup */
+ unsigned int max_initial_qualify_time;
};
static void global_destructor(void *obj)
@@ -161,6 +164,21 @@ unsigned int ast_sip_get_keep_alive_interval(void)
return interval;
}
+unsigned int ast_sip_get_max_initial_qualify_time(void)
+{
+ unsigned int time;
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return DEFAULT_MAX_INITIAL_QUALIFY_TIME;
+ }
+
+ time = cfg->max_initial_qualify_time;
+ ao2_ref(cfg, -1);
+ return time;
+}
+
/*!
* \internal
* \brief Observer to set default global object if none exist.
@@ -271,6 +289,9 @@ int ast_sip_initialize_sorcery_global(void)
ast_sorcery_object_field_register(sorcery, "global", "keep_alive_interval",
__stringify(DEFAULT_KEEPALIVE_INTERVAL),
OPT_UINT_T, 0, FLDSET(struct global_config, keep_alive_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "max_initial_qualify_time",
+ __stringify(DEFAULT_MAX_INITIAL_QUALIFY_TIME),
+ OPT_UINT_T, 0, FLDSET(struct global_config, max_initial_qualify_time));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index bf428d5c5..a8b94112b 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -233,6 +233,12 @@ void ast_sip_initialize_dns(void);
/*!
* \internal
+ * \brief Initialize our own resolver support
+ */
+void ast_sip_initialize_resolver(void);
+
+/*!
+ * \internal
* \brief Initialize global configuration
*
* \retval 0 Success
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 73ffdca0e..21650417f 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -188,6 +188,40 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
return contact;
}
+static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags);
+static int cli_contact_populate_container(void *obj, void *arg, int flags);
+
+static int gather_contacts_for_aor(void *obj, void *arg, int flags)
+{
+ struct ao2_container *aor_contacts;
+ struct ast_sip_aor *aor = obj;
+ struct ao2_container *container = arg;
+
+ aor_contacts = ast_sip_location_retrieve_aor_contacts(aor);
+ if (!aor_contacts) {
+ return 0;
+ }
+ ao2_callback(aor_contacts, OBJ_MULTIPLE | OBJ_NODATA, cli_contact_populate_container,
+ container);
+ ao2_ref(aor_contacts, -1);
+ return CMP_MATCH;
+}
+
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list)
+{
+ struct ao2_container *contacts;
+
+ contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
+ if (!contacts) {
+ return NULL;
+ }
+
+ ast_sip_for_each_aor(aor_list, gather_contacts_for_aor, contacts);
+
+ return contacts;
+}
+
struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name)
{
return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
@@ -208,6 +242,7 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
ast_string_field_set(contact, uri, uri);
contact->expiration_time = expiration_time;
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
if (path_info && aor->support_path) {
ast_string_field_set(contact, path, path_info);
@@ -712,8 +747,8 @@ static int cli_contact_print_body(void *obj, void *arg, int flags)
"Contact",
flexwidth, flexwidth,
wrapper->contact_id,
- (status ? (status->status == AVAILABLE ? "Avail" : "Unavail") : "Unknown"),
- (status ? ((long long) status->rtt) / 1000.0 : NAN));
+ ast_sip_get_contact_short_status_label(status->status),
+ (status->status != UNKNOWN ? ((long long) status->rtt) / 1000.0 : NAN));
return 0;
}
@@ -853,7 +888,8 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 0, 0);
ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
- PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+ PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+ ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout));
ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
@@ -862,6 +898,7 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
+ ast_sorcery_object_field_register(sorcery, "aor", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_aor, qualify_timeout));
ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 0eecb5e0a..54fdb658b 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -19,6 +19,7 @@
#include "asterisk/utils.h"
#include "asterisk/sorcery.h"
#include "asterisk/callerid.h"
+#include "asterisk/test.h"
/*! \brief Number of buckets for persistent endpoint information */
#define PERSISTENT_BUCKETS 53
@@ -59,31 +60,66 @@ static int persistent_endpoint_cmp(void *obj, void *arg, int flags)
static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
{
struct sip_persistent_endpoint *persistent = obj;
+ struct ast_endpoint *endpoint = persistent->endpoint;
char *aor = arg;
- RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
- RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+ struct ao2_container *contacts;
+ struct ast_json *blob;
+ struct ao2_iterator i;
+ struct ast_sip_contact *contact;
+ enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;
if (!ast_strlen_zero(aor) && !strstr(persistent->aors, aor)) {
return 0;
}
- if ((contact = ast_sip_location_retrieve_contact_from_aor_list(persistent->aors))) {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);
+ /* Find all the contacts for this endpoint. If ANY are available,
+ * mark the endpoint as ONLINE.
+ */
+ contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);
+ if (contacts) {
+ i = ao2_iterator_init(contacts, 0);
+ while ((contact = ao2_iterator_next(&i))
+ && state == AST_ENDPOINT_OFFLINE) {
+ struct ast_sip_contact_status *contact_status;
+ const char *contact_id = ast_sorcery_object_get_id(contact);
+
+ contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
+ CONTACT_STATUS, contact_id);
+
+ if (contact_status && contact_status->status != UNAVAILABLE) {
+ state = AST_ENDPOINT_ONLINE;
+ }
+ ao2_cleanup(contact_status);
+ ao2_ref(contact, -1);
+ }
+ ao2_iterator_destroy(&i);
+ ao2_ref(contacts, -1);
+ }
+
+ /* If there was no state change, don't publish anything. */
+ if (ast_endpoint_get_state(endpoint) == state) {
+ return 0;
+ }
+
+ if (state == AST_ENDPOINT_ONLINE) {
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Reachable");
+ ast_verb(1, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));
} else {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");
+ ast_verb(1, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));
}
- ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);
-
- ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));
+ ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);
+ ast_json_unref(blob);
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));
return 0;
}
/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
-static void persistent_endpoint_contact_observer(const void *object)
+static void persistent_endpoint_contact_created_observer(const void *object)
{
char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
@@ -92,12 +128,74 @@ static void persistent_endpoint_contact_observer(const void *object)
ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
}
+/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
+static void persistent_endpoint_contact_deleted_observer(const void *object)
+{
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_verb(1, "Contact %s/%s is now Unavailable\n", aor, contact);
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
/*! \brief Observer for contacts so state can be updated on respective endpoints */
static const struct ast_sorcery_observer state_contact_observer = {
- .created = persistent_endpoint_contact_observer,
- .deleted = persistent_endpoint_contact_observer,
+ .created = persistent_endpoint_contact_created_observer,
+ .deleted = persistent_endpoint_contact_deleted_observer,
};
+/*! \brief Function called when stuff relating to a contact status happens (updated) */
+static void persistent_endpoint_contact_status_observer(const void *object)
+{
+ const struct ast_sip_contact_status *contact_status = object;
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ /* If rtt_start is set (this is the outgoing OPTIONS) or
+ * there's no status change, ignore.
+ */
+ if (contact_status->rtt_start.tv_sec > 0
+ || contact_status->status == contact_status->last_status) {
+ return;
+ }
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",
+ "Contact: %s\r\n"
+ "Status: %s",
+ ast_sorcery_object_get_id(contact_status),
+ ast_sip_get_contact_status_label(contact_status->status));
+
+ ast_verb(1, "Contact %s/%s is now %s\n", aor, contact,
+ ast_sip_get_contact_status_label(contact_status->status));
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer state_contact_status_observer = {
+ .updated = persistent_endpoint_contact_status_observer,
+};
static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
@@ -1796,6 +1894,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
}
ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);
+ ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
if (ast_sip_initialize_sorcery_domain_alias()) {
ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");
@@ -1852,6 +1951,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
void ast_res_pjsip_destroy_configuration(void)
{
+ ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
+ ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);
ast_sip_destroy_sorcery_global();
ast_sip_destroy_sorcery_location();
ast_sip_destroy_sorcery_auth();
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 9794827b5..40b6f7b4c 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -28,12 +28,35 @@
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "asterisk/time.h"
+#include "asterisk/test.h"
#include "include/res_pjsip_private.h"
#define DEFAULT_LANGUAGE "en"
#define DEFAULT_ENCODING "text/plain"
#define QUALIFIED_BUCKETS 211
+static const char *status_map [] = {
+ [UNAVAILABLE] = "Unreachable",
+ [AVAILABLE] = "Reachable",
+ [UNKNOWN] = "Unknown",
+};
+
+static const char *short_status_map [] = {
+ [UNAVAILABLE] = "Unavail",
+ [AVAILABLE] = "Avail",
+ [UNKNOWN] = "Unknown",
+};
+
+const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)
+{
+ return status_map[status];
+}
+
+const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status)
+{
+ return short_status_map[status];
+}
+
/*!
* \internal
* \brief Create a ast_sip_contact_status object.
@@ -47,7 +70,7 @@ static void *contact_status_alloc(const char *name)
return NULL;
}
- status->status = UNAVAILABLE;
+ status->status = UNKNOWN;
return status;
}
@@ -85,19 +108,6 @@ static struct ast_sip_contact_status *find_or_create_contact_status(const struct
return status;
}
-static void delete_contact_status(const struct ast_sip_contact *contact)
-{
- struct ast_sip_contact_status *status = ast_sorcery_retrieve_by_id(
- ast_sip_get_sorcery(), CONTACT_STATUS, ast_sorcery_object_get_id(contact));
-
- if (!status) {
- return;
- }
-
- ast_sorcery_delete(ast_sip_get_sorcery(), status);
- ao2_ref(status, -1);
-}
-
/*!
* \internal
* \brief Update an ast_sip_contact_status's elements.
@@ -110,34 +120,46 @@ static void update_contact_status(const struct ast_sip_contact *contact,
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->last_status = status->status;
update->status = value;
/* if the contact is available calculate the rtt as
the diff between the last start time and "now" */
- update->rtt = update->status == AVAILABLE ?
+ update->rtt = update->status == AVAILABLE && status->rtt_start.tv_sec > 0 ?
ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0;
update->rtt_start = ast_tv(0, 0);
+
+
+ ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",
+ "Contact: %s\r\n"
+ "Status: %s\r\n"
+ "RTT: %" PRId64,
+ ast_sorcery_object_get_id(update),
+ ast_sip_get_contact_status_label(update->status),
+ update->rtt);
+
if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
contact->uri);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -152,18 +174,22 @@ static void init_start_time(const struct ast_sip_contact *contact)
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->status = status->status;
+ update->last_status = status->last_status;
+ update->rtt = status->rtt;
update->rtt_start = ast_tvnow();
if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
@@ -171,8 +197,8 @@ static void init_start_time(const struct ast_sip_contact *contact)
contact->uri);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -320,7 +346,7 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
init_start_time(contact);
ao2_ref(contact, +1);
- if (ast_sip_send_request(tdata, NULL, endpoint_local, contact, qualify_contact_cb)
+ if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)
!= PJ_SUCCESS) {
ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
contact->uri);
@@ -484,7 +510,7 @@ static void qualify_and_schedule(struct ast_sip_contact *contact)
schedule_qualify(contact, contact->qualify_frequency * 1000);
} else {
- delete_contact_status(contact);
+ update_contact_status(contact, UNKNOWN);
}
}
@@ -923,6 +949,32 @@ static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)
return CMP_MATCH;
}
+static int rtt_start_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_sip_contact_status *status = obj;
+ long int sec, usec;
+
+ if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {
+ return -1;
+ }
+
+ status->rtt_start = ast_tv(sec, usec);
+
+ return 0;
+}
+
+static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_contact_status *status = obj;
+
+ if (ast_asprintf(buf, "%ld.%06ld", status->rtt_start.tv_sec, status->rtt_start.tv_usec) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
int ast_sip_initialize_sorcery_qualify(void)
{
struct ast_sorcery *sorcery = ast_sip_get_sorcery();
@@ -936,10 +988,14 @@ int ast_sip_initialize_sorcery_qualify(void)
return -1;
}
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, status));
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, rtt));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, last_status));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, status));
+ ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",
+ "0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, rtt));
return 0;
}
@@ -949,16 +1005,25 @@ static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
struct ast_sip_contact *contact = obj;
struct ast_sip_aor *aor = arg;
int initial_interval;
+ int max_time = ast_sip_get_max_initial_qualify_time();
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
/* Delay initial qualification by a random fraction of the specified interval */
- initial_interval = contact->qualify_frequency * 1000;
- initial_interval = (int)(initial_interval * ast_random_double());
+ if (max_time && max_time < contact->qualify_frequency) {
+ initial_interval = max_time;
+ } else {
+ initial_interval = contact->qualify_frequency;
+ }
+
+ initial_interval = (int)((initial_interval * 1000) * ast_random_double());
if (contact->qualify_frequency) {
schedule_qualify(contact, initial_interval);
+ } else {
+ update_contact_status(contact, UNKNOWN);
}
return 0;
@@ -1030,11 +1095,6 @@ static void qualify_and_schedule_all(void)
ao2_ref(endpoints, -1);
}
-static const char *status_map [] = {
- [UNAVAILABLE] = "Unreachable",
- [AVAILABLE] = "Reachable",
-};
-
static int format_contact_status(void *obj, void *arg, int flags)
{
struct ast_sip_contact_wrapper *wrapper = obj;
@@ -1055,12 +1115,11 @@ static int format_contact_status(void *obj, void *arg, int flags)
ast_str_append(&buf, 0, "AOR: %s\r\n", wrapper->aor_id);
ast_str_append(&buf, 0, "URI: %s\r\n", contact->uri);
- if (status) {
- ast_str_append(&buf, 0, "Status: %s\r\n", status_map[status->status]);
- ast_str_append(&buf, 0, "RoundtripUsec: %" PRId64 "\r\n", status->rtt);
- } else {
- ast_str_append(&buf, 0, "Status: Unknown\r\n");
+ ast_str_append(&buf, 0, "Status: %s\r\n", ast_sip_get_contact_status_label(status->status));
+ if (status->status == UNKNOWN) {
ast_str_append(&buf, 0, "RoundtripUsec: N/A\r\n");
+ } else {
+ ast_str_append(&buf, 0, "RoundtripUsec: %" PRId64 "\r\n", status->rtt);
}
ast_str_append(&buf, 0, "EndpointName: %s\r\n",
ast_sorcery_object_get_id(endpoint));
diff --git a/res/res_pjsip/pjsip_resolver.c b/res/res_pjsip/pjsip_resolver.c
new file mode 100644
index 000000000..e4cc51af1
--- /dev/null
+++ b/res/res_pjsip/pjsip_resolver.c
@@ -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
diff --git a/res/res_pjsip_diversion.c b/res/res_pjsip_diversion.c
index a4ac157e4..49f789212 100644
--- a/res/res_pjsip_diversion.c
+++ b/res/res_pjsip_diversion.c
@@ -248,6 +248,7 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
pjsip_name_addr *name_addr;
pjsip_sip_uri *uri;
pjsip_param *param;
+ pjsip_fromto_hdr *old_hdr;
struct ast_party_id *id = &data->from;
pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
@@ -273,6 +274,10 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
pj_list_insert_before(&hdr->other_param, param);
hdr->uri = (pjsip_uri *) name_addr;
+ old_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &diversion_name, NULL);
+ if (old_hdr) {
+ pj_list_erase(old_hdr);
+ }
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
}
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index b209b86a5..ebc43d15b 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -2603,11 +2603,12 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
sip_subscription_accept(sub_tree, rdata, resp);
if (generate_initial_notify(sub_tree->root)) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ } else {
+ send_notify(sub_tree, 1);
+ ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
+ "Resource: %s",
+ sub_tree->root->resource);
}
- send_notify(sub_tree, 1);
- ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
- "Resource: %s",
- sub_tree->root->resource);
}
resource_tree_destroy(&tree);
diff --git a/tests/test_dns_query_set.c b/tests/test_dns_query_set.c
new file mode 100644
index 000000000..08829f59e
--- /dev/null
+++ b/tests/test_dns_query_set.c
@@ -0,0 +1,365 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@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");