Yes, in fact that’s exactly the behavior I’m utilizing to make this work.
So firstly, I want to emphasize that this is a “could I?” not a “should I?” thread. That’s why I limited the scope to the dialplan. In searching about this I found a number of threads asking about similar solutions, and general guidance to use AGI, but nothing to say if it was possible or not in the dialplan. So I’d like to use this thread for “here’s how you can”. I’ve already learned some decent “here’s why you’ll shouldn’t” points. And, since info on Asterisk like this fortunately tends to live a long time on the internet, I’ll just note it’s February 2024 and this was proof-of-concepted on Asterisk 24.
Basically how the origination solution I’ve cobbled together works is:
[Incoming call]
1) A call comes in that triggers this system
2) We originate out using
(local/outbound_context_1,exten,outbound_context_2) as
many times as needed (2 or more, otherwise this is massive
unnecessary complexity). Use the `a` option to not block.
3) Park the incoming call in a dedicated lot with `findslot => first`
NOTE: Per discussion below this isn't required, but call parking
brings some desirable features if transparency is a goal.
[Outgoing calls: outbound_context_1]
1) outbound_context_1 starts from the originate
2) Wait for the parked call exten to go INUSE. This is a race condition
check (race conditions will become a theme)
3) Answer() the channel. This will launch outbound_context_2
4) WaitIfCondition on:
- That the call is not parked
- EXTENSION_STATE([first park slot exten]@[parkinglot])
can be used for this.
- That the exten dialed in outbound_context_2 is not ringing
- EXTENSION_STATE([EXTEN]@outbound_context_2)
can be used for this.
- That where-ever the parked call goes after this (in my case
a confbridge) is idle.
5) Check if where-ever the parked call goes after this is idle and if the
parked call slot is idle. If so:
5a) wait a half-second or so and recheck to ensure we're not racing the
other thread transitioning to the target.
5b) If the recheck is still true, Hangup(). This will hangup
the other end in outbound_context_2
6) Check if the exten we called in outbound_context_2 is INUSE.
If so, Goto a context that:
6a) Originate another local call that:
- Unparks the parked call on one side
- Launches wherever the parked call goes
(In my case a ConfBridge())
6b) Connects this side to wherever it needs to go
7) Check if wherever the calls are going is active. If so:
7a) WaitForCondition on the exten we dialed in outbound_context_2
being INUSE
7b) Goto the same context as 6
8) If we got here we've fallen through, and there's plenty of reasons
that can happen. Wait a small delay to not busy-loop and goto 4
[Outgoing calls: outbound_context_2]
- This is launched by the other side of the originate that
launches outbound_context_1
- Dial a hinted exten. Something like:
exten => 1234,hint,PJSIP/1234
exten => 1234,n,Dial(PJSIP/1234,45)
exten => 1234,n,Hangup()
- That's it.
So that’s the how, here’s the “why not”:
- This is rife with race conditions. When testing it initially worked great with a local SIP device that signalled quickly. When I tried dialing in from a remote device with slower signalling I started losing calls in the main wait loop. The main race point is the dual condition “are the parked slot and the parked call target both idle” check. Calls can be lost here as both conditions will briefly become true as the parked call transitions, making it look like the parked call hung up.
- We’re matching on exten state, and there are many of those. To reduce conditional complexity my implementation only uses
INUSE
which is likely naive. The alternative is match more states, which means heavier conditionals with more opportunities for bugs and logic errors.
- I’m sure this isn’t performant. I’ve only tested it with one call on a otherwise idle test Asterisk instance
- Lots of other problems I’m sure I haven’t found yet, this is a PoC.
So, I wanted to know if you could, and yea, you can. Should you? Unless you need something low-volume, fast (as in person time to solution fast), and have a reason you can’t immediately use AGI
or ARI
I’d say no. This is not optimal, I wouldn’t run it on a system with any volume or even one where parallel calls into this thing are reasonably expected. I’m likely going to refactor this into a ARI
once I can jump the security hoops to get that going.
And finally, just because I had a heck of a time finding this in the docs, here’s how you originate a local call:
exten => s,n,Originate(Local/1234@dialin_bridge_outbound_originator_primary,exten,dialin_bridge_outbound_originator_secondary,5678,1,1,a)
This will start a call at 1234,1
in context dialin_bridge_outbound_originator_primary
, and when that answers it will also start processing 5678,1
in context dialin_bridge_outbound_originator_secondary