How to define trunk failover / continue behavior? 603 should not continue

I’m in the process of configuring Transnexus’/ClearIP’s inline proxy for STIR/SHAKEN purposes. The problem I’m currently running into is that outbound calls that should get blocked (for robocall mitigation or blacklist reasons) are failing over to the next trunk in my outbound route. Sometimes failover is desired, say if there is a timeout at the proxy or the proxy couldn’t reach my providers etc, in which case failing over and sending the call directly to my provider is desired. But if the proxy determines that a number should not be called then they reply with a 603 and expect the call to terminate.

My Setup
Fully updated on FreePBX 17. Asterisk version: 21.3.1

Outbound Route order:
Trunk 1: Proxy. Continue if busy = no
Trunk 2: Sip Provider: Continue if busy = no

I’ve spent a while digging into how this trunk failover logic works, and it seems relatively simple. macro-dialout-trunk checks if continue if busy is true, if not it goes to s-${DIALSTATUS}

exten => s,n,GotoIf($["${ARG4}" = "on"]?continue,1:s-${DIALSTATUS},1)

Here’s the logs I’m seeing:

Sip response from proxy:

SIP/2.0 603 Decline
Via: SIP/2.0/UDPxxxxxxxxxxxxx:5160;received=xxxxxxxxxxxx;rport=5160;branch=z9hG4bKP
jf69c2338-5f50-47d4-a4ad-4c2112968df3
From: "xxxxx" <sip:xxxxxxxx@xxxxxxxxxx>;tag=34c8b7fd-1d1b-4951-8c81-d0d9d0d8144f
To: <sip:xxxxxxxxxx@xxxxxxxxxx>;tag=f32185473df1df7d01ec7e8d5656d92c-fe4a94ad
Call-ID: c1b7bd47-d23a-4e50-8c6d-59a585615b7b
CSeq: 4719 INVITE
Reason: SIP;cause=603;text="blacklisted"
Content-Length: 0

Pertinent part of Asterisk logs (let me know if more of the surrounding logs are required)

  == Spawn extension (from-pstn, 1xxxxxxxxxx, 1) exited non-zero on 'PJSIP/ClearIP-Proxy-0000001d'
    -- PJSIP/ClearIP-Proxy-0000001d Internal Gosub(func-apply-sipheaders,s,1(7)) complete GOSUB_RETVAL=
    -- Called PJSIP/1xxxxxxxxxx@ClearIP-Proxy
    -- PJSIP/ClearIP-Proxy-0000001d Internal Gosub(app-missedcall-hangup,ClearIP,1) start
    -- Executing [ClearIP@app-missedcall-hangup:1] NoOp("PJSIP/ClearIP-Proxy-0000001d", "Dialed: ClearIP") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:2] NoOp("PJSIP/ClearIP-Proxy-0000001d", "Caller: 902") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:3] GotoIf("PJSIP/ClearIP-Proxy-0000001d", "0?exit") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:4] Set("PJSIP/ClearIP-Proxy-0000001d", "EXTENNUM=ClearIP") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:5] Set("PJSIP/ClearIP-Proxy-0000001d", "FEXTENNUM=ClearIP") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:6] GotoIf("PJSIP/ClearIP-Proxy-0000001d", "0?exit") in new stack
    -- Executing [ClearIP@app-missedcall-hangup:7] AGI("PJSIP/ClearIP-Proxy-0000001d", "agi://127.0.0.1/missedcallnotify.php,ClearIP,,ClearIP,0,,PJSIP/ClearIP-Proxy-0000001d,,,,") in new stack
    -- <PJSIP/ClearIP-Proxy-0000001d>AGI Script agi://127.0.0.1/missedcallnotify.php completed, returning 0
    -- Executing [ClearIP@app-missedcall-hangup:8] Return("PJSIP/ClearIP-Proxy-0000001d", "") in new stack
  == Spawn extension (from-pstn, 1xxxxxxxxxx, 1) exited non-zero on 'PJSIP/ClearIP-Proxy-0000001d'
    -- PJSIP/ClearIP-Proxy-0000001d Internal Gosub(app-missedcall-hangup,ClearIP,1) complete GOSUB_RETVAL=
  == Everyone is busy/congested at this time (1:0/0/1)
    -- Executing [1xxxxxxxxxx@trunk-dial-with-exten:2] Return("PJSIP/902-0000001c", "") in new stack
    -- Executing [s@macro-dialout-trunk:37] NoOp("PJSIP/902-0000001c", "Dial failed for some reason with DIALSTATUS = CHANUNAVAIL and HANGUPCAUSE = 21") in new stack
    -- Executing [s@macro-dialout-trunk:38] GotoIf("PJSIP/902-0000001c", "0?continue,1:s-CHANUNAVAIL,1") in new stack
    -- Goto (macro-dialout-trunk,s-CHANUNAVAIL,1)
    -- Executing [s-CHANUNAVAIL@macro-dialout-trunk:1] Set("PJSIP/902-0000001c", "RC=21") in new stack
    -- Executing [s-CHANUNAVAIL@macro-dialout-trunk:2] Goto("PJSIP/902-0000001c", "21,1") in new stack
    -- Goto (macro-dialout-trunk,21,1)
    -- Executing [21@macro-dialout-trunk:1] Goto("PJSIP/902-0000001c", "continue,1") in new stack
    -- Goto (macro-dialout-trunk,continue,1)
    -- Executing [continue@macro-dialout-trunk:1] NoOp("PJSIP/902-0000001c", "TRUNK Dial failed due to CHANUNAVAIL HANGUPCAUSE: 21 - failing through to other trunks") in new stack

For easy reference, the pertinent parts of [macro-dialout-trunk]:

exten => s,n,Noop(Dial failed for some reason with DIALSTATUS = ${DIALSTATUS} and HANGUPCAUSE = ${HANGUPCAUSE})

exten => s,n,GotoIf($["${ARG4}" = "on"]?continue,1:s-${DIALSTATUS},1)

exten => s,n(customtrunk),Set(pre_num=${CUT(OUT_${DIAL_TRUNK},$,1)})

exten => s,n,Set(the_num=${CUT(OUT_${DIAL_TRUNK},$,2)})

exten => s,n,Set(post_num=${CUT(OUT_${DIAL_TRUNK},$,3)})

exten => s,n,GotoIf($["${the_num}" = "OUTNUM"]?outnum:skipoutnum)

exten => s,n(outnum),Set(the_num=${OUTNUM})

exten => s,n(skipoutnum),Dial(${pre_num:4}${the_num}${post_num},${TRUNK_RING_TIMER},${DIAL_TRUNK_OPTIONS})

exten => s,n,Noop(Dial failed for some reason with DIALSTATUS = ${DIALSTATUS} and HANGUPCAUSE = ${HANGUPCAUSE})

exten => s,n,GotoIf($["${ARG4}" = "on"]?continue,1:s-${DIALSTATUS},1)

exten => s,n(chanfull),Noop(max channels used up)

exten => s-BUSY,1,Noop(Dial failed due to trunk reporting BUSY - giving up)

exten => s-BUSY,n,Playtones(busy)

exten => s-BUSY,n,Busy(20)

exten => s-ANSWER,1,Noop(Call successfully answered - Hanging up now)

exten => s-ANSWER,n,Gosub(macro-hangupcall,s,1())

exten => s-NOANSWER,1,Noop(Dial failed due to trunk reporting NOANSWER - giving up)

exten => s-NOANSWER,n,Progress

exten => s-NOANSWER,n,Playback(number-not-answering,noanswer)

exten => s-NOANSWER,n,Congestion(20)

exten => s-INVALIDNMBR,1,Noop(Dial failed due to trunk reporting Address Incomplete - giving up)

exten => s-INVALIDNMBR,n,Progress

exten => s-INVALIDNMBR,n,Playback(ss-noservice,noanswer)

exten => s-INVALIDNMBR,n,Busy(20)

exten => s-CHANGED,1,Noop(Dial failed due to trunk reporting Number Changed - giving up)

exten => s-CHANGED,n,Playtones(busy)

exten => s-CHANGED,n,Busy(20)

exten => _s-.,1,Set(RC=${IF($[${ISNULL(${HANGUPCAUSE})}]?0:${HANGUPCAUSE})})

exten => _s-.,n,Goto(${RC},1)

exten => 17,1,Goto(s-BUSY,1)

exten => 18,1,Goto(s-NOANSWER,1)

exten => 22,1,Goto(s-CHANGED,1)

exten => 23,1,Goto(s-CHANGED,1)

exten => 1,1,Goto(s-INVALIDNMBR,1)

exten => 28,1,Goto(s-INVALIDNMBR,1)

exten => _X,1,Goto(continue,1)

exten => _X.,1,Goto(continue,1)

exten => continue,1(noreport),Noop(TRUNK Dial failed due to ${DIALSTATUS} HANGUPCAUSE: ${HANGUPCAUSE} - failing through to other trunks)

exten => continue,n,ExecIf($["${AMPUSER}"!="" ]?Set(CALLERID(number)=${AMPUSER}))

exten => continue,n,Return()

I’m having trouble finding where DIALSTATUS is set. Clearly in my case it is equal to CHANUNAVAIL when determine if the call should continue. I know that hangup cause 21 correlates to sip responses: 401, 403, 407, and 603. So I’m suspecting this is the expected behavior. However, asterisk seems to also interpret the 603 as busy, based on Everyone is busy/congested at this time (1:0/0/1) . And, correct me if I’m wrong, if the DIALSTATUS were instead set to BUSY, I would be dropped into ‘s-BUSY’ and my call would fail (successfully).

I believe I have some flexibility on the text or codes that are returned from the proxy. Maybe that’s where I need to make a change. However, what should it be changed to? Where can I see the logic that determines the DIALSTATUS? I have looked but haven’t been able to find it.

However, TransNexus certainly seems to think that ‘603+’ (where the plus is the extra info in the reason header, I think…) is the correct error code here. So, I also think it might be good to modify or override the logic on my side to instead interpret 603 codes as busy and not call advance.

Any thoughts or suggestions would be appreciated.

Shameless bump here. I really just want to know where to look for the logic that sets DIALSTATUS and a way to override it.