asterisk/res/res_pjsip_refer.c

1300 lines
45 KiB
C
Raw Normal View History

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2013, 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>pjproject</depend>
<depend>res_pjsip</depend>
<depend>res_pjsip_session</depend>
<depend>res_pjsip_pubsub</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include <pjsip.h>
#include <pjsip_ua.h>
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/bridge.h"
#include "asterisk/framehook.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_channels.h"
#include "asterisk/causes.h"
/*! \brief REFER Progress structure */
struct refer_progress {
/*! \brief Subscription to provide updates on */
pjsip_evsub *sub;
/*! \brief Dialog for subscription */
pjsip_dialog *dlg;
/*! \brief Received packet, used to construct final response in case no subscription exists */
pjsip_rx_data *rdata;
/*! \brief Frame hook for monitoring REFER progress */
int framehook;
/*! \brief Last received subclass in frame hook */
int subclass;
/*! \brief Serializer for notifications */
struct ast_taskprocessor *serializer;
/*! \brief Stasis subscription for bridge events */
struct stasis_subscription *bridge_sub;
/*! \brief Reference to transfer_channel_data related to the refer */
struct transfer_channel_data *transfer_data;
/*! \brief Uniqueid of transferee channel */
char *transferee;
/*! \brief Non-zero if the 100 notify has been sent */
int sent_100;
/*! \brief Whether to notifies all the progress details on blind transfer */
unsigned int refer_blind_progress;
};
/*! \brief REFER Progress notification structure */
struct refer_progress_notification {
/*! \brief Refer progress structure to send notification on */
struct refer_progress *progress;
/*! \brief SIP response code to send */
int response;
/*! \brief Subscription state */
pjsip_evsub_state state;
};
/*! \brief REFER Progress module, used to attach REFER progress structure to subscriptions */
static pjsip_module refer_progress_module = {
.name = { "REFER Progress", 14 },
.id = -1,
};
/*! \brief Destructor for REFER Progress notification structure */
static void refer_progress_notification_destroy(void *obj)
{
struct refer_progress_notification *notification = obj;
ao2_cleanup(notification->progress);
}
/*! \brief Allocator for REFER Progress notification structure */
static struct refer_progress_notification *refer_progress_notification_alloc(struct refer_progress *progress, int response,
pjsip_evsub_state state)
{
struct refer_progress_notification *notification = ao2_alloc(sizeof(*notification), refer_progress_notification_destroy);
if (!notification) {
return NULL;
}
ao2_ref(progress, +1);
notification->progress = progress;
notification->response = response;
notification->state = state;
return notification;
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
/*! \brief Serialized callback for subscription notification
*
* Locking and serialization:
*
* Although refer_progress_notify() always runs in the progress serializer,
* the pjproject evsub module itself can cause the subscription to be
* destroyed which then triggers refer_progress_on_evsub_state() to clean
* it up. In this case, it's possible that refer_progress_notify() could
* get the subscription pulled out from under it while it's trying to use it.
*
* At one point we tried to have refer_progress_on_evsub_state() push the
* cleanup to the serializer and wait for its return before returning to
* pjproject but since pjproject calls its state callbacks with the dialog
* locked, this required us to unlock the dialog while waiting for the
* serialized cleanup, then lock it again before returning to pjproject.
* There were also still some cases where other callers of
* refer_progress_notify() weren't using the serializer and crashes were
* resulting.
*
* Although all callers of refer_progress_notify() now use the progress
* serializer, we decided to simplify the locking so we didn't have to
* unlock and relock the dialog in refer_progress_on_evsub_state().
*
* Now, refer_progress_notify() holds the dialog lock for all its work
* rather than just when calling pjsip_evsub_set_mod_data() to clear the
* module data. Since pjproject also holds the dialog lock while calling
* refer_progress_on_evsub_state(), there should be no more chances for
* the subscription to be cleaned up while still being used to send NOTIFYs.
*/
static int refer_progress_notify(void *data)
{
RAII_VAR(struct refer_progress_notification *, notification, data, ao2_cleanup);
pjsip_evsub *sub;
pjsip_tx_data *tdata;
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
pjsip_dlg_inc_lock(notification->progress->dlg);
/* If the subscription has already been terminated we can't send a notification */
if (!(sub = notification->progress->sub)) {
ast_debug(3, "Not sending NOTIFY of response '%d' and state '%u' on progress monitor '%p' as subscription has been terminated\n",
notification->response, notification->state, notification->progress);
pjsip_dlg_dec_lock(notification->progress->dlg);
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
return 0;
}
/* Send a deferred initial 100 Trying SIP frag NOTIFY if we haven't already. */
if (!notification->progress->sent_100) {
notification->progress->sent_100 = 1;
if (notification->response != 100) {
ast_debug(3, "Sending initial 100 Trying NOTIFY for progress monitor '%p'\n",
notification->progress);
if (pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) {
pjsip_xfer_send_request(sub, tdata);
}
}
}
ast_debug(3, "Sending NOTIFY with response '%d' and state '%u' on subscription '%p' and progress monitor '%p'\n",
notification->response, notification->state, sub, notification->progress);
/* Actually send the notification */
if (pjsip_xfer_notify(sub, notification->state, notification->response, NULL, &tdata) == PJ_SUCCESS) {
pjsip_xfer_send_request(sub, tdata);
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
pjsip_dlg_dec_lock(notification->progress->dlg);
return 0;
}
static void refer_progress_bridge(void *data, struct stasis_subscription *sub,
Multiple revisions 399887,400138,400178,400180-400181 ........ r399887 | dlee | 2013-09-26 10:41:47 -0500 (Thu, 26 Sep 2013) | 1 line Minor performance bump by not allocate manager variable struct if we don't need it ........ r400138 | dlee | 2013-09-30 10:24:00 -0500 (Mon, 30 Sep 2013) | 23 lines Stasis performance improvements This patch addresses several performance problems that were found in the initial performance testing of Asterisk 12. The Stasis dispatch object was allocated as an AO2 object, even though it has a very confined lifecycle. This was replaced with a straight ast_malloc(). The Stasis message router was spending an inordinate amount of time searching hash tables. In this case, most of our routers had 6 or fewer routes in them to begin with. This was replaced with an array that's searched linearly for the route. We more heavily rely on AO2 objects in Asterisk 12, and the memset() in ao2_ref() actually became noticeable on the profile. This was #ifdef'ed to only run when AO2_DEBUG was enabled. After being misled by an erroneous comment in taskprocessor.c during profiling, the wrong comment was removed. Review: https://reviewboard.asterisk.org/r/2873/ ........ r400178 | dlee | 2013-09-30 13:26:27 -0500 (Mon, 30 Sep 2013) | 24 lines Taskprocessor optimization; switch Stasis to use taskprocessors This patch optimizes taskprocessor to use a semaphore for signaling, which the OS can do a better job at managing contention and waiting that we can with a mutex and condition. The taskprocessor execution was also slightly optimized to reduce the number of locks taken. The only observable difference in the taskprocessor implementation is that when the final reference to the taskprocessor goes away, it will execute all tasks to completion instead of discarding the unexecuted tasks. For systems where unnamed semaphores are not supported, a really simple semaphore implementation is provided. (Which gives identical performance as the original taskprocessor implementation). The way we ended up implementing Stasis caused the threadpool to be a burden instead of a boost to performance. This was switched to just use taskprocessors directly for subscriptions. Review: https://reviewboard.asterisk.org/r/2881/ ........ r400180 | dlee | 2013-09-30 13:39:34 -0500 (Mon, 30 Sep 2013) | 28 lines Optimize how Stasis forwards are dispatched This patch optimizes how forwards are dispatched in Stasis. Originally, forwards were dispatched as subscriptions that are invoked on the publishing thread. This did not account for the vast number of forwards we would end up having in the system, and the amount of work it would take to walk though the forward subscriptions. This patch modifies Stasis so that rather than walking the tree of forwards on every dispatch, when forwards and subscriptions are changed, the subscriber list for every topic in the tree is changed. This has a couple of benefits. First, this reduces the workload of dispatching messages. It also reduces contention when dispatching to different topics that happen to forward to the same aggregation topic (as happens with all of the channel, bridge and endpoint topics). Since forwards are no longer subscriptions, the bulk of this patch is simply changing stasis_subscription objects to stasis_forward objects (which, admittedly, I should have done in the first place.) Since this required me to yet again put in a growing array, I finally abstracted that out into a set of ast_vector macros in asterisk/vector.h. Review: https://reviewboard.asterisk.org/r/2883/ ........ r400181 | dlee | 2013-09-30 13:48:57 -0500 (Mon, 30 Sep 2013) | 28 lines Remove dispatch object allocation from Stasis publishing While looking for areas for performance improvement, I realized that an unused feature in Stasis was negatively impacting performance. When a message is sent to a subscriber, a dispatch object is allocated for the dispatch, containing the topic the message was published to, the subscriber the message is being sent to, and the message itself. The topic is actually unused by any subscriber in Asterisk today. And the subscriber is associated with the taskprocessor the message is being dispatched to. First, this patch removes the unused topic parameter from Stasis subscription callbacks. Second, this patch introduces the concept of taskprocessor local data, data that may be set on a taskprocessor and provided along with the data pointer when a task is pushed using the ast_taskprocessor_push_local() call. This allows the task to have both data specific to that taskprocessor, in addition to data specific to that invocation. With those two changes, the dispatch object can be removed completely, and the message is simply refcounted and sent directly to the taskprocessor. Review: https://reviewboard.asterisk.org/r/2884/ ........ Merged revisions 399887,400138,400178,400180-400181 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@400186 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2013-09-30 18:55:27 +00:00
struct stasis_message *message)
{
struct refer_progress *progress = data;
struct ast_bridge_blob *enter_blob;
struct refer_progress_notification *notification;
struct ast_channel *chan;
if (stasis_subscription_final_message(sub, message)) {
ao2_ref(progress, -1);
return;
}
if (ast_channel_entered_bridge_type() != stasis_message_type(message)) {
/* Don't care */
return;
}
enter_blob = stasis_message_data(message);
if (strcmp(enter_blob->channel->base->uniqueid, progress->transferee)) {
/* Don't care */
return;
}
if (!progress->transfer_data->completed) {
/* We can't act on this message because the transfer_channel_data doesn't show that
* the transfer is ready to progress */
return;
}
/* OMG the transferee is joining a bridge. His call got answered! */
notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
if (notification) {
if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
progress->bridge_sub = stasis_unsubscribe(progress->bridge_sub);
}
chan = ast_channel_get_by_name(progress->transferee);
if (!chan) {
/* The channel is already gone */
return;
}
ast_channel_lock(chan);
ast_debug(3, "Detaching REFER progress monitoring hook from '%s' as it has joined a bridge\n",
ast_channel_name(chan));
ast_framehook_detach(chan, progress->framehook);
ast_channel_unlock(chan);
ast_channel_unref(chan);
}
/*! \brief Progress monitoring frame hook - examines frames to determine state of transfer */
static struct ast_frame *refer_progress_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
{
struct refer_progress *progress = data;
struct refer_progress_notification *notification = NULL;
/* We only care about frames *to* the channel */
if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
return f;
}
/* If the completed flag hasn't been raised, skip this pass. */
if (!progress->transfer_data->completed) {
return f;
}
/* Determine the state of the REFER based on the control frames (or voice frames) passing */
if (f->frametype == AST_FRAME_VOICE && !progress->subclass) {
/* Media is passing without progress, this means the call has been answered */
progress->subclass = AST_CONTROL_ANSWER;
notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
} else if (f->frametype == AST_FRAME_CONTROL) {
/* Based on the control frame being written we can send a NOTIFY advising of the progress */
if ((f->subclass.integer == AST_CONTROL_RING) || (f->subclass.integer == AST_CONTROL_RINGING)) {
/* Don't set progress->subclass; an ANSWER can still follow */
notification = refer_progress_notification_alloc(progress, 180, PJSIP_EVSUB_STATE_ACTIVE);
} else if (f->subclass.integer == AST_CONTROL_BUSY) {
progress->subclass = f->subclass.integer;
notification = refer_progress_notification_alloc(progress, 486, PJSIP_EVSUB_STATE_TERMINATED);
} else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
progress->subclass = f->subclass.integer;
notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED);
} else if (f->subclass.integer == AST_CONTROL_PROGRESS) {
/* Don't set progress->subclass; an ANSWER can still follow */
notification = refer_progress_notification_alloc(progress, 183, PJSIP_EVSUB_STATE_ACTIVE);
} else if (f->subclass.integer == AST_CONTROL_PROCEEDING) {
/* Don't set progress->subclass; an ANSWER can still follow */
notification = refer_progress_notification_alloc(progress, 100, PJSIP_EVSUB_STATE_ACTIVE);
} else if (f->subclass.integer == AST_CONTROL_ANSWER) {
progress->subclass = f->subclass.integer;
notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
}
}
/* If a notification is due to be sent push it to the thread pool */
if (notification) {
/* If the subscription is being terminated we don't need the frame hook any longer */
if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) {
ast_debug(3, "Detaching REFER progress monitoring hook from '%s' as subscription is being terminated\n",
ast_channel_name(chan));
ast_framehook_detach(chan, progress->framehook);
}
if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
return f;
}
/*! \brief Destroy callback for monitoring framehook */
static void refer_progress_framehook_destroy(void *data)
{
struct refer_progress *progress = data;
struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED);
if (notification && ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
if (progress->bridge_sub) {
progress->bridge_sub = stasis_unsubscribe(progress->bridge_sub);
}
ao2_cleanup(progress);
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
/*!
* \brief Callback for REFER subscription state changes
* \see refer_progress_notify
*
* The documentation attached to refer_progress_notify has more
* information about the locking issues with cleaning up
* the subscription.
*
* \note pjproject holds the dialog lock while calling this function.
*/
static void refer_progress_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
{
struct refer_progress *progress = pjsip_evsub_get_mod_data(sub, refer_progress_module.id);
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
/*
* If being destroyed, remove the progress object from the subscription
* and release the reference it had.
*/
if (progress && (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)) {
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
pjsip_evsub_set_mod_data(progress->sub, refer_progress_module.id, NULL);
progress->sub = NULL;
ao2_cleanup(progress);
}
}
/*! \brief Callback structure for subscription */
static pjsip_evsub_user refer_progress_evsub_cb = {
.on_evsub_state = refer_progress_on_evsub_state,
};
static int dlg_releaser_task(void *data) {
pjsip_dlg_dec_session((pjsip_dialog *)data, &refer_progress_module);
return 0;
}
/*! \brief Destructor for REFER progress sutrcture */
static void refer_progress_destroy(void *obj)
{
struct refer_progress *progress = obj;
if (progress->bridge_sub) {
progress->bridge_sub = stasis_unsubscribe(progress->bridge_sub);
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
if (progress->dlg) {
/*
* Although the dlg session count was incremented in a pjsip servant
* thread, there's no guarantee that the last thread to unref this progress
* object was one. Before we decrement, we need to make sure that this
* is either a servant thread or that we push the decrement to a
* serializer that is one.
*
* Because pjsip_dlg_dec_session requires the dialog lock, we don't want
* to wait on the task to complete if we had to push it to a serializer.
*/
if (ast_sip_thread_is_servant()) {
pjsip_dlg_dec_session(progress->dlg, &refer_progress_module);
} else {
ast_sip_push_task(NULL, dlg_releaser_task, progress->dlg);
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
}
ao2_cleanup(progress->transfer_data);
ast_free(progress->transferee);
ast_taskprocessor_unreference(progress->serializer);
}
/*! \brief Internal helper function which sets up a refer progress structure if needed */
static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *rdata, struct refer_progress **progress)
{
const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
pjsip_generic_string_hdr *refer_sub = NULL;
const pj_str_t str_true = { "true", 4 };
pjsip_hdr hdr_list;
char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
*progress = NULL;
/* Grab the optional Refer-Sub header, it can be used to suppress the implicit subscription */
refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL);
if ((refer_sub && pj_strnicmp(&refer_sub->hvalue, &str_true, 4))) {
return 0;
}
if (!(*progress = ao2_alloc(sizeof(struct refer_progress), refer_progress_destroy))) {
return -1;
}
ast_debug(3, "Created progress monitor '%p' for transfer occurring from channel '%s' and endpoint '%s'\n",
progress, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
(*progress)->refer_blind_progress = session->endpoint->refer_blind_progress;
(*progress)->framehook = -1;
/* Create name with seq number appended. */
ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/refer/%s",
ast_sorcery_object_get_id(session->endpoint));
if (!((*progress)->serializer = ast_sip_create_serializer(tps_name))) {
goto error;
}
/* Create the implicit subscription for monitoring of this transfer */
if (pjsip_xfer_create_uas(session->inv_session->dlg, &refer_progress_evsub_cb, rdata, &(*progress)->sub) != PJ_SUCCESS) {
goto error;
}
res_pjsip_refer: Refactor progress locking and serialization Although refer_progress_notify() always runs in the progress serializer, the pjproject evsub module itself can cause the subscription to be destroyed which then triggers refer_progress_on_evsub_state() to clean it up. In this case, it's possible that refer_progress_notify() could get the subscription pulled out from under it while it's trying to use it. At one point we tried to have refer_progress_on_evsub_state() push the cleanup to the serializer and wait for its return before returning to pjproject but since pjproject calls its state callbacks with the dialog locked, this required us to unlock the dialog while waiting for the serialized cleanup, then lock it again before returning to pjproject. There were also still some cases where other callers of refer_progress_notify() weren't using the serializer and crashes were resulting. Although all callers of refer_progress_notify() now use the progress serializer, we decided to simplify the locking so we didn't have to unlock and relock the dialog in refer_progress_on_evsub_state(). Now, refer_progress_notify() holds the dialog lock for its duration and since pjproject also holds the dialog lock while calling refer_progress_on_evsub_state() (which does the cleanup), there should be no more chances for the subscription to be cleaned up while still being used to send NOTIFYs. To be extra safe, we also now increment the session count on the dialog when we create a progress object and decrement the count when the progress is destroyed. ASTERISK-29313 Change-Id: I97a8bb01771a3c85345649b8124507f7622a8480
2021-02-19 19:25:13 +00:00
/* To prevent a potential deadlock we need the dialog so we can lock/unlock */
(*progress)->dlg = session->inv_session->dlg;
/* We also need to make sure it stays around until we're done with it */
pjsip_dlg_inc_session((*progress)->dlg, &refer_progress_module);
/* Associate the REFER progress structure with the subscription */
ao2_ref(*progress, +1);
pjsip_evsub_set_mod_data((*progress)->sub, refer_progress_module.id, *progress);
pj_list_init(&hdr_list);
if (refer_sub) {
pjsip_hdr *hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(session->inv_session->dlg->pool, &str_refer_sub, &str_true);
pj_list_push_back(&hdr_list, hdr);
}
/* Accept the REFER request */
ast_debug(3, "Accepting REFER request for progress monitor '%p'\n", *progress);
pjsip_xfer_accept((*progress)->sub, rdata, 202, &hdr_list);
return 0;
error:
ao2_cleanup(*progress);
*progress = NULL;
return -1;
}
/*! \brief Structure for attended transfer task */
struct refer_attended {
/*! \brief Transferer session */
struct ast_sip_session *transferer;
/*! \brief Transferer channel */
struct ast_channel *transferer_chan;
/*! \brief Second transferer session */
struct ast_sip_session *transferer_second;
/*! \brief Optional refer progress structure */
struct refer_progress *progress;
};
/*! \brief Destructor for attended transfer task */
static void refer_attended_destroy(void *obj)
{
struct refer_attended *attended = obj;
ao2_cleanup(attended->transferer);
ast_channel_cleanup(attended->transferer_chan);
ao2_cleanup(attended->transferer_second);
ao2_cleanup(attended->progress);
}
/*! \brief Allocator for attended transfer task */
static struct refer_attended *refer_attended_alloc(struct ast_sip_session *transferer,
struct ast_sip_session *transferer_second,
struct refer_progress *progress)
{
struct refer_attended *attended;
attended = ao2_alloc_options(sizeof(*attended), refer_attended_destroy,
AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!attended) {
return NULL;
}
ao2_ref(transferer, +1);
attended->transferer = transferer;
ast_channel_ref(transferer->channel);
attended->transferer_chan = transferer->channel;
ao2_ref(transferer_second, +1);
attended->transferer_second = transferer_second;
if (progress) {
ao2_ref(progress, +1);
attended->progress = progress;
}
return attended;
}
static int session_end_if_deferred_task(void *data)
{
struct ast_sip_session *session = data;
ast_sip_session_end_if_deferred(session);
ao2_ref(session, -1);
return 0;
}
static int defer_termination_cancel_task(void *data)
{
struct ast_sip_session *session = data;
ast_sip_session_end_if_deferred(session);
ast_sip_session_defer_termination_cancel(session);
ao2_ref(session, -1);
return 0;
}
/*!
* \internal
* \brief Convert transfer enum to SIP response code.
* \since 13.3.0
*
* \param xfer_code Core transfer function enum result.
*
* \return SIP response code
*/
static int xfer_response_code2sip(enum ast_transfer_result xfer_code)
{
int response;
response = 503;
switch (xfer_code) {
case AST_BRIDGE_TRANSFER_INVALID:
response = 400;
break;
case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
response = 403;
break;
case AST_BRIDGE_TRANSFER_FAIL:
response = 500;
break;
case AST_BRIDGE_TRANSFER_SUCCESS:
response = 200;
break;
}
return response;
}
/*! \brief Task for attended transfer executed by attended->transferer_second serializer */
static int refer_attended_task(void *data)
{
struct refer_attended *attended = data;
int response;
int (*task_cb)(void *data);
if (attended->transferer_second->channel) {
ast_debug(3, "Performing a REFER attended transfer - Transferer #1: %s Transferer #2: %s\n",
ast_channel_name(attended->transferer_chan),
ast_channel_name(attended->transferer_second->channel));
response = xfer_response_code2sip(ast_bridge_transfer_attended(
attended->transferer_chan,
attended->transferer_second->channel));
ast_debug(3, "Final response for REFER attended transfer - Transferer #1: %s Transferer #2: %s is '%d'\n",
ast_channel_name(attended->transferer_chan),
ast_channel_name(attended->transferer_second->channel),
response);
} else {
ast_debug(3, "Received REFER request on channel '%s' but other channel has gone.\n",
ast_channel_name(attended->transferer_chan));
response = 603;
}
if (attended->progress) {
struct refer_progress_notification *notification;
notification = refer_progress_notification_alloc(attended->progress, response,
PJSIP_EVSUB_STATE_TERMINATED);
if (notification) {
if (ast_sip_push_task(attended->progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
}
if (response == 200) {
task_cb = session_end_if_deferred_task;
} else {
task_cb = defer_termination_cancel_task;
}
if (!ast_sip_push_task(attended->transferer->serializer,
task_cb, attended->transferer)) {
/* Gave the ref to the pushed task. */
attended->transferer = NULL;
} else {
/* Do this anyway even though it is the wrong serializer. */
ast_sip_session_end_if_deferred(attended->transferer);
}
ao2_ref(attended, -1);
return 0;
}
/*! \brief Structure for blind transfer callback details */
struct refer_blind {
/*! \brief Context being used for transfer */
const char *context;
/*! \brief Optional progress structure */
struct refer_progress *progress;
/*! \brief REFER message */
pjsip_rx_data *rdata;
/*! \brief Optional Replaces header */
pjsip_replaces_hdr *replaces;
/*! \brief Optional Refer-To header */
pjsip_sip_uri *refer_to;
/*! \brief Attended transfer flag */
unsigned int attended:1;
};
/*! \brief Blind transfer callback function */
static void refer_blind_callback(struct ast_channel *chan, struct transfer_channel_data *user_data_wrapper,
enum ast_transfer_type transfer_type)
{
struct refer_blind *refer = user_data_wrapper->data;
pjsip_generic_string_hdr *referred_by;
static const pj_str_t str_referred_by = { "Referred-By", 11 };
static const pj_str_t str_referred_by_s = { "b", 1 };
pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes");
if (refer->progress && !refer->attended && !refer->progress->refer_blind_progress) {
/* If blind transfer and endpoint doesn't want to receive all the progress details */
struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
PJSIP_EVSUB_STATE_TERMINATED);
if (notification) {
if (ast_sip_push_task(refer->progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
} else if (refer->progress) {
/* If attended transfer and progress monitoring is being done attach a frame hook so we can monitor it */
struct ast_framehook_interface hook = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = refer_progress_framehook,
.destroy_cb = refer_progress_framehook_destroy,
.data = refer->progress,
.disable_inheritance = 1,
};
refer->progress->transferee = ast_strdup(ast_channel_uniqueid(chan));
if (!refer->progress->transferee) {
struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
PJSIP_EVSUB_STATE_TERMINATED);
ast_log(LOG_WARNING, "Could not copy channel name '%s' during transfer - assuming success\n",
ast_channel_name(chan));
if (notification) {
if (ast_sip_push_task(refer->progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
}
/* Progress needs a reference to the transfer_channel_data so that it can track the completed status of the transfer */
ao2_ref(user_data_wrapper, +1);
refer->progress->transfer_data = user_data_wrapper;
/* We need to bump the reference count up on the progress structure since it is in the frame hook now */
ao2_ref(refer->progress, +1);
/* If we can't attach a frame hook for whatever reason send a notification of success immediately */
ast_channel_lock(chan);
refer->progress->framehook = ast_framehook_attach(chan, &hook);
ast_channel_unlock(chan);
if (refer->progress->framehook < 0) {
struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
PJSIP_EVSUB_STATE_TERMINATED);
ast_log(LOG_WARNING, "Could not attach REFER transfer progress monitoring hook to channel '%s' - assuming success\n",
ast_channel_name(chan));
if (notification) {
if (ast_sip_push_task(refer->progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
ao2_cleanup(refer->progress);
}
/* We need to bump the reference count for the stasis subscription */
ao2_ref(refer->progress, +1);
/* We also will need to detect if the transferee enters a bridge. This is currently the only reliable way to
* detect if the transfer target has answered the call
*/
main/stasis: Allow subscriptions to use a threadpool for message delivery Prior to this patch, all Stasis subscriptions would receive a dedicated thread for servicing published messages. In contrast, prior to r400178 (see review https://reviewboard.asterisk.org/r/2881/), the subscriptions shared a thread pool. It was discovered during some initial work on Stasis that, for a low subscription count with high message throughput, the threadpool was not as performant as simply having a dedicated thread per subscriber. For situations where a subscriber receives a substantial number of messages and is always present, the model of having a dedicated thread per subscriber makes sense. While we still have plenty of subscriptions that would follow this model, e.g., AMI, CDRs, CEL, etc., there are plenty that also fall into the following two categories: * Large number of subscriptions, specifically those tied to endpoints/peers. * Low number of messages. Some subscriptions exist specifically to coordinate a single message - the subscription is created, a message is published, the delivery is synchronized, and the subscription is destroyed. In both of the latter two cases, creating a dedicated thread is wasteful (and in the case of a large number of peers/endpoints, harmful). In those cases, having shared delivery threads is far more performant. This patch adds the ability of a subscriber to Stasis to choose whether or not their messages are dispatched on a dedicated thread or on a threadpool. The threadpool is configurable through stasis.conf. Review: https://reviewboard.asterisk.org/r/4193 ASTERISK-24533 #close Reported by: xrobau Tested by: xrobau ........ Merged revisions 428681 from http://svn.asterisk.org/svn/asterisk/branches/12 ........ Merged revisions 428687 from http://svn.asterisk.org/svn/asterisk/branches/13 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@428688 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-12-01 17:59:21 +00:00
refer->progress->bridge_sub = stasis_subscribe_pool(ast_bridge_topic_all(), refer_progress_bridge, refer->progress);
if (!refer->progress->bridge_sub) {
struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
PJSIP_EVSUB_STATE_TERMINATED);
ast_log(LOG_WARNING, "Could not create bridge stasis subscription for monitoring progress on transfer of channel '%s' - assuming success\n",
ast_channel_name(chan));
if (notification) {
if (ast_sip_push_task(refer->progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
ast_channel_lock(chan);
ast_framehook_detach(chan, refer->progress->framehook);
ast_channel_unlock(chan);
ao2_cleanup(refer->progress);
} else {
stasis_subscription_accept_message_type(refer->progress->bridge_sub, ast_channel_entered_bridge_type());
stasis_subscription_accept_message_type(refer->progress->bridge_sub, stasis_subscription_change_type());
stasis_subscription_set_filter(refer->progress->bridge_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
}
}
pbx_builtin_setvar_helper(chan, "SIPREFERRINGCONTEXT", S_OR(refer->context, NULL));
referred_by = pjsip_msg_find_hdr_by_names(refer->rdata->msg_info.msg,
&str_referred_by, &str_referred_by_s, NULL);
if (referred_by) {
size_t uri_size = pj_strlen(&referred_by->hvalue) + 1;
char *uri = ast_alloca(uri_size);
ast_copy_pj_str(uri, &referred_by->hvalue, uri_size);
pbx_builtin_setvar_helper(chan, "__SIPREFERREDBYHDR", S_OR(uri, NULL));
} else {
pbx_builtin_setvar_helper(chan, "SIPREFERREDBYHDR", NULL);
}
if (refer->replaces) {
char replaces[512];
char *replaces_val = NULL;
int len;
len = pjsip_hdr_print_on(refer->replaces, replaces, sizeof(replaces) - 1);
if (len != -1) {
/* pjsip_hdr_print_on does not NULL terminate the buffer */
replaces[len] = '\0';
replaces_val = replaces + sizeof("Replaces:");
}
pbx_builtin_setvar_helper(chan, "__SIPREPLACESHDR", replaces_val);
} else {
pbx_builtin_setvar_helper(chan, "SIPREPLACESHDR", NULL);
}
if (refer->refer_to) {
char refer_to[PJSIP_MAX_URL_SIZE];
pjsip_uri_print(PJSIP_URI_IN_REQ_URI, refer->refer_to, refer_to, sizeof(refer_to));
pbx_builtin_setvar_helper(chan, "SIPREFERTOHDR", S_OR(refer_to, NULL));
} else {
pbx_builtin_setvar_helper(chan, "SIPREFERTOHDR", NULL);
}
}
/*!
* \internal
* \brief Set the passed in context variable to the determined transfer context.
* \since 13.3.0
*
* \param context Set to the determined transfer context.
* \param session INVITE dialog SIP session.
*/
#define DETERMINE_TRANSFER_CONTEXT(context, session) \
do { \
ast_channel_lock((session)->channel); \
context = pbx_builtin_getvar_helper((session)->channel, "TRANSFER_CONTEXT"); \
if (ast_strlen_zero(context)) { \
context = (session)->endpoint->context; \
} else { \
context = ast_strdupa(context); \
} \
ast_channel_unlock((session)->channel); \
} while (0) \
static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
pjsip_param *replaces_param, struct refer_progress *progress)
{
const pj_str_t str_replaces = { "Replaces", 8 };
pj_str_t replaces_content;
pjsip_replaces_hdr *replaces;
int parsed_len;
pjsip_dialog *dlg;
pj_strdup_with_null(rdata->tp_info.pool, &replaces_content, &replaces_param->value);
/* Parsing the parameter as a Replaces header easily grabs the needed information */
if (!(replaces = pjsip_parse_hdr(rdata->tp_info.pool, &str_replaces, replaces_content.ptr,
pj_strlen(&replaces_content), &parsed_len))) {
ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' with invalid Replaces header, rejecting\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 400;
}
/* See if the dialog is local, or remote */
if ((dlg = pjsip_ua_find_dialog(&replaces->call_id, &replaces->to_tag, &replaces->from_tag, PJ_TRUE))) {
RAII_VAR(struct ast_sip_session *, other_session, ast_sip_dialog_get_session(dlg), ao2_cleanup);
struct refer_attended *attended;
pjsip_dlg_dec_lock(dlg);
if (!other_session) {
ast_debug(3, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but no session exists on it\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 603;
}
/* We defer actually doing the attended transfer to the other session so no deadlock can occur */
if (!(attended = refer_attended_alloc(session, other_session, progress))) {
ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but could not allocate structure to complete, rejecting\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 500;
}
if (ast_sip_session_defer_termination(session)) {
ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but could not defer termination, rejecting\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
ao2_cleanup(attended);
return 500;
}
/* Push it to the other session, which will have both channels with minimal locking */
if (ast_sip_push_task(other_session->serializer, refer_attended_task, attended)) {
ast_sip_session_end_if_deferred(session);
ast_sip_session_defer_termination_cancel(session);
ao2_cleanup(attended);
return 500;
}
ast_debug(3, "Attended transfer from '%s' pushed to second channel serializer\n",
ast_channel_name(session->channel));
return 200;
} else {
const char *context;
struct refer_blind refer = { 0, };
int response;
DETERMINE_TRANSFER_CONTEXT(context, session);
if (!ast_exists_extension(NULL, context, "external_replaces", 1, NULL)) {
ast_log(LOG_ERROR, "Received REFER for remote session on channel '%s' from endpoint '%s' but 'external_replaces' extension not found in context %s\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), context);
return 404;
}
refer.context = context;
refer.progress = progress;
refer.rdata = rdata;
refer.replaces = replaces;
refer.refer_to = target_uri;
refer.attended = 1;
if (ast_sip_session_defer_termination(session)) {
ast_log(LOG_ERROR, "Received REFER for remote session on channel '%s' from endpoint '%s' but could not defer termination, rejecting\n",
ast_channel_name(session->channel),
ast_sorcery_object_get_id(session->endpoint));
return 500;
}
response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel,
"external_replaces", context, refer_blind_callback, &refer));
ast_sip_session_end_if_deferred(session);
if (response != 200) {
ast_sip_session_defer_termination_cancel(session);
}
return response;
}
}
static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target,
struct refer_progress *progress)
{
const char *context;
char exten[AST_MAX_EXTENSION];
struct refer_blind refer = { 0, };
int response;
/* If no explicit transfer context has been provided use their configured context */
DETERMINE_TRANSFER_CONTEXT(context, session);
/* Using the user portion of the target URI see if it exists as a valid extension in their context */
ast_copy_pj_str(exten, &target->user, sizeof(exten));
/*
* We may want to match in the dialplan without any user
* options getting in the way.
*/
AST_SIP_USER_OPTIONS_TRUNCATE_CHECK(exten);
/* Uri without exten */
if (ast_strlen_zero(exten)) {
ast_copy_string(exten, "s", sizeof(exten));
ast_debug(3, "Channel '%s' from endpoint '%s' attempted blind transfer to a target without extension. Target was set to 's@%s'\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), context);
}
if (!ast_exists_extension(NULL, context, exten, 1, NULL)) {
ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted blind transfer to '%s@%s' but target does not exist\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), exten, context);
return 404;
}
refer.context = context;
refer.progress = progress;
refer.rdata = rdata;
refer.refer_to = target;
refer.attended = 0;
if (ast_sip_session_defer_termination(session)) {
ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted blind transfer but could not defer termination, rejecting\n",
ast_channel_name(session->channel),
ast_sorcery_object_get_id(session->endpoint));
return 500;
}
response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel,
exten, context, refer_blind_callback, &refer));
ast_sip_session_end_if_deferred(session);
if (response != 200) {
ast_sip_session_defer_termination_cancel(session);
}
return response;
}
/*! \brief Structure used to retrieve channel from another session */
struct invite_replaces {
/*! \brief Session we want the channel from */
struct ast_sip_session *session;
/*! \brief Channel from the session (with reference) */
struct ast_channel *channel;
/*! \brief Bridge the channel is in */
struct ast_bridge *bridge;
};
/*! \brief Task for invite replaces */
static int invite_replaces(void *data)
{
struct invite_replaces *invite = data;
if (!invite->session->channel) {
return -1;
}
ast_channel_ref(invite->session->channel);
invite->channel = invite->session->channel;
invite->bridge = ast_bridge_transfer_acquire_bridge(invite->channel);
return 0;
}
static int refer_incoming_invite_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{
pjsip_dialog *other_dlg = NULL;
pjsip_tx_data *packet;
int response = 0;
RAII_VAR(struct ast_sip_session *, other_session, NULL, ao2_cleanup);
struct invite_replaces invite;
/* If a Replaces header is present make sure it is valid */
if (pjsip_replaces_verify_request(rdata, &other_dlg, PJ_TRUE, &packet) != PJ_SUCCESS) {
response = packet->msg->line.status.code;
ast_assert(response != 0);
pjsip_tx_data_dec_ref(packet);
goto inv_replace_failed;
}
/* If no other dialog exists then this INVITE request does not have a Replaces header */
if (!other_dlg) {
return 0;
}
other_session = ast_sip_dialog_get_session(other_dlg);
pjsip_dlg_dec_lock(other_dlg);
/* Don't accept an in-dialog INVITE with Replaces as it does not make much sense */
if (session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
response = 488;
goto inv_replace_failed;
}
if (!other_session) {
ast_debug(3, "INVITE with Replaces received on channel '%s' from endpoint '%s', but requested session does not exist\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
response = 481;
goto inv_replace_failed;
}
invite.session = other_session;
res_pjsip.c: Split ast_sip_push_task_synchronous() to fit expectations. ast_sip_push_task_synchronous() did not necessarily execute the passed in task under the specified serializer. If the current thread is any registered pjsip thread then it would execute the task immediately instead of under the specified serializer. Reentrancy issues could result if the task does not execute with the right serializer. The original reason ast_sip_push_task_synchronous() checked to see if the current thread was a registered pjsip thread was because of a deadlock with masquerades and the channel technology's fixup callback (ASTERISK_22936). A subsequent masquerade deadlock fix (ASTERISK_24356) involving call pickups avoided the original deadlock situation entirely. The PJSIP channel technology's fixup callback no longer needed to call ast_sip_push_task_synchronous(). However, there are a few places where this unexpected behavior is still required to avoid deadlocks. The pjsip monitor thread executes callbacks that do calls to ast_sip_push_task_synchronous() that would deadlock if the task were actually pushed to the specified serializer. I ran into one dealing with the pubsub subscriptions where an ao2 destructor called ast_sip_push_task_synchronous(). * Split ast_sip_push_task_synchronous() into ast_sip_push_task_wait_servant() and ast_sip_push_task_wait_serializer(). ast_sip_push_task_wait_servant() has the old behavior of ast_sip_push_task_synchronous(). ast_sip_push_task_wait_serializer() has the new behavior where the task is always executed by the specified serializer or a picked serializer if one is not passed in. Both functions behave the same if the current thread is not a SIP servant. * Redirected ast_sip_push_task_synchronous() to ast_sip_push_task_wait_servant() to preserve API for released branches. ASTERISK_26806 Change-Id: Id040fa42c0e5972f4c8deef380921461d213b9f3
2018-03-27 16:04:42 +00:00
if (ast_sip_push_task_wait_serializer(other_session->serializer, invite_replaces,
&invite)) {
response = 481;
goto inv_replace_failed;
}
ast_channel_lock(session->channel);
ast_setstate(session->channel, AST_STATE_RING);
ast_channel_unlock(session->channel);
ast_raw_answer(session->channel);
ast_debug(3, "INVITE with Replaces being attempted. '%s' --> '%s'\n",
ast_channel_name(session->channel), ast_channel_name(invite.channel));
/* Unhold the channel now, as later we are not having access to it anymore */
ast_queue_unhold(session->channel);
ast_queue_frame(session->channel, &ast_null_frame);
if (!invite.bridge) {
struct ast_channel *chan = session->channel;
/*
* This will use a synchronous task but we aren't operating in
* the serializer at this point in time, so it won't deadlock.
*/
if (!ast_channel_move(invite.channel, chan)) {
/*
* We can't directly use session->channel because ast_channel_move()
* does a masquerade which changes session->channel to a different
* channel. To ensure we work on the right channel we store a
* pointer locally before we begin so it remains valid.
*/
ast_hangup(chan);
} else {
response = AST_CAUSE_FAILURE;
}
} else {
if (ast_bridge_impart(invite.bridge, session->channel, invite.channel, NULL,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
response = AST_CAUSE_FAILURE;
}
}
ast_channel_unref(invite.channel);
ao2_cleanup(invite.bridge);
if (!response) {
/*
* On success we cannot use session->channel in the debug message.
* This thread either no longer has a ref to session->channel or
* session->channel is no longer the original channel.
*/
ast_debug(3, "INVITE with Replaces successfully completed.\n");
} else {
ast_debug(3, "INVITE with Replaces failed on channel '%s', hanging up with cause '%d'\n",
ast_channel_name(session->channel), response);
ast_channel_lock(session->channel);
ast_channel_hangupcause_set(session->channel, response);
ast_channel_unlock(session->channel);
ast_hangup(session->channel);
}
return 1;
inv_replace_failed:
if (session->inv_session->dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
ast_debug(3, "INVITE with Replaces failed on channel '%s', sending response of '%d'\n",
ast_channel_name(session->channel), response);
session->defer_terminate = 1;
ast_hangup(session->channel);
if (pjsip_inv_end_session(session->inv_session, response, NULL, &packet) == PJ_SUCCESS
&& packet) {
ast_sip_session_send_response(session, packet);
}
} else {
ast_debug(3, "INVITE with Replaces in-dialog on channel '%s', hanging up\n",
ast_channel_name(session->channel));
ast_queue_hangup(session->channel);
}
return 1;
}
static int refer_incoming_refer_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
{
pjsip_generic_string_hdr *refer_to;
char *uri;
size_t uri_size;
pjsip_uri *target;
pjsip_sip_uri *target_uri;
RAII_VAR(struct refer_progress *, progress, NULL, ao2_cleanup);
pjsip_param *replaces;
int response;
static const pj_str_t str_refer_to = { "Refer-To", 8 };
static const pj_str_t str_refer_to_s = { "r", 1 };
static const pj_str_t str_replaces = { "Replaces", 8 };
if (!session->channel) {
/* No channel to refer. Likely because the call was just hung up. */
pjsip_dlg_respond(session->inv_session->dlg, rdata, 404, NULL, NULL, NULL);
ast_debug(3, "Received a REFER on a session with no channel from endpoint '%s'.\n",
ast_sorcery_object_get_id(session->endpoint));
return 0;
}
if (!session->endpoint->allowtransfer) {
pjsip_dlg_respond(session->inv_session->dlg, rdata, 603, NULL, NULL, NULL);
ast_log(LOG_WARNING, "Endpoint %s transfer attempt blocked due to configuration\n",
ast_sorcery_object_get_id(session->endpoint));
return 0;
}
/* A Refer-To header is required */
refer_to = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &str_refer_to, &str_refer_to_s, NULL);
if (!refer_to) {
pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
ast_debug(3, "Received a REFER without Refer-To on channel '%s' from endpoint '%s'\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 0;
}
/* The ast_copy_pj_str to uri is needed because it puts the NULL terminator to the uri
* as pjsip_parse_uri require a NULL terminated uri
*/
uri_size = pj_strlen(&refer_to->hvalue) + 1;
uri = ast_alloca(uri_size);
ast_copy_pj_str(uri, &refer_to->hvalue, uri_size);
target = pjsip_parse_uri(rdata->tp_info.pool, uri, uri_size - 1, 0);
if (!target
|| (!PJSIP_URI_SCHEME_IS_SIP(target)
&& !PJSIP_URI_SCHEME_IS_SIPS(target))) {
pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
ast_debug(3, "Received a REFER without a parseable Refer-To ('%s') on channel '%s' from endpoint '%s'\n",
uri, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 0;
}
target_uri = pjsip_uri_get_uri(target);
/* Set up REFER progress subscription if requested/possible */
if (refer_progress_alloc(session, rdata, &progress)) {
pjsip_dlg_respond(session->inv_session->dlg, rdata, 500, NULL, NULL, NULL);
ast_debug(3, "Could not set up subscription for REFER on channel '%s' from endpoint '%s'\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
return 0;
}
/* Determine if this is an attended or blind transfer */
if ((replaces = pjsip_param_find(&target_uri->header_param, &str_replaces)) ||
(replaces = pjsip_param_find(&target_uri->other_param, &str_replaces))) {
response = refer_incoming_attended_request(session, rdata, target_uri, replaces, progress);
} else {
response = refer_incoming_blind_request(session, rdata, target_uri, progress);
}
if (!progress) {
/* The transferer has requested no subscription, so send a final response immediately */
pjsip_tx_data *tdata;
const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
const pj_str_t str_false = { "false", 5 };
pjsip_hdr *hdr;
ast_debug(3, "Progress monitoring not requested for REFER on channel '%s' from endpoint '%s', sending immediate response of '%d'\n",
ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), response);
if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, response, NULL, &tdata) != PJ_SUCCESS) {
pjsip_dlg_respond(session->inv_session->dlg, rdata, response, NULL, NULL, NULL);
return 0;
}
hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub, &str_false);
pjsip_msg_add_hdr(tdata->msg, hdr);
pjsip_dlg_send_response(session->inv_session->dlg, pjsip_rdata_get_tsx(rdata), tdata);
} else if (response != 200) {
/* Since this failed we can send a final NOTIFY now and terminate the subscription */
struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, response, PJSIP_EVSUB_STATE_TERMINATED);
if (notification) {
/* The refer_progress_notify function will call ao2_cleanup on this for us */
if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
ao2_cleanup(notification);
}
}
}
return 0;
}
static int refer_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
{
if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_refer_method())) {
return refer_incoming_refer_request(session, rdata);
} else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_invite_method)) {
return refer_incoming_invite_request(session, rdata);
} else {
return 0;
}
}
/*!
* \brief Use the value of a channel variable as the value of a SIP header
*
* This looks up a variable name on a channel, then takes that value and adds
* it to an outgoing SIP request. If the header already exists on the message,
* then no action is taken.
*
* \pre chan is locked.
*
* \param chan The channel on which to find the variable.
* \param var_name The name of the channel variable to use.
* \param header_name The name of the SIP header to add to the outgoing message.
* \param tdata The outgoing SIP message on which to add the header
*/
static void add_header_from_channel_var(struct ast_channel *chan, const char *var_name, const char *header_name, pjsip_tx_data *tdata)
{
const char *var_value;
pj_str_t pj_header_name;
pjsip_hdr *header;
var_value = pbx_builtin_getvar_helper(chan, var_name);
if (ast_strlen_zero(var_value)) {
return;
}
pj_cstr(&pj_header_name, header_name);
header = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_header_name, NULL);
if (header) {
return;
}
ast_sip_add_header(tdata, header_name, var_value);
}
static void refer_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
{
if (pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_invite_method)
|| !session->channel
|| session->inv_session->state != PJSIP_INV_STATE_NULL) {
return;
}
ast_channel_lock(session->channel);
add_header_from_channel_var(session->channel, "SIPREPLACESHDR", "Replaces", tdata);
add_header_from_channel_var(session->channel, "SIPREFERREDBYHDR", "Referred-By", tdata);
ast_channel_unlock(session->channel);
}
static struct ast_sip_session_supplement refer_supplement = {
.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1,
.incoming_request = refer_incoming_request,
.outgoing_request = refer_outgoing_request,
};
static int load_module(void)
{
const pj_str_t str_norefersub = { "norefersub", 10 };
pjsip_replaces_init_module(ast_sip_get_pjsip_endpoint());
pjsip_xfer_init_module(ast_sip_get_pjsip_endpoint());
if (ast_sip_get_norefersub()) {
pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
}
ast_sip_register_service(&refer_progress_module);
ast_sip_session_register_supplement(&refer_supplement);
ast_module_shutdown_ref(ast_module_info->self);
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
ast_sip_session_unregister_supplement(&refer_supplement);
ast_sip_unregister_service(&refer_progress_module);
return 0;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Blind and Attended Transfer Support",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.load_pri = AST_MODPRI_APP_DEPEND,
.requires = "res_pjsip,res_pjsip_session,res_pjsip_pubsub",
);