Messinet Secure Services
wiki:Asterisk/DialOut
Last modified 11 months ago Last modified on 06/10/11 01:59:09

Outbound dialing templates with Google Voice, DUNDi, PSTN, ENUM & VoIP providers

This family of templates is a pathway by which you can configure your Asterisk server to direct outbound calls. The beauty of templates is that you can create easily-repeatable blocks of dialplan code that can be stacked and/or interchanged to create different outbound pathways based on the context in which a device is placed. For those of you who don't want the explanation, skip to Putting it all together?.

It is a method of Least Cost Routing which includes in order:

  1. Google Voice
  2. Public Switched Telephone Network (PSTN)
  3. DUNDi
  4. E.164 Number Mapping (ENUM)
  5. Voice over IP (VoIP)

The first step in the configuration is to create the outbound templates. I create a template for each possible outbound route, making it easy to mix and match for devices which should not have "full" outbound access.

Google Voice Template

To configure Asterisk to inter operate with Google Voice, see https://wiki.asterisk.org/wiki/display/AST/Calling+using+Google.

[gv](!)
same => n,Set(ARRAY(CALLERID(num),CDR(userfield)=1${EXTEN},gv))
same => n,Dial(Gtalk/<jabber.conf_account>/+${EXTEN}@voice.google.com,40,KL(7200000:120000)T)

Local PSTN & DUNDi Template

[pstn-dundi](!)
same => n,Set(CDR(userfield)=pstn)
same => n,GosubIf($[${DIALPLAN_EXISTS(dundi-e164-local,${EXTEN},1)}]?dundi-e164-local,${EXTEN},1)
same => n,Set(ARRAY(i,id)=1,${DUNDIQUERY(${EXTEN})})
same => n,Set(max=${DUNDIRESULT(${id},getnum)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=dundi:${DUNDIRESULT(${id},${i})})
same => n,Dial(${DUNDIRESULT(${id},${i})},40,KT)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

ENUM Template

[enum](!)
same => n,Set(ARRAY(i,id)=1,${ENUMQUERY(+${EXTEN},ALL,e164.org)})
same => n,Set(max=${ENUMRESULT(${id},getnum)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(uri=${ENUMRESULT(${id},${i})})
same => n,Set(CDR(userfield)=enum:${uri})
same => n,ExecIf($["${uri:0:3}" = "sip"]?Dial(SIP/${uri:4},40,KL(7200000:120000)T))
same => n,ExecIf($["${uri:0:4}" = "iax2"]?Dial(IAX2/${uri:5},40,KL(7200000:120000)T))
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

VoIP: SIP & IAX2 Templates

In [globals], I have defined the global variables "IAX2TRUNKS" and "SIPTRUNKS" each listing my respective IAX2 or SIP peers in ascending order of cost per minute as follows:

[globals]
...
SIPTRUNKS=callwithus&diamondcard&voicemeup
IAX2TRUNKS=callwithus&diamondcard
...

These templates are nearly identical except for the dialstrings, but I want to have separate templates so it is easy to enable/disable one or the other.

[sip](!)
same => n,Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(SIPTRUNKS,&)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=${CUT(SIPTRUNKS,&,${i})})
same => n,Dial(SIP/${EXTEN}@${CUT(SIPTRUNKS,&,${i})},40,KL(7200000:120000)T)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()
[iax2](!)
same => n,Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(IAX2TRUNKS,&)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=${CUT(IAX2TRUNKS,&,${i})})
same => n,Dial(IAX2/${CUT(IAX2TRUNKS,&,${i})}/${EXTEN},40,KL(7200000:120000)T)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

Outbound Contexts

I use a special [out] context to instantiate some other options prior to each call passing through the other templates. The [out] context serves as a starter for outbound calls where I can filter out bad extensions, start recording calls, or change the music on hold. In my example, I also set the Caller*ID to the accountcode.

Notice that the first line begins with...

exten => _X.,1,...

Without that, none of the instructions in each of the above templates would work!

[out](!)
exten => _X.,1,Gosub(block,${EXTEN},1)
same => n,Gosub(mixmonitor,${EXTEN},1)
same => n,Gosub(moh,${EXTEN},1)
same => n,Set(CALLERID(num)=${CDR(accountcode)})

Now that the templates are configured, I can create my outbound contexts quickly and easily. The first block is defined for my devices that have full outbound access; they can dial out using Google Voice, Local PSTN, DUNDi, ENUM and all of my VoIP providers. It is important to note that the templates listed after the [outbound] context marker will be copied to this context in order and before anything else defined in this context.

The only exten line I place in each outbound context is one that sends the call to Allison Smith saying "No route exists to the dialed destination. Goodbye." after the dialplan has exhausted all options.

[outbound](out,gv,pstn-dundi,enum,sip,iax2)
same => n,Playback(no-route-exists-to-dest&vm-goodbye)

This next block is for my "guest" devices. I give these devices access to anything that doesn't cost me money!

[outbound-guest](out,pstn-dundi,enum)
same => n,Playback(no-route-exists-to-dest&vm-goodbye)

I configure calls to exit via a certain pathway by adding the following to the [context] in which my devices are registered in extensions.conf. I use the following [context] so that I can properly format the numbers prior to sending them to my [outbound] context(s).

[context]
exten => _NXXXXXX,1,Goto(outbound,1773${EXTEN},1)
exten => _1NXXNXXXXXX,1,Goto(outbound,${EXTEN},1)
exten => _011.,1,Goto(outbound,${EXTEN:3},1)

Putting It All Together

[globals]
...
SIPTRUNKS=callwithus&diamondcard&voicemeup
IAX2TRUNKS=callwithus&diamondcard
...

[context]
exten => _NXXXXXX,1,Goto(outbound,1773${EXTEN},1)
exten => _1NXXNXXXXXX,1,Goto(outbound,${EXTEN},1)
exten => _011.,1,Goto(outbound,${EXTEN:3},1)

[context-guest]
exten => _NXXXXXX,1,Goto(outbound-guest,1773${EXTEN},1)
exten => _1NXXNXXXXXX,1,Goto(outbound-guest,${EXTEN},1)
exten => _011.,1,Goto(outbound-guest,${EXTEN:3},1)

[gv](!)
same => n,Set(ARRAY(CALLERID(num),CDR(userfield)=1${EXTEN},gv))
same => n,Dial(Gtalk/<jabber.conf_account>/+${EXTEN}@voice.google.com,40,KL(7200000:120000)T)

[pstn-dundi](!)
same => n,Set(CDR(userfield)=pstn)
same => n,GosubIf($[${DIALPLAN_EXISTS(dundi-e164-local,${EXTEN},1)}]?dundi-e164-local,${EXTEN},1)
same => n,Set(ARRAY(i,id)=1,${DUNDIQUERY(${EXTEN})})
same => n,Set(max=${DUNDIRESULT(${id},getnum)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=dundi:${DUNDIRESULT(${id},${i})})
same => n,Dial(${DUNDIRESULT(${id},${i})},40,KT)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

[enum](!)
same => n,Set(ARRAY(i,id)=1,${ENUMQUERY(+${EXTEN},ALL,e164.org)})
same => n,Set(max=${ENUMRESULT(${id},getnum)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(uri=${ENUMRESULT(${id},${i})})
same => n,Set(CDR(userfield)=enum:${uri})
same => n,ExecIf($["${uri:0:3}" = "sip"]?Dial(SIP/${uri:4},40,KL(7200000:120000)T))
same => n,ExecIf($["${uri:0:4}" = "iax2"]?Dial(IAX2/${uri:5},40,KL(7200000:120000)T))
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

[sip](!)
same => n,Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(SIPTRUNKS,&)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=${CUT(SIPTRUNKS,&,${i})})
same => n,Dial(SIP/${EXTEN}@${CUT(SIPTRUNKS,&,${i})},40,KL(7200000:120000)T)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

[iax2](!)
same => n,Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(IAX2TRUNKS,&)})
same => n,While($["${i}" <= "${max}"])
same => n,Set(CDR(userfield)=${CUT(IAX2TRUNKS,&,${i})})
same => n,Dial(IAX2/${CUT(IAX2TRUNKS,&,${i})}/${EXTEN},40,KL(7200000:120000)T)
same => n,Set(i=${MATH(${i}+1,i)})
same => n,EndWhile()

[outbound](out,gv,pstn-dundi,enum,sip,iax2)
same => n,Playback(no-route-exists-to-dest&vm-goodbye)

[outbound-guest](out,pstn-dundi,enum)
same => n,Playback(no-route-exists-to-dest&vm-goodbye)

[dundi-e164-local]
include => dundi-e164-canonical
include => dundi-e164-customers
include => dundi-e164-via-pstn
exten => _1NXXNXXXXXX.,2,Return()

Verify The Outbound Dialplan Configuration

If all is well, you should see the following when you issue dialplan show outbound at the Asterisk command prompt.

*CLI> dialplan show outbound
[ Context 'outbound' created by 'pbx_config' ]
  '_X.' =>          1. Gosub(block,${EXTEN},1)                    [pbx_config]
                    2. Gosub(mixmonitor,${EXTEN},1)               [pbx_config]
                    3. Gosub(moh,${EXTEN},1)                      [pbx_config]
                    4. Set(CALLERID(num)=${CDR(accountcode)})     [pbx_config]
                    5. Set(ARRAY(CALLERID(num),CDR(userfield)=1${EXTEN},gv)) [pbx_config]
                    6. Dial(Gtalk/<jabber.conf_account>/+${EXTEN}@voice.google.com,40,KL(7200000:120000)T) [pbx_config]
                    7. Set(CDR(userfield)=pstn)                   [pbx_config]
                    8. GosubIf($[${DIALPLAN_EXISTS(dundi-e164-local,${EXTEN},1)}]?dundi-e164-local,${EXTEN},1) [pbx_config]
                    9. Set(ARRAY(i,id)=1,${DUNDIQUERY(${EXTEN})}) [pbx_config]
                    10. Set(max=${DUNDIRESULT(${id},getnum)})     [pbx_config]
                    11. While($["${i}" <= "${max}"])              [pbx_config]
                    12. Set(CDR(userfield)=dundi:${DUNDIRESULT(${id},${i})}) [pbx_config]
                    13. Dial(${DUNDIRESULT(${id},${i})},40,KT)    [pbx_config]
                    14. Set(i=${MATH(${i}+1,i)})                  [pbx_config]
                    15. EndWhile()                                [pbx_config]
                    16. Set(ARRAY(i,id)=1,${ENUMQUERY(+${EXTEN},ALL,e164.org)}) [pbx_config]
                    17. Set(max=${ENUMRESULT(${id},getnum)})      [pbx_config]
                    18. While($["${i}" <= "${max}"])              [pbx_config]
                    19. Set(uri=${ENUMRESULT(${id},${i})})        [pbx_config]
                    20. Set(CDR(userfield)=enum:${uri})           [pbx_config]
                    21. ExecIf($["${uri:0:3}" = "sip"]?Dial(SIP/${uri:4},40,KL(7200000:120000)T)) [pbx_config]
                    22. ExecIf($["${uri:0:4}" = "iax2"]?Dial(IAX2/${uri:5},40,KL(7200000:120000)T)) [pbx_config]
                    23. Set(i=${MATH(${i}+1,i)})                  [pbx_config]
                    24. EndWhile()                                [pbx_config]
                    25. Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(SIPTRUNKS,&)}) [pbx_config]
                    26. While($["${i}" <= "${max}"])              [pbx_config]
                    27. Set(CDR(userfield)=${CUT(SIPTRUNKS,&,${i})}) [pbx_config]
                    28. Dial(SIP/${EXTEN}@${CUT(SIPTRUNKS,&,${i})},40,KL(7200000:120000)T) [pbx_config]
                    29. Set(i=${MATH(${i}+1,i)})                  [pbx_config]
                    30. EndWhile()                                [pbx_config]
                    31. Set(ARRAY(CDR(amaflags),i,max)=BILLING,1,${FIELDQTY(IAX2TRUNKS,&)}) [pbx_config]
                    32. While($["${i}" <= "${max}"])              [pbx_config]
                    33. Set(CDR(userfield)=${CUT(IAX2TRUNKS,&,${i})}) [pbx_config]
                    34. Dial(IAX2/${CUT(IAX2TRUNKS,&,${i})}/${EXTEN},40,KL(7200000:120000)T) [pbx_config]
                    35. Set(i=${MATH(${i}+1,i)})                  [pbx_config]
                    36. EndWhile()                                [pbx_config]
                    37. Playback(no-route-exists-to-dest&vm-goodbye) [pbx_config]