diff options
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 @@ -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 @@ -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========================= @@ -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"); |