Skip to content
Snippets Groups Projects
Commit 895dbe53 authored by Joshua Colp's avatar Joshua Colp Committed by Gerrit Code Review
Browse files

Merge "chan_sip: Fix early call pickup caused deadlock." into 11

parents 6d911576 0e6d3f5e
No related branches found
No related tags found
No related merge requests found
......@@ -25121,6 +25121,39 @@ static int sip_t38_abort(const void *data)
return 0;
}
 
/*! \brief Checks state of the p->refer->refer_call state: can it be picked up?
*
* It will terminate the call if it cannot be picked up; e.g. because it
* was gone, or because it wasn't in a ringing state.
*
* \return 1 if terminated, 0 if ok
*/
static int terminate_on_invalid_replaces_state(struct sip_pvt *p, struct sip_request *req, const char *replace_id)
{
if (p->refer->refer_call == p) {
ast_log(LOG_NOTICE, "INVITE with replaces into its own call id (%s == %s)!\n", replace_id, p->callid);
transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */
} else if (!p->refer->refer_call->owner) {
/* Oops, someting wrong anyway, no owner, no call */
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
/* Check for better return code */
transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replace)", req);
} else if (ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RINGING &&
ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RING &&
ast_channel_state(p->refer->refer_call->owner) != AST_STATE_UP) {
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
transmit_response_reliable(p, "603 Declined (Replaces)", req);
} else {
/* Ok */
return 0;
}
/* Terminated */
return 1;
}
/*!
* \brief bare-bones support for SIP UPDATE
*
......@@ -25322,6 +25355,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
int gotdest;
const char *p_replaces;
char *replace_id = NULL;
int magic_call_id = 0;
int refer_locked = 0;
const char *required;
unsigned int required_profile = 0;
......@@ -25553,40 +25587,27 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
ast_channel_unlock(subscription->owner);
}
subscription = dialog_unref(subscription, "unref dialog subscription");
magic_call_id = !ast_strlen_zero(pickup.exten);
}
}
 
/* This locks both refer_call pvt and refer_call pvt's owner!!!*/
if (!error && ast_strlen_zero(pickup.exten) && (p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag)) == NULL) {
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
error = 1;
} else {
refer_locked = 1;
}
/* The matched call is the call from the transferer to Asterisk .
We want to bridge the bridged part of the call to the
incoming invite, thus taking over the refered call */
if (p->refer->refer_call == p) {
ast_log(LOG_NOTICE, "INVITE with replaces into it's own call id (%s == %s)!\n", replace_id, p->callid);
transmit_response_reliable(p, "400 Bad request", req); /* The best way to not not accept the transfer */
error = 1;
}
if (!error && ast_strlen_zero(pickup.exten) && !p->refer->refer_call->owner) {
/* Oops, someting wrong anyway, no owner, no call */
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existing call id (%s)!\n", replace_id);
/* Check for better return code */
transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replace)", req);
error = 1;
}
if (!error && ast_strlen_zero(pickup.exten) && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RINGING && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_RING && ast_channel_state(p->refer->refer_call->owner) != AST_STATE_UP) {
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-ringing or active call id (%s)!\n", replace_id);
transmit_response_reliable(p, "603 Declined (Replaces)", req);
error = 1;
if (!error) {
if (magic_call_id) {
;
/* This locks both refer_call pvt and refer_call pvt's owner!!!*/
} else if ((p->refer->refer_call = get_sip_pvt_byid_locked(replace_id, totag, fromtag))) {
/* The matched call is the call from the transferer to Asterisk .
We want to bridge the bridged part of the call to the
incoming invite, thus taking over the refered call */
refer_locked = 1;
if (terminate_on_invalid_replaces_state(p, req, replace_id)) {
error = 1;
}
} else {
ast_log(LOG_NOTICE, "Supervised transfer attempted to replace non-existent call id (%s)!\n", replace_id);
transmit_response_reliable(p, "481 Call Leg Does Not Exist (Replaces)", req);
error = 1;
}
}
 
if (error) { /* Give up this dialog */
......@@ -25828,11 +25849,52 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
goto request_invite_cleanup;
}
 
/* We cannot call sip_new with any channel locks
* held. Unlock the referred channel if locked. */
if (refer_locked) {
sip_pvt_unlock(p->refer->refer_call);
if (p->refer->refer_call->owner) {
ast_channel_unlock(p->refer->refer_call->owner);
}
}
/* First invitation - create the channel. Allocation
* failures are handled below. */
c = sip_new(p, AST_STATE_DOWN, S_OR(p->peername, NULL), NULL, p->logger_callid);
 
/* Reacquire the lock on the referred call. */
if (refer_locked) {
/* Reuse deadlock avoid pattern found in
* get_sip_pvt_byid_locked. */
sip_pvt_lock(p->refer->refer_call);
while (p->refer->refer_call->owner && ast_channel_trylock(p->refer->refer_call->owner)) {
sip_pvt_unlock(p->refer->refer_call);
usleep(1);
sip_pvt_lock(p->refer->refer_call);
}
/* And now, check once more that the call is still
* still available. Yuck. Bail out if it isn't. */
if (terminate_on_invalid_replaces_state(p, req, replace_id)) {
/* Free the referred call: it's not ours anymore. */
sip_pvt_unlock(p->refer->refer_call);
if (p->refer->refer_call->owner) {
ast_channel_unlock(p->refer->refer_call->owner);
}
p->refer->refer_call = dialog_unref(p->refer->refer_call, "unref dialog p->refer->refer_call");
refer_locked = 0;
/* Kill the channel we just created. */
sip_pvt_unlock(p);
ast_hangup(c);
sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */
/* Mark the call as failed. */
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
p->invitestate = INV_COMPLETED;
res = INV_REQ_ERROR;
goto request_invite_cleanup;
}
}
if (cc_recall_core_id != -1) {
ast_setup_cc_recall_datastore(c, cc_recall_core_id);
ast_cc_agent_set_interfaces_chanvar(c);
......@@ -25891,7 +25953,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
p->lastinvite = seqno;
 
if (c && replace_id) { /* Attended transfer or call pickup - we're the target */
if (!ast_strlen_zero(pickup.exten)) {
if (magic_call_id) {
append_history(p, "Xfer", "INVITE/Replace received");
 
/* Let the caller know we're giving it a shot */
......@@ -26062,7 +26124,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 
request_invite_cleanup:
 
if (refer_locked && p->refer && p->refer->refer_call) {
if (refer_locked) {
sip_pvt_unlock(p->refer->refer_call);
if (p->refer->refer_call->owner) {
ast_channel_unlock(p->refer->refer_call->owner);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment