What is a remote SIP transfer?
Let's imagine a scenario where Alice places a call to Bob, and then Bob performs an attended transfer to Carol. In this scenario, Alice is registered to Asterisk instance A (asterisk_a.com), and Bob is registered to Asterisk instance B (asterisk_b.com). The key to this scenario is that Asterisk A has been explicitly configured to be able to call Bob directly, despite the fact that Bob does not register to Asterisk A.
Initially, Alice places a call to Bob through Alice's Asterisk instance:
Now, Bob wants to perform an attended transfer to Carol, so he places a call to Carol:
As you can see, Bob has simultaneous calls through two separate Asterisk servers. Now when Bob performs the attended transfer, what happens? Bob will send a SIP REFER request to Asterisk A to indicate that he wants to transfer Alice. The REFER request has a Refer-To header that specifies details of the transfer. An example Refer-To header looks something like the following:
That's a bit verbose. So let's break it down a little bit. First, there is a SIP URI:
Next, there is a URI header. There are some URL-escaped character sequences in there. If we decode them, we get the following:
If we break down the parts of this, what the Replaces section tells us is that the REFER request is saying that the SIP dialog with Call-ID "eOh2AiDxGeoObQv4t.2UiiEusICXdBCf", to-tag "660f6ec9-3451-4fd1-95d1-edf8c631fd66" and from-tag "RO-.-1p.8Z1kbbmFzioFRL3ye1-Epi0M" needs to be replaced by the party that Bob is talking to.
Asterisk has built into it a bit of an optimization to avoid unnecessary SIP traffic by looking up the dialog referred to by the Replaces header. If the dialog is found in the Asterisk system, then Asterisk simply performs a local attended transfer. This involves internal operations such as moving a channel from one bridge to another, or creating a local channel to link two bridges together.
However, in this case, the dialog referred to by Bob's Replaces header is not on Asterisk A. It's on Asterisk B. So Asterisk A cannot perform a local attended transfer. This is where a remote attended transfer is required.
From a SIP point of view
If you look at specifications for SIP attended transfers, remote attended transfers are considered to be the normal case since SIP user agents involved in a call are not typically colocated. When a SIP user agent receives a REFER header, the user agent is supposed to send an INVITE to the URI in the Refer-To header. The INVITE should have a Replaces header that has the same contents as the Replaces URI header from the REFER request.
In the scenario above, when Asterisk A receives the REFER request from Bob, Asterisk A should respond by sending an INVITE to
sip:carol@asterisk_b.com and add
This way, when Asterisk B receives the INVITE, it knows to replace the referred-to dialog with the incoming INVITE. By doing this, the final picture looks something like the following:
How Asterisk handles this
Asterisk will rarely ever directly place outbound calls without going through the dialplan. When Asterisk A receives the REFER request from Bob, Asterisk does not immediately send an INVITE with Replaces header to Asterisk B. Instead, Asterisk A looks for a specifically-named extension called "external_replaces". Asterisk searches for this extension in the context specified by the
TRANSFER_CONTEXT channel variable, if present on Bob's channel, or in the configured context for Bob's endpoint otherwise. Once in the dialplan, it is the job of the dialplan writer to determine whether to complete the transfer or not.
In the external_replaces extension, you will have access to the following channel variables:
SIPTRANSFER: Set to "yes" to indicate that a SIP transfer is happening. This is only useful if, for whatever reason, you are using your the
external_replacesextension for purposes other than a SIP remote attended transfer.
SIPREFERRINGCONTEXT: This is the dialplan context in which the
external_replacesextension was found. This may be useful if your
external_replacesextension calls into subroutines or goes to other contexts.
SIPREFERTOHDR: This is the SIP URI in the Refer-To header in the REFER request sent by the transferring party.
The big reason why Asterisk calls into the dialplan instead of automatically sending an INVITE to the Refer-To URI is for security purposes. If Asterisk automatically sent an INVITE out without going through the dialplan, there are chances that transfers could be used to place calls to destinations that would result in large charges.
Now that the theory has been presented, you'll need to write your
external_replaces extension. Well actually, first, you will need to determine whether you want to allow remote attended transfers at all. If you do not, then your best bet is to not write an
external_replaces extension at all.
If you do want to write an
external_replaces externsion, the first thing you want to do is determine if you want to perform the remote attended transfer.
SIPREFERTOHDR, and values provided by the
CHANNEL() dialplan function. For instance, you might use
CHANNEL(endpoint) to see which PJSIP endpoint is performing the transfer. You can inspect
SIPREFERTOHDR to determine if the transfer is destined for a trusted domain.
If you decide not to perform the transfer, the simplest thing to do is to call the
If you decide to perform the transfer, the most straightforward way to do this is with the
Dial() application. Here is an example of how one might complete the transfer
Let's examine that
Dial() more closely. First, we're dialing using PJSIP, which is pretty obvious. Next, we have the endpoint name. The endpoint name could be any configured endpoint you want to use to make this call. Remember that endpoint settings are things such as what codecs to use, what user name to place in the from header, etc. By default, if you just dial
PJSIP/my_endpoint, Asterisk looks at my_endpoint's configured
aors to determine what location to send the outgoing call to. However, you can always override this default and specify a URI to send the call to instead. This is what is being done in this
Dial() statement. We're dialing using settings for an endpoint called "default_outgoing", presumably used as a default endpoint for outgoing calls. We're sending the call out to the URI specified by
SIPREFERTOHDR though. Using the scenario on this page, the
Dial() command would route the call to
Avoiding Remote Attended Transfers
In Asterisk, remote attended transfers are sometimes necessary, but avoiding them is typically a good idea. The biggest reason is the security concerns of allowing users to make calls to untrusted domains.
The easiest but most severe way to prevent remote attended transfers is to set
allow_transfer to no for all endpoints. The problem with doing this is that it also prevents local attended transfers and blind transfers.
A better way is to configure your Asterisk server to only call phones that are directly registered to it, and trusted SIP servers. In the scenario we have been inspecting before, the remote attended transfer could have been avoided by having Asterisk A call Bob through Asterisk B instead of dialing Bob directly. By receiving the initial call through Asterisk B, Bob will send his REFER request to Asterisk B instead of Asterisk A. Asterisk B will be able to perform a local attended transfer to Carol as a result.