Dialplan ignored / s@default triggered

I am working with a 3CX to Asterisk trunk, expecting calls to be forwarded to extension 9876 when extension 9877 is dialed from 9874. I have tried many dial plans, with the one below being the last, but s@default is always processed instead. I intend 9876 to get the call and play back the demo-greeting.

extensions.conf:

[general]
static=yes
writeprotect=no
clearglobalvars=no

[default]
; Handle calls for 9877 and forward to 9876
exten => 9877,1,NoOp(Forwarding calls for 9877 to 9876)
same => n,Dial(PJSIP/9876@3cx-trunk,30) ; Replace ‘3cx-trunk’ with your trunk name
same => n,Hangup()

; Optional: Catch-all for other calls in the default context
exten => _.,1,NoOp(Catch-all for unmatched extensions)
same => n,Playback(demo-congrats) ; Optional: Play the demo greeting
same => n,Hangup()

pjsip.conf:
[global]
rtp_symmetric=yes ; Enable symmetric RTP
force_rport=yes ; Ensure responses go back to the source port
rewrite_contact=yes ; Rewrite the Contact header for NAT traversal

[transport-udp-nat]
type=transport
protocol=udp
bind=0.0.0.0
local_net=10.10.0.0/23
external_media_address=AsteriskPublicIP
external_signaling_address=AsteriskPublicIP
[3cx-trunk]
type=endpoint
transport=transport-udp-nat
context=default ; Use the same context as in extensions.conf
disallow=all
allow=ulaw
aors=3cx-trunk
outbound_auth=3cx-auth
dtmf_mode=rfc4733
[3cx-auth]
type=auth
username=user
password=pass
[3cx-trunk]
type=aor
contact=sip:3CXPublicIP:5060
qualify_frequency=30

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0

[9877]
type=endpoint
context=default
disallow=all
allow=ulaw
aors=9877
auth=9877-auth

[auth9877]
type=auth
auth_type=userpass
username=user ; Use the Authentication ID from 3CX
password=pass; Use the Authentication Password from 3CX

[9877]
type=aor
contact=sip:9877@3CXfqdn:5060
max_contacts=1

[registration_9877]
type=registration
transport=transport-udp-nat
outbound_auth=auth9877
server_uri=sip:3CXfqdn:5060
client_uri=sip:9877@3CXfqdn
retry_interval=30

[3cx-server]
type=identify
endpoint=3cx-trunk
match=3CXfqdn ; Match the DNS name instead of the IP

Log:

Connected to Asterisk 20.6.0~dfsg+~cs6.13.40431414-2build5 currently running on SC3CX-Asterisk (pid = 8768)
– Executing [s@default:1] wait(“PJSIP/3cx-trunk-00000001”, “1”)
– Executing [s@default:1] answer(“PJSIP/3cx-trunk-00000001”, “”)
> 0x7766102c0370 – Strict RTP learning after remote address set to: 3CXPublicIP:9282
– Digit timeout set to 5.000
– Response timeout set to 10.000
– Executing [s@default:1] background(“PJSIP/3cx-trunk-00000001”, “demo-congrats”)
– <PJSIP/3cx-trunk-00000001> Playing ‘demo-congrats.slin’ (language ‘en’)
> 0x7766102c0370 – Strict RTP switching to RTP target address 3CXPublicIP:9282 as source
== Spawn extension (default, s, 1) exited non-zero on 'PJSIP/3cx-trunk-0000000

Thoughts?

What does the SIP traffic show? (pjsip set logger on). The destination in the dialplan is based on what was requested by the remote side. Either it did not specify a user portion in the Request URI, or it used what the registration provided - “s”.

<— Received SIP request (1003 bytes) from UDP:3CXPublicIP:5060 —>
INVITE sip:s@AsteriskPublicIP:5060 SIP/2.0
Via: SIP/2.0/UDP 10.201.9.63:5060;branch=z9hG4bK-524287-1—1677a86fd9342d50;rport
Max-Forwards: 70
Contact: sip:9877@3CXPublicIP:5060
To: sip:9877@3CXfqdn
From: "Raymond Norton"sip:9874@3CXfqdn;tag=7db0a514
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
CSeq: 1 INVITE
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REGISTER, SUBSCRIBE, NOTIFY, REFER, INFO, MESSAGE, UPDATE
Content-Type: application/sdp
Supported: replaces, timer
Content-Length: 492

v=0
o=3cxPS 31125600932462592 28834360752865281 IN IP4 3CXPublicIP
s=3cxPS Audio call
c=IN IP4 3CXPublicIP
b=AS:117
t=0 0
a=X-nat:0
m=audio 9292 RTP/AVP 18 9 0 8 120 96
c=IN IP4 3CXPublicIP
b=TIAS:96000
b=AS:117
a=sendrecv
a=rtpmap:18 G729/8000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:120 opus/48000/2
a=fmtp:120 useinbandfec=1
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-15
a=rtcp:9293
m=video 0 RTP/AVP 31
c=IN IP4 127.0.0.1

<— Transmitting SIP response (359 bytes) to UDP:3CXPublicIP:5060 —>
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 10.201.9.63:5060;rport=5060;received=3CXPublicIP;branch=z9hG4bK-524287-1—1677a86fd9342d50
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
From: “Raymond Norton” sip:9874@3CXfqdn;tag=7db0a514
To: sip:9877@3CXfqdn
CSeq: 1 INVITE
Server: Asterisk PBX 20.6.0~dfsg+~cs6.13.40431414-2build5
Content-Length: 0

-- Executing [s@default:1] wait("PJSIP/3cx-trunk-00000002", "1")

<— Transmitting SIP request (453 bytes) to UDP:3CXPublicIP:5060 —>
OPTIONS sip:3CXPublicIP:5060 SIP/2.0
Via: SIP/2.0/UDP AsteriskPublicIP:5060;rport;branch=z9hG4bKPj7ee9e84c-3798-4531-8960-465dff46bcbb
From: sip:3cx-trunk@10.10.0.61;tag=29c0cdd7-916c-477f-bd79-825033d2b305
To: sip:3CXPublicIP
Contact: sip:3cx-trunk@AsteriskPublicIP:5060
Call-ID: 5df21c71-9119-4de0-a537-0467410922fa
CSeq: 6768 OPTIONS
Max-Forwards: 70
User-Agent: Asterisk PBX 20.6.0~dfsg+~cs6.13.40431414-2build5
Content-Length: 0

<— Received SIP response (556 bytes) from UDP:3CXPublicIP:5060 —>
SIP/2.0 200 OK
Via: SIP/2.0/UDP AsteriskPublicIP:5060;rport=5060;branch=z9hG4bKPj7ee9e84c-3798-4531-8960-465dff46bcbb
To: sip:3CXPublicIP;tag=cf1d522f
From: sip:3cx-trunk@10.10.0.61;tag=29c0cdd7-916c-477f-bd79-825033d2b305
Call-ID: 5df21c71-9119-4de0-a537-0467410922fa
CSeq: 6768 OPTIONS
Accept: application/sdp
Accept-Language: en
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REGISTER, SUBSCRIBE, NOTIFY, REFER, INFO, MESSAGE, UPDATE
Supported: replaces, timer
Allow-Events: message-summary, dialog, call-info, line-seize
Content-Length: 0

-- Executing [s@default:1] answer("PJSIP/3cx-trunk-00000002", "")
   > 0x7766102c11e0 -- Strict RTP learning after remote address set to: 3CXPublicIP:9292

<— Transmitting SIP response (911 bytes) to UDP:3CXPublicIP:5060 —>
SIP/2.0 200 OK
Via: SIP/2.0/UDP 10.201.9.63:5060;rport=5060;received=3CXPublicIP;branch=z9hG4bK-524287-1—1677a86fd9342d50
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
From: “Raymond Norton” sip:9874@3CXfqdn;tag=7db0a514
To: sip:9877@3CXfqdn;tag=d980180a-9dec-4895-95a0-62063e23d54b
CSeq: 1 INVITE
Server: Asterisk PBX 20.6.0~dfsg+~cs6.13.40431414-2build5
Contact: sip:AsteriskPublicIP:5060
Allow: OPTIONS, REGISTER, SUBSCRIBE, NOTIFY, PUBLISH, INVITE, ACK, BYE, CANCEL, UPDATE, PRACK, MESSAGE, INFO, REFER
Supported: 100rel, timer, replaces, norefersub
Content-Type: application/sdp
Content-Length: 281

v=0
o=- 3003121664 3321888771 IN IP4 AsteriskPublicIP
s=Asterisk
c=IN IP4 AsteriskPublicIP
t=0 0
m=audio 6360 RTP/AVP 0 96
a=rtpmap:0 PCMU/8000
a=rtpmap:96 telephone-event/8000
a=fmtp:96 0-16
a=ptime:20
a=maxptime:140
a=sendrecv
m=video 0 RTP/AVP 31
c=IN IP4 AsteriskPublicIP

<— Received SIP request (383 bytes) from UDP:3CXPublicIP:5060 —>
ACK sip:AsteriskPublicIP:5060 SIP/2.0
Via: SIP/2.0/UDP 10.201.9.63:5060;branch=z9hG4bK-524287-1—c41991094037db49;rport
Max-Forwards: 70
Contact: sip:9877@3CXPublicIP:5060
To: sip:9877@3CXfqdn;tag=d980180a-9dec-4895-95a0-62063e23d54b
From: “Raymond Norton” sip:9874@3CXfqdn;tag=7db0a514
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
CSeq: 1 ACK
Content-Length: 0

-- Digit timeout set to 5.000
-- Response timeout set to 10.000
-- Executing [s@default:1] background("PJSIP/3cx-trunk-00000002", "demo-congrats")
-- <PJSIP/3cx-trunk-00000002> Playing 'demo-congrats.slin' (language 'en')
   > 0x7766102c11e0 -- Strict RTP switching to RTP target address 3CXPublicIP:9292 as source

<— Received SIP request (419 bytes) from UDP:3CXPublicIP:5060 —>
BYE sip:AsteriskPublicIP:5060 SIP/2.0
Via: SIP/2.0/UDP 10.201.9.63:5060;branch=z9hG4bK-524287-1—e6542854e7227c18;rport
Max-Forwards: 70
Contact: sip:9877@3CXPublicIP:5060
To: sip:9877@3CXfqdn;tag=d980180a-9dec-4895-95a0-62063e23d54b
From: “Raymond Norton” sip:9874@3CXfqdn;tag=7db0a514
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
CSeq: 2 BYE
Reason: SIP;text=“Call terminated”
Content-Length: 0

<— Transmitting SIP response (393 bytes) to UDP:3CXPublicIP:5060 —>
SIP/2.0 200 OK
Via: SIP/2.0/UDP 10.201.9.63:5060;rport=5060;received=3CXPublicIP;branch=z9hG4bK-524287-1—e6542854e7227c18
Call-ID: EEAv_nbZy1OurHhxXqKRyg…
From: “Raymond Norton” sip:9874@3CXfqdn;tag=7db0a514
To: sip:9877@3CXfqdn;tag=d980180a-9dec-4895-95a0-62063e23d54b
CSeq: 2 BYE
Server: Asterisk PBX 20.6.0~dfsg+~cs6.13.40431414-2build5
Content-Length: 0

== Spawn extension (default, s, 1) exited non-zero on ‘PJSIP/3cx-trunk-00000002’
SC3CX-Asterisk*CLI>

I don’t know the capabilities of 3CX, but it looks like you have connected to it as though you were a phone. For a private trunk, you would normally connect by static addresses, and therefore not need to register. Strictly used, registration only tells the other side how to handle a single URI.

In this case, if the 3CX doesn’t support being configured with your static address, you would need to write dialplan to parse the destination number from the To header.

It requested the “s” extension in the request URI. This is where Asterisk looks for the dialed number. If it needs to be pulled from elsewhere (such as the To header) then custom dialplan logic has to be written to extract the header using the PJSIP_HEADER dialplan function, and then to parse out the user portion, and use that instead.

Of course, this is my limited understanding at work, but the trunk is/was configured as IP based, not requiring registration. However, I felt 9877 needed to act as a generic phone, requiring registration. It sounds like I’m missing the mark, but my project requires an extension(s) located on Asterisk to forward all calls to an ATA device hanging off 3CX, prepending each call with intercom dial codes. The end game would be to have multiple trunks to other 3CX systems so a single VM could provide the dial codes needed for each school district.

Having multiple registrations to the same system can cause confusion, and is best avoided. Your usage appeared to be as a tie line. Whilst SIP makes no distinction between “trunk” and “extension”, and doesn’t make use of these terms, a tie line would normally be considered to be a trunk by systems that do use those terms.

If you have to mimic a direct phone connection, you need to distinguish between them, which in this case is probably best achieved by by setting the contact user for the registration, to the extension number.

Are you recommending this be changed to the extension and get rid of the 9877 lines in pjsip.conf?
[3cx-trunk]
type=aor
contact=sip:3CXPublicIP:5060
qualify_frequency=30

I had grand hopes of a single asterisk vm, but if that is asking for trouble, I can use one VM per school district.

I’m not making detailed suggestions, as I don’t know enough about the 3CX. I’m assuming the capabilities of a reasonable SIP PABX, in which case you need to do whatever is needed to make it consider that Asterisk is a PABX at the end of a tie trunk.

With the roles reversed, and calling a number on the 3CX, you would want a dialstring of PJSIP/${EXTEN}@3cx-trunk, but I don’t know what you do to achieve that on the 3CX. (Again, with roles reversed, on FreePBX, the connection would be set up as an intra-company trunk.) You would remove the type=register section.

It looks like 3CX is what used to be called a Centrex solution, with more traditional phone lines, and would be called a Cloud PABX. That means that the expected usage is have just phones, not tie trunks. It might support tie trunks, but its probably against their business model to encourage their use, so documentation might be deeply buried. This might help SIP tie trunk | 3CX Forums

(A traditional Centrex system is where you have phones connected to a remote, central exchange, possibly run by the PSTN operator, but in a way where you use short numbers to dial other people in your office.)

Incidentally, I would want to consider survivability in any school communications system. Do you have a plan for how to implement period bells if a cyber attack takes down the cloud, or to allow internal communication, if the school becomes a relief centre in a natural disaster that takes down your links to the cloud?

Although it is a hack, I wanted to see it work, and it does. 9876 rings now, but the demo greeting does not play (I am researching that.) My next project is to figure out how to include the proper extension info in the header, as @jcolp recommended.

; Fallback for undefined extensions
exten => s,1,NoOp(Default handler - unexpected routing)
same => n,Dial(PJSIP/9876@3cx-trunk,30) ; Adjust trunk name as needed
same => n,Playback(demo-congrats)
same => n,Hangup()

In answer to your worst-case scenario, we are using a private cloud, and when not, the district will host the VM on-premises. Runnning an intercom/paging system on Voip is also a concern. Still, the schools I am working with usually have a direct connect analog mic attached to their systems, so all-call paging will still work with a network outage. My project will send bell tones and pre-recorded announcements through Asterisk to a Bogen or similar unit.