Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

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:

Full-call

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:

Refer-To: <sip:carol@asterisk_b.com?Replaces=eOh2AiDxGeoObQv4t.2UiiEusICXdBCf%3Bto-tag%3D660f6ec9-3451-4fd1-95d1-edf8c631fd66%3Bfrom-tag%3DRO-.-1p.8Z1kbbmFzioFRL3ye1-Epi0M>

That's a bit verbose. So let's break it down a little bit. First, there is a SIP URI:

sip:carol@asterisk_b.com

Next, there is a URI header. There are some URL-escaped character sequences in there. If we decode them, we get the following:

Replaces: eOh2AiDxGeoObQv4t.2UiiEusICXdBCf;to-tag=660f6ec9-3451-4fd1-95d1-edf8c631fd66;from-tag=RO-.-1p.8Z1kbbmFzioFRL3ye1-Epi0M

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

Replaces: eOh2AiDxGeoObQv4t.2UiiEusICXdBCf;to-tag=660f6ec9-3451-4fd1-95d1-edf8c631fd66;from-tag=RO-.-1p.8Z1kbbmFzioFRL3ye1-Epi0M

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_replaces extension for purposes other than a SIP remote attended transfer.
  • SIPREFERRINGCONTEXT: This is the dialplan context in which the external_replaces extension was found. This may be useful if your external_replaces extension 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.

Writing your external_replaces extension

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.

Icon

Asterisk dialplan contains functions for manipulating strings. However, there is nothing in the dialplan that is designed to parse a URI. While you could use string manipulation functions for looking at URI details, it is recommended that you do so in an AGI using a programming language that provides URI parsing libraries.

If you decide not to perform the transfer, the simplest thing to do is to call the Hangup() application.

Icon

Calling Hangup() in this situation can have different effects depending on what type of phone Bob is using. Asterisk updates the phone with a notification that the attended transfer failed. It is up to the phone to decide if it wants to try to reinvite itself back into the original conversation with Alice or simply hang up.

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

exten => external_replaces,1,NoOp()
	same => n,Dial(PJSIP/default_outgoing/${SIPREFERTOHDR}

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 sip:carol@asterisk_b.

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.

  • No labels