Attended transfer feature broken

Hi,

We see some strange things with attended transfer feature.
We have multiple servers, some are migrated from FreePBX 15 to FreePBX 17. Some are fresh installs on FreePBX 17. All are Asterisk 20. On all the servers we use the same modules and module versions.

On some migrated machines as well fresh installed machines the attended transfer feature is broken.
On some migrated machines this feature works fine however we have a few machines where it works fine but the initial CallerID is not shown on extension B after transfer completed (but this is another issue).

Problem: External call > extension A answers and is performing an attended transfer to extension B. Extension B answers and extension A is completing the transfer. After this, the call to B is stopped, as well the call on A. However the external caller is still ‘in a call with nowhere’.

Every server has a custom ringback context that we are using as TRANSFER_CONTEXT variable:
TRANSFER_CONTEXT = blindxfer_ringback

[blindxfer_ringback]
exten => _.,1,NOOP(entering user defined context [custom-transfer-ringback] in extensions_custom.conf)
exten => _.,n,ExecIf($[${LEN(${ORIGINAL_PAI})}] > 6?Set(HASH(__SIPHEADERS,P-Asserted-Identity)=${ORIGINAL_PAI}))
exten => _.,n,set(timeoutd=30) ; hard set timeout in seconds
;exten => _.,n,Macro(blkvm-set,) ;block vm
exten => _.,n,dial(local/${EXTEN}@from-internal,${timeoutd})
; BLINDTRANSFER variable is of the form technology/xxxx-yyyyyy we need only xxxx
exten => _.,n,Noop(Returning call to channel: ${BLINDTRANSFER})
; cut everything after the - character
exten => _.,n,set(foo=${CUT(BLINDTRANSFER,-,1)})
; cut everything before the / character
exten => _.,n,set(cb_exten=${CUT(foo,/,2)})
exten => _.,n,Gotoif($["${DIALSTATUS}" = "ANSWER"]?hangup)
exten => _.,n,Set(CALLERID(name)=RB:${CALLERID(name)}) ; prefix CID name with RB to indicate it is a ringback
exten => _.,n,dial(local/${cb_exten}@from-internal)
exten => _.,n(hangup),Hangup()
exten => _*.,1,dial(local/${EXTEN}@from-internal,15)
exten => _*.,n,Hangup()

I collected the logs on both of machines, working and non working. Tried to compare but I do not see the reason.

On the problematic servers (fresh install) I see the call is entering the blindxfer_ringback context (for no reason? on working servers no enter to this context) and then bad things happen :stuck_out_tongue:

Working server:
extension A - 2222
extension B - 1093

Part 1: 2025-01-30 15:39:16] VERBOSE[1693605][C-00002ad8] pbx.c: Executing [INBOUND_ROUT - Pastebin.com
Part 2: [2025-01-30 15:39:24] VERBOSE[1693632][C-00002ada] pbx.c: Executing [1093@from-i - Pastebin.com

Non working server:
extension A - 1300
extension B - 1301

Part 1: [2025-01-30 16:54:57] VERBOSE[2862706][C-000000f4] pbx.c: Executing [INBOUND_ROU - Pastebin.com
Part 2: [2025-01-30 16:54:57] VERBOSE[2862706][C-000000f4] pbx.c: Executing [INBOUND_ROU - Pastebin.com

Maybe someone has any idea what’s going on here? And why the call is entering blindxfer_ringback? Or maybe someone has any idea how to check further?

Thanks!!

Well there’s a couple problems I see here. First, in the working system your custom transfer context is not being used. It never hits it. Second, in the calls that fail it is due to you sending the call to some other non-existent context when it’s processing the dial-trunk part.

[2025-01-30 16:55:10] VERBOSE[2862764][C-000000f4] pbx.c: Executing [s@macro-dialout-trunk:28] Gosub("Local/external_replaces@from-internal-00000096;2", "trunk-dial-with-exten,external_replaces,1()") in new stack

[2025-01-30 16:55:10] ERROR[2862764][C-000000f4] app_stack.c: Gosub attempted to reach non-existent destination 'trunk-dial-with-exten,external_replaces,1' from 'macro-dialout-trunk,s,28'

Because you sent the call to a bad non-existent destination it causes the call to hangup because it can’t process any further.

Yes but the issue is there is no reference for ‘external_replaces’ anywhere in the dialplans… nothing found in all files across /etc/asterisk.

Also the question is why in the working system the custom transfer context is not being used and in the other one is being used. The actions taken for the transfer as well the setups (inbound route > extension) are identical…

I would have to see the differences between the raw dialplan and how you are declaring the TRANSFER_EXTENSION, can’t really tell you why without looking the dialplan more.

Your entire pastebin is filled with external_replaces because it looks like that how you redacted everything. So really that is calling on whatever extension is being used. If you didn’t redact/replace things with external_replaces then you’re sending the call to external_replaces as the extension at some point.

Only the IPs are hidden in my logs :slight_smile:
external_replaces is not a placeholder, it is how I see it in the logs… and that’s strange, no idea yet where it comes from as none of the config files contains such string.

Further I checked the PCAP of the calls on both working and not working machines.
On the working machine, I see an INVITE to the extension B coming from extension A. But on the non-working server I only see one INVITE, coming from EXTERNAL_NUMBER to extension A so there is no INVITE to extension B during the transfer action.

Trying to compare both pcap’s/invites now…

The external_replaces extension is invoked if an attended transfer request is received but Asterisk has no knowledge of the call leg[1], such that the transfer can not be handled internally. Actually seeing the SIP traffic for ALL involved call legs would show if this is actually the case.

[1] res_pjsip Remote Attended Transfers - Asterisk Documentation

Can you explain where external_replaces comes from? If you didn’t change it then it means you must of called on it some how or something sent it as the extension.

Here’s the call hitting the TRANSFER_CONTEXT

[2025-01-30 16:55:10] VERBOSE[2862706][C-000000f4] pbx.c: Executing [external_replaces@blindxfer_ringback:1] NoOp("PJSIP/kamailio_sbc04_pjsip-00000206", "entering user defined context [custom-transfer-ringback] in extensions_custom.conf") in new stack
[2025-01-30 16:55:10] VERBOSE[2862706][C-000000f4] pbx.c: Executing [external_replaces@blindxfer_ringback:2] ExecIf("PJSIP/kamailio_sbc04_pjsip-00000206", "35 > 6?Set(HASH(__SIPHEADERS,P-Asserted-Identity)=<sip:0EXTERNAL_NUMBER@SBC_PUBLIC_IP:5060>)") in new stack
[2025-01-30 16:55:10] VERBOSE[2862706][C-000000f4] pbx.c: Executing [external_replaces@blindxfer_ringback:3] Set("PJSIP/kamailio_sbc04_pjsip-00000206", "timeoutd=30") in new stack
[2025-01-30 16:55:10] VERBOSE[2862706][C-000000f4] pbx.c: Executing [external_replaces@blindxfer_ringback:4] Dial("PJSIP/kamailio_sbc04_pjsip-00000206", "local/external_replaces@from-internal,30") in new stack
[2025-01-30 16:55:10] VERBOSE[2862706][C-000000f4] app_dial.c: Called local/external_replaces@from-internal

Since you have exten => _.,1,NoOp() as you pattern match it matches anything and everything.

Here is what is happening, something is initiating the transfer sending external_replaces as the digits/user/extension to transfer to. Since your TRANSFER_CONTEXT accepts everything as a matching pattern, it will process it as you see in the above output. You are sending the call back into the internal dialplan which means something else is also matching everything otherwise it would have failed with the same error I showed earlier (sending to a non-existent extension,context,priority)

This indicates you have an Outbound Pattern of . which matches everything. Calling on the macro-user-callerid(LIMIT,EXTERNAL) is the first line of an outbound route context.

[2025-01-30 16:55:10] VERBOSE[2862764][C-000000f4] pbx.c: Executing [external_replaces@from-internal:1] Gosub("Local/external_replaces@from-internal-00000096;2", "macro-user-callerid,s,1(LIMIT,EXTERNAL)") in new stack

So your system is trying to send external_replaces out through the Outbound Routes and trunk which is failing.

All that answers where it comes from. Thanks.

This is what I see in sngrep for a performed transfer try on a problematic server:

0495XX - external number
1300 - extension A
1301 - extension B

The 3rd INVITE seems to be from the ringback context.

The 2nd INVITE looks like this:

This is the REFER:

There is no difference in these between a working server and non working server…

Transfer actions:

  • 1300 ringing and answers the call
  • 1300 (Yealink T42S) presses Transfer and starts a call to 1301 (here is NO separate INVITE)
  • 1301 answers the call from 1300
  • 1300 presses Transfer to perform the transfer to 1301
  • Call is gone to nowhere

I can’t do anything with that. I need all of the SIP signalling for every involved call, because the REFER says “I want you to attended transfer this call leg to this call leg”, and so all call legs have to be examined including the Call-ID, To Tag, and From Tag. It has to all be correlated back to see if the REFER really doesn’t match an active call in Asterisk.

I understand but I cannot provide these data in public :slight_smile:
How can we proceed in this case?

If you can’t, then you’ll need to learn some SIP including how Attended Transfer works and do the reading and correlation yourself.

It’s not about learning some SIP.
It’s about exposing/sharing private data such as IP addresses and private telephone numbers on this forum.

If you use the Asterisk method which just outputs to console, then you could copy and paste and obfuscate.

1 Like

Note that, when obfuscating, it is important that the same string is always obfuscated in the same, distinct way.

(In practice from tags, to tags, branch tags, and any part of a call ID that doesn’t follow an “@”, is unlikely to contain any sensitive information.)

Although probably not an issue here, IP addresses should, additionally, only be obfuscated in ways that retain the distinction between private, public, and shared address ranges.

To decide whether a transfer needs to be handled externally, Asterisk will match the Replaces parameter against the combination of call-ID, from tag, and to tag, for all the call legs it is currently handling, other than that on which it was requested (which would be an error), and, if the complete combination doesn’t match, will need to do an external transfer.

@jcolp @david55 as requested: <--- Transmitting SIP request (467 bytes) to TCP:111.111.111.111:12455 --- - Pastebin.com

As I understand these correctly, somehow Refer-To is trying to send the call to an non existing path…

These are taken on a fresh installed server. There were no other calls active, only 2 extensions and a few trunks are registered/online.

Correct. There is no call in the provided log matching the Refer-To details at all. The call doesn’t appear to exist within Asterisk. Why that is, I don’t know, or even if it should be. After this Asterisk sends a SIP INVITE with Replaces with the Refer-To details to elsewhere which succeeds and then eventually hangs up.

Very strange.
Where could the Refer-To come from? Who is setting this? The client? Asterisk dialplan?

The client. It produces it in the REFER it sends.