]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_pjsip_refer/session: Calls dropped during transfer
authorKevin Harwell <kharwell@digium.com>
Tue, 13 Jun 2017 19:17:29 +0000 (14:17 -0500)
committerKevin Harwell <kharwell@digium.com>
Tue, 13 Jun 2017 21:05:33 +0000 (16:05 -0500)
When doing an attended transfer it's possible for the transferer, after
receiving an accepted response from Asterisk, to send a BYE to Asterisk,
which can then be processed before Asterisk has time to start and/or
complete the transfer process. This of course causes the transfer to not
complete successfully, thus dropping the call.

This patch makes it so any BYEs received from the transferer, after the REFER,
that initiate a session end are deferred until the transfer is complete. This
allows the channel that would have otherwise been hung up by Asterisk to
remain available throughout the transfer process.

ASTERISK-27053 #close

Change-Id: I43586db79079457d92d71f1fd993be9a3b409d5a

include/asterisk/res_pjsip_session.h
res/res_pjsip_refer.c
res/res_pjsip_session.c
res/res_pjsip_session.exports.in

index 7e65e6d7cf5a381f7f5794494adface0ee823887..c9a41f8e570d735c540430b24650bb1ca9b91884 100644 (file)
@@ -151,6 +151,10 @@ struct ast_sip_session {
        struct ast_sip_aor *aor;
        /*! From header saved at invite creation */
        pjsip_fromto_hdr *saved_from_hdr;
+       /*! Whether the end of the session should be deferred */
+       unsigned int defer_end:1;
+       /*! Session end (remote hangup) requested while termination deferred */
+       unsigned int ended_while_deferred:1;
 };
 
 typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
@@ -475,6 +479,13 @@ int ast_sip_session_defer_termination(struct ast_sip_session *session);
  */
 void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session);
 
+/*!
+ * \brief End the session if it had been previously deferred
+ *
+ * \param session The session to end if it had been deferred
+ */
+void ast_sip_session_end_if_deferred(struct ast_sip_session *session);
+
 /*!
  * \brief Register an SDP handler
  *
index 99295d5f3b07e2e5737db1c95d4bc7c4f0cbe883..06f709e128f99773b8b1d6b696cc77aaece547c8 100644 (file)
@@ -539,6 +539,7 @@ static int refer_attended_task(void *data)
                }
        }
 
+       ast_sip_session_end_if_deferred(attended->transferer);
        if (response != 200) {
                if (!ast_sip_push_task(attended->transferer->serializer,
                        defer_termination_cancel, attended->transferer)) {
@@ -757,6 +758,7 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi
 
                /* 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;
@@ -794,9 +796,12 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi
 
                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;
        }
 }
@@ -841,9 +846,12 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r
 
        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;
 }
 
index 60850f04d34b556980db9a7002dbac520ddcbeb0..0954a85b474761755e39e55c76821d824b37c727 100644 (file)
@@ -1849,6 +1849,8 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response)
        }
 }
 
+static int session_end(void *vsession);
+
 static int session_termination_task(void *data)
 {
        struct ast_sip_session *session = data;
@@ -1883,6 +1885,9 @@ int ast_sip_session_defer_termination(struct ast_sip_session *session)
 
        session->defer_terminate = 1;
 
+       session->defer_end = 1;
+       session->ended_while_deferred = 0;
+
        session->scheduled_termination.id = 0;
        ao2_ref(session, +1);
        session->scheduled_termination.user_data = session;
@@ -1920,6 +1925,7 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session)
                /* Already canceled or timer fired. */
                return;
        }
+
        session->defer_terminate = 0;
 
        if (session->terminate_while_deferred) {
@@ -1931,6 +1937,22 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session)
        sip_session_defer_termination_stop_timer(session);
 }
 
+void ast_sip_session_end_if_deferred(struct ast_sip_session *session)
+{
+       if (!session->defer_end) {
+               return;
+       }
+
+       session->defer_end = 0;
+
+       if (session->ended_while_deferred) {
+               /* Complete the session end started by the remote hangup. */
+               ast_debug(3, "Ending session (%p) after being deferred\n", session);
+               session->ended_while_deferred = 0;
+               session_end(session);
+       }
+}
+
 struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg)
 {
        pjsip_inv_session *inv_session = pjsip_dlg_get_inv_session(dlg);
@@ -2620,6 +2642,12 @@ static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
        }
 
        if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+               if (session->defer_end) {
+                       ast_debug(3, "Deferring session (%p) end\n", session);
+                       session->ended_while_deferred = 1;
+                       return;
+               }
+
                if (ast_sip_push_task(session->serializer, session_end, session)) {
                        /* Do it anyway even though this is not the right thread. */
                        session_end(session);
index a39485e66d2a01f9b7f6493f84963986ca9aa015..fdfc5fb4724615c568bf91985299bc34519a7ec3 100644 (file)
@@ -3,6 +3,7 @@
                LINKER_SYMBOL_PREFIXast_sip_session_terminate;
                LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
                LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;
+               LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;
                LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
                LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
                LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;