summaryrefslogtreecommitdiff
path: root/res/ari
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2016-05-09 14:48:51 -0500
committerMark Michelson <mmichelson@digium.com>2016-05-27 09:08:49 -0500
commit88d997913faabe81f8b9e7bdaa56742be0d669b9 (patch)
treedde0bb91e5840514d684887bb8e5e10864e3dcc3 /res/ari
parentf6c33771f660c3ad15bc554b355cb21e83c85e36 (diff)
ARI: Re-implement the ARI dial command, allowing for early bridging.
ARI dial had been implemented using the Dial API. This made great sense when dialing was 100% separate from bridging. However, if a channel were to be added to a bridge during the dial attempt, there would be a conflict between the dialing thread and the bridging thread. Each would be attempting to read frames from the dialed channel and act on them. The initial attempt to make the two play nice was to have the Dial API suspend the channel in the bridge and stay in charge of the channel until the dial was complete. The problem with this was that it was riddled with potential race conditions. It also was not well-suited for the case where the channel changed which bridge it was in during the dial. This new approach removes the use of the Dial API altogether. Instead, the channel we are dialing is placed into an invisible ARI dialing bridge. The bridge channel thread handles incoming frames from the channel. If the channel is added to a real bridge, it is departed from the invisible bridge and then added to the real bridge. Similarly, if the channel is removed from the real bridge, it is automatically added back to the invisible bridge if the dial attempt is still active. This approach keeps the threading simple by always having the channel being handled by bridge channel threads. ASTERISK-25925 Change-Id: I7750359ddf45fcd45eaec749c5b3822de4a8ddbb
Diffstat (limited to 'res/ari')
-rw-r--r--res/ari/resource_channels.c139
1 files changed, 114 insertions, 25 deletions
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index c838bc39c..d0abcfd25 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -48,6 +48,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/format_cache.h"
#include "asterisk/core_local.h"
#include "asterisk/dial.h"
+#include "asterisk/max_forwards.h"
#include "resource_channels.h"
#include <limits.h>
@@ -1500,6 +1501,67 @@ static void *ari_channel_thread(void *data)
return NULL;
}
+struct ast_datastore_info dialstring_info = {
+ .type = "ARI Dialstring",
+ .destroy = ast_free_ptr,
+};
+
+/*!
+ * \brief Save dialstring onto a channel datastore
+ *
+ * This will later be retrieved when it comes time to actually dial the channel
+ *
+ * \param chan The channel on which to save the dialstring
+ * \param dialstring The dialstring to save
+ * \retval 0 SUCCESS!
+ * \reval -1 Failure :(
+ */
+static int save_dialstring(struct ast_channel *chan, const char *dialstring)
+{
+ struct ast_datastore *datastore;
+
+ datastore = ast_datastore_alloc(&dialstring_info, NULL);
+ if (!datastore) {
+ return -1;
+ }
+
+ datastore->data = ast_strdup(dialstring);
+ if (!datastore->data) {
+ ast_datastore_free(datastore);
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (ast_channel_datastore_add(chan, datastore)) {
+ ast_channel_unlock(chan);
+ ast_datastore_free(datastore);
+ return -1;
+ }
+ ast_channel_unlock(chan);
+
+ return 0;
+}
+
+/*!
+ * \brief Retrieve the dialstring from the channel datastore
+ *
+ * \pre chan is locked
+ * \param chan Channel that was previously created in ARI
+ * \retval NULL Failed to find datastore
+ * \retval non-NULL The dialstring
+ */
+static char *restore_dialstring(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ datastore = ast_channel_datastore_find(chan, &dialstring_info, NULL);
+ if (!datastore) {
+ return NULL;
+ }
+
+ return datastore->data;
+}
+
void ast_ari_channels_create(struct ast_variable *headers,
struct ast_ari_channels_create_args *args,
struct ast_ari_response *response)
@@ -1559,6 +1621,12 @@ void ast_ari_channels_create(struct ast_variable *headers,
return;
}
+ if (save_dialstring(chan_data->chan, stuff)) {
+ ast_ari_response_alloc_failed(response);
+ chan_data_destroy(chan_data);
+ return;
+ }
+
snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan_data->chan));
if (ast_pthread_create_detached(&thread, NULL, ari_channel_thread, chan_data)) {
@@ -1577,8 +1645,8 @@ void ast_ari_channels_dial(struct ast_variable *headers,
{
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel *, caller, NULL, ast_channel_cleanup);
- struct ast_channel *callee;
- struct ast_dial *dial;
+ RAII_VAR(struct ast_channel *, callee, NULL, ast_channel_cleanup);
+ char *dialstring;
control = find_control(response, args->channel_id);
if (control == NULL) {
@@ -1595,43 +1663,64 @@ void ast_ari_channels_dial(struct ast_variable *headers,
return;
}
- if (ast_channel_state(callee) != AST_STATE_DOWN) {
- ast_channel_unref(callee);
+ if (ast_channel_state(callee) != AST_STATE_DOWN
+ && ast_channel_state(callee) != AST_STATE_RESERVED) {
ast_ari_response_error(response, 409, "Conflict",
"Channel is not in the 'Down' state");
return;
}
- dial = ast_dial_create();
- if (!dial) {
- ast_channel_unref(callee);
- ast_ari_response_alloc_failed(response);
- return;
+ /* XXX This is straight up copied from main/dial.c. It's probably good
+ * to separate this to some common method.
+ */
+ if (caller) {
+ ast_channel_lock_both(caller, callee);
+ } else {
+ ast_channel_lock(callee);
}
- if (ast_dial_append_channel(dial, callee) < 0) {
- ast_channel_unref(callee);
- ast_dial_destroy(dial);
- ast_ari_response_alloc_failed(response);
+ dialstring = restore_dialstring(callee);
+ if (!dialstring) {
+ ast_channel_unlock(callee);
+ if (caller) {
+ ast_channel_unlock(caller);
+ }
+ ast_ari_response_error(response, 409, "Conflict",
+ "Dialing a channel not created by ARI");
return;
}
-
- /* From this point, we don't have to unref the callee channel on
- * failure paths because the dial owns the reference to the called
- * channel and will unref the channel for us
+ /* Make a copy of the dialstring just in case some jerk tries to hang up the
+ * channel before we can actually dial
*/
+ dialstring = ast_strdupa(dialstring);
- if (ast_dial_prerun(dial, caller, NULL)) {
- ast_dial_destroy(dial);
- ast_ari_response_alloc_failed(response);
- return;
+ ast_channel_stage_snapshot(callee);
+ if (caller) {
+ ast_channel_inherit_variables(caller, callee);
+ ast_channel_datastore_inherit(caller, callee);
+ ast_max_forwards_decrement(callee);
+
+ /* Copy over callerid information */
+ ast_party_redirecting_copy(ast_channel_redirecting(callee), ast_channel_redirecting(caller));
+
+ ast_channel_dialed(callee)->transit_network_select = ast_channel_dialed(caller)->transit_network_select;
+
+ ast_connected_line_copy_from_caller(ast_channel_connected(callee), ast_channel_caller(caller));
+
+ ast_channel_language_set(callee, ast_channel_language(caller));
+ ast_channel_req_accountcodes(callee, caller, AST_CHANNEL_REQUESTOR_BRIDGE_PEER);
+ if (ast_strlen_zero(ast_channel_musicclass(callee)))
+ ast_channel_musicclass_set(callee, ast_channel_musicclass(caller));
+
+ ast_channel_adsicpe_set(callee, ast_channel_adsicpe(caller));
+ ast_channel_transfercapability_set(callee, ast_channel_transfercapability(caller));
+ ast_channel_unlock(caller);
}
- ast_dial_set_user_data(dial, control);
- ast_dial_set_global_timeout(dial, args->timeout * 1000);
+ ast_channel_stage_snapshot_done(callee);
+ ast_channel_unlock(callee);
- if (stasis_app_control_dial(control, dial)) {
- ast_dial_destroy(dial);
+ if (stasis_app_control_dial(control, dialstring, args->timeout)) {
ast_ari_response_alloc_failed(response);
return;
}