FreePBX | Register | Issues | Wiki | Portal | Support

Hooking for fun and income


(Lorne Gaetz) #1

Despite the frequency with which it arises here in the forum, there is not yet a good resource for learning to use dialplan hooks in FreePBX. This will probably end up in the wiki at some point, but until that happens, here are the broad strokes for leveraging dialplan hooks in FreePBX 14.

What is a dialplan hook?

A dialplan hook refers to several pre-defined FreePBX contexts that exist solely for users to add their own Asterisk Macros to be run at specific locations in the call flow. They all occur immediately before a Dial() application and are used to run custom dialplan to perform some function not supported by the GUI. As a user, you define dialplan hooks as macros in the file /etc/asterisk/extensions_custom.conf as individual asterisk contexts. Use whatever method you are comfortable with for editing the file; beginners may find is useful to use the Config Edit module. You may be aware of something in Asterisk called a “Predial Handler”. Despite the name similarity with the macros that follow, they are completely separate things having nothing to do with each other.

Aren’t macros deprecated in Asterisk?

Yes. However, as of this writing (with 15 in beta) , FreePBX core still relies on dozens of Macros. This will have to be dealt with in time, and I have already opened a ticket for dealing with the predial hook macros. At some point this post will need updates to show how to use the predial subroutines.

How do I add custom SIP headers?

This entire post is a lead in to what is really needed, and that is a guide to adding custom SIP headers in a supportable way. This is beyond the scope of today’s sermon but will form the basis of a future thread. I have been working with SIP headers a lot lately, and have been reminded once again of all the flawed internet how to’s or recommended methods that may once have worked, but no longer. For the longest time we had one SIP driver, and got comfortable with methods for adding SIP headers to chan_sip channels. When PJSIP came along, those methods didn’t work for pjsip channels, so new methods were devised. FreePBX 14 introduced Dial changes that now break some of the early methods for adding headers for PJSIP channels. The end result is that virtually all of the posted methods for adding custom SIP headers are incomplete at best, or at worst are completely useless for FreePBX 14.

When do I use a dialplan hook and when do I use a Custom Destination?

If you’re able to structure your call flow such that the custom dialplan you want to execute looks something like this:

Inbound Route -> Time Condition -> <custom dialplan> -> Ring Group

Then you don’t need (or want) to use a dialplan hook. The above is better done with a Custom Destination. Custom Destinations are preferred over dialplan hooks so use the GUI wherever possible.

How do I do X on every outbound call?

The first (and for the longest time, the only) dialplan hook going back more than a decade is macro-dialout-trunk-predial-hook. There are many examples floating around the 'net on how to use it, and it’s existence is testament to all the configuration edge cases for the various SIP providers around the world. If your provider requires some specific setting that can’t be done via the GUI, you would use this hook in order to make the necessary channel modifications prior to the trunk dial. A bare bones example would look like this:

[macro-dialout-trunk-predial-hook]
exten => s,1,NoOp(Entering user defined context macro-dialout-trunk-predial-hook in extensions_custom.conf)
; additional lines here 
exten => s,n,MacroExit

The above example is trivial, it only records a single line in the Asterisk full log and then exits sending the call back to the FreePBX generated dialplan. An extension of s is used as is required for all Macros. You can see the above in action by saving the change, reloading the dialplan and make a call after running the following at the bash prompt:

[root@freepbx ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [s@macro-dialout-trunk:20] Macro("PJSIP/5004-00000024", "dialout-trunk-predial-hook,") in new stack
[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [s@macro-dialout-trunk-predial-hook:1] NoOp("PJSIP/5004-00000024", "Entering user defined context macro-dialout-trunk-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 10:46:09] VERBOSE[20229][C-00000011] pbx.c: Executing [s@macro-dialout-trunk-predial-hook:3] MacroExit("PJSIP/5004-00000024", "") in new stack

Once the bare bones dialplan is in place and confirmed working with tail, the hard part begins where you actually write useful dialplan that both works for all edge cases and doesn’t break all the other edge cases.

How do I do X on every local call?

It’s only a little less straight forward to perform similar action on local extension calls. There are several dialplan hooks used for calls to local extensions depending on the ring strategy used to reach the extension. You can see the dialplan hooks in use on local calls by running the same tail from above. So focusing on a simple case where local extension 5004 calls local extension 5005 with no FMFM enabled, you would see the following:

[root@freepbx ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:50:07] VERBOSE[20945][C-00000012] pbx.c: Executing [s@macro-dial-one:52] Macro("PJSIP/5004-00000026", "dialout-one-predial-hook,") in new stack
[2019-04-06 10:50:07] VERBOSE[20945][C-00000012] pbx.c: Executing [s@macro-dialout-one-predial-hook:1] MacroExit("PJSIP/5004-00000026", "") in new stack

The above shows us that just before a call goes to a local extension, the Macro macro-dialout-one-predial-hook is called. Using the same technique as above, we can add our own dialplan such as:

[macro-dialout-one-predial-hook]
exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
; add additional lines here
exten => s,n,MacroExit

After save and reload, the tail confirms it’s working:

[root@freepbx ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [s@macro-dial-one:52] Macro("PJSIP/5004-00000027", "dialout-one-predial-hook,") in new stack
[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [s@macro-dialout-one-predial-hook:1] NoOp("PJSIP/5004-00000027", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 10:57:00] VERBOSE[22171][C-00000013] pbx.c: Executing [s@macro-dialout-one-predial-hook:2] MacroExit("PJSIP/5004-00000027", "") in new stack

How do I apply custom dialplan selectively on some outbound calls?

There is no single simple answer to this and sometimes the answer is ‘you can’t’. But usually one can identify a channel variable that is set to a unique value for all the calls you want to modify, and if so, that allows you to write dialplan that only acts on those calls. A simple example would be dialplan that does something different if the local call originates from extension 5004:

[macro-dialout-one-predial-hook]
exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,GotoIf($["${AMPUSER}"="5004"]?special)
exten => s,n,MacroExit
exten => s,n(special),NoOp(Incoming call from etension 5004)
exten => s,n,MacroExit

Call from 5004:

[root@freepbx ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [s@macro-dial-one:52] Macro("PJSIP/5004-0000002a", "dialout-one-predial-hook,") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [s@macro-dialout-one-predial-hook:1] NoOp("PJSIP/5004-0000002a", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [s@macro-dialout-one-predial-hook:2] GotoIf("PJSIP/5004-0000002a", "1?special") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx_builtins.c: Goto (macro-dialout-one-predial-hook,s,4)
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [s@macro-dialout-one-predial-hook:4] NoOp("PJSIP/5004-0000002a", "Incoming call from etension 5004") in new stack
[2019-04-06 11:55:35] VERBOSE[31283][C-00000016] pbx.c: Executing [s@macro-dialout-one-predial-hook:5] MacroExit("PJSIP/5004-0000002a", "") in new stack

Call from 5016:

[root@freepbx ~]# tail -f /var/log/asterisk/full | grep predial

[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [s@macro-dial-one:52] Macro("PJSIP/5016-0000002b", "dialout-one-predial-hook,") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [s@macro-dialout-one-predial-hook:1] NoOp("PJSIP/5016-0000002b", "Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [s@macro-dialout-one-predial-hook:2] GotoIf("PJSIP/5016-0000002b", "0?special") in new stack
[2019-04-06 11:55:45] VERBOSE[31493][C-00000017] pbx.c: Executing [s@macro-dialout-one-predial-hook:3] MacroExit("PJSIP/5016-0000002b", "") in new stack

And that leads to the obvious question, how do I know which channel variable to check and for what value? Each situation is unique so you need to figure that out for yourself. In doing so the DumpChan dialplan application is essential. Otherwise start a thread asking for ideas.

How do I perform a custom action on hangup?

Nothing to it. Using the techniques above, you would add a hangup handler to the channel. A hangup handler, as the name implies, is a subroutine that runs after the channel hangs up. A simple example:

The dialplan:

[macro-dialout-one-predial-hook]
exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,Set(CHANNEL(hangup_handler_push)=lgaetz-do-this-on-hangup,s,1)
exten => s,n,MacroExit

[lgaetz-do-this-on-hangup]
exten => s,1,Noop(Entering user defined context lgaetz-do-this-on-hangup in extensions_custom.conf)
; additional lines 
exten => s,n,Return

The log entries:

[root@freepbx asterisk]# tail -f /var/log/asterisk/full | grep lgaetz

[2019-04-06 16:31:31] VERBOSE[7503][C-00000018] pbx.c: Executing [s@macro-dialout-one-predial-hook:2] Set("SIP/5002-00000007", "CHANNEL(hangup_handler_push)=lgaetz-do-this-on-hangup,s,1") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] app_stack.c: SIP/5002-00000007 Internal Gosub(lgaetz-do-this-on-hangup,s,1) start
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] pbx.c: Executing [s@lgaetz-do-this-on-hangup:1] NoOp("SIP/5002-00000007", "Entering user defined context lgaetz-do-this-on-hangup in extensions_custom.conf") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] pbx.c: Executing [s@lgaetz-do-this-on-hangup:2] Return("SIP/5002-00000007", "") in new stack
[2019-04-06 16:31:35] VERBOSE[7503][C-00000018] app_stack.c: SIP/5002-00000007 Internal Gosub(lgaetz-do-this-on-hangup,s,1) complete GOSUB_RETVAL=

Continued in part 2 - Virtuous Signalling


Virtuous Signalling
Using *-custom contexts overwrites dialplan
How to add sip header to incoming call?
What is the purpose of all the includes
Dial Plan - Dropping extra digits
SIP Header
How to hide inbound phone number i softphone
Modifying existing Macro with module
Announcement / IVR before dial-out
Dialplan quesiton. Which file to edit?
Anyway to create a truly dynamic 'followme'?
Call limit
Outbound CID how to add +country code
Zulu no audio when dialing through DAHDI
Async AGI - How to do in with SNG 7
[Solved] Keep Certain Extensions from Receiving Caller ID Completely?
[Solved] Keep Certain Extensions from Receiving Caller ID Completely?
(Avayax) #2

Will the use and configuration of these dial plan macros be affected by the macro functionality being deprecated in Asterisk?

[post updated to address this - mod]


(Itzik) #3

Good stuff here!! Thank you for the nice write up, Lorne!


(Tom Ray) #4

Well since Asterisk 13 (as far as I can recall) the use of Pre-Dialer hooks have been in place for applying SIP Headers with something like exten => _X.,1,GoSub(func-set-sipheaders,s,1(X-Header,header-value) and when all the Dial() commands are executed they are run with the options b(func-apply-sipheaders^s^1) which in turn runs this GoSub that will use the proper ADDHeader (SIP or PJSIP) based on the channel type.

I use these for all my custom dialplan to hook in PJSIP headers as the only place you can apply them is in the pre-dial hook with the b() dial option. So this has been in place for quite some time. Now the earlier versions of 13 didn’t have the PJSIP check (real early versions) so yeah, that broke adding PJSIP headers with the function but it’s been fixed for sometime and has been this way on v14.

[func-apply-sipheaders]
include => func-apply-sipheaders-custom
exten => s,1,Noop(Applying SIP Headers to channel ${CHANNEL})
exten => s,n,Set(TECH=${CUT(CHANNEL,/,1)})
exten => s,n,Set(SIPHEADERKEYS=${HASHKEYS(SIPHEADERS)})
exten => s,n,While($["${SET(sipkey=${SHIFT(SIPHEADERKEYS)})}" != “”])
exten => s,n,Set(sipheader=${HASH(SIPHEADERS,${sipkey})})
exten => s,n,ExecIf($["${sipheader}" = “unset” & “${TECH}” = “SIP”]?SIPRemoveHeader(${sipkey}:))
exten => s,n,ExecIf($["${sipheader}" = “unset” & “${TECH}” = “PJSIP”]?Set(PJSIP_HEADER(remove,${sipkey})=))
exten => s,n,ExecIf($["${sipkey}" = “Alert-Info” & ${REGEX("^<[^>]*>" ${sipheader})} != 1]?Set(sipheader=http://127.0.0.1;info=${sipheader}))
exten => s,n,ExecIf($["${TECH}" = “SIP”]?SIPAddHeader(${sipkey}:${sipheader}))
exten => s,n,ExecIf($["${TECH}" = “PJSIP”]?Set(PJSIP_HEADER(add,${sipkey})=${sipheader}))
exten => s,n,EndWhile
exten => s,n,Return()


#5

I believe that there is a problem with macro-dialout-trunk-predial-hook:

Before calling the hook, the variable ‘custom’ is set to the trunk name, e.g. ‘SIP/Flowroute’, so the user’s code can take appropriate actions for specific trunks. AFAIK, this variable is not read by FreePBX – it exists solely to aid hook authors. Unfortunately, because of pjsip differences, pjsip trunks set custom to just ‘PJSIP’. If you have multiple pjsip trunks requiring differing hook actions, you need to fish the trunk name out of various internal variables, which is likely not robust against updates. I hope that macro-dialout-trunk will be updated to correct this problem.


(Lorne Gaetz) #6

Confirmed, I see the same. I was not aware of this channel var until now, nor was I aware of the issue with PJSIP, thanks for that. Have you opened a ticket on this, it needs one. Does it work for IAX2 trunks, there are still plenty of those in the wild?

edit - Looking into this further, I don’t think this is a bug. The channel variable custom is used by FreePBX dialplan to identify trunks of type custom. The fact that it is populated with the peer name of chan_sip trunks is a byproduct not its intent.

There is a supportable way to determine the trunk name without relying on custom:

  1. Determine value of channel var DIAL_TRUNK, it will be an integer
  2. Determine value of global var OUT_${DIAL_TRUNK} this will be the trunk tech/name if SIP or IAX but will only be the tech if PJSIP.
  3. If value from 2 == “PJSIP” then the trunk name will be in global variable OUT_${DIAL_TRUNK}_SUFFIX in the format @trunkname

Distilling the above:

exten => s,n,ExecIF($["${OUT_${DIAL_TRUNK}_SUFFIX}"!=""]?Set(trunk_name=${OUT_${DIAL_TRUNK}_SUFFIX}):Set(trunk_name=${OUT_${DIAL_TRUNK}}))
exten => s,n,Noop(trunk name: ${trunk_name})

(Lorne Gaetz) #7

That technique will work for all endpoints in 14, but not for outbound calls in 13, so I take issues with the claim of “quite some time”. My point still stands tho, doing a google or forum search for “adding sip header” will yield plenty of results that don’t work, or only partially work.


(Tom Ray) #8

I’m not sure how to reply to this without sounding like a complete jerk about it, so here it goes. It’s been 22 months since the release of v14, in my terms that is quite some time. Now if you’re going to take issue with my use of a phrase then my reply to that is simple.

I take issue with the fact that PJSIP, since its introduction, has documented that setting PJSIP headers requires them to be on the called channel that needs them. To do so one must use the pre-dial handler options that were introduced in v11 so that means the b() option for the Dial() string. So that would mean since FreePBX v12 until now for v13, almost five years now, not a single person involved with the FreePBX Project caught this and applied a fix for it? It makes it even worse that v14 does have the pre-dial handlers and solves it and not a single person still thought “We should do this for v13 since it is still a supported release”. This is something that never should have been in issue in v13 at all and definitely should still not be an issue this late in the game on v13.

I am also curious as to why with v15 you are still relying on Macro’s? Granted I don’t use the commercial modules so I have no idea what kind of dialplan those will generate for most things but I’ve been through the latest release of v14 dialplan and have pretty much migrated all the core Marco() functions to GoSub(). It’s a bit time consuming but why isn’t this already done with v15 dialplan? Why does this still sound like it’s going to be a work in progress even after the release of v15 stable? It just doesn’t make any sense to me.


(Lorne Gaetz) #9

Hi Tom:

From my original post:

There are supported methods for adding custom headers but they differ depending on call direction, FreePBX version and channel driver. There may or may not be support for removing/modifying headers, again depending on channel driver and FreePBX version. It is complicated enough that when the time comes it will be another long post, and will necessarily require the information here as a requisite.


(Andrew Nagy) #10

This is not as easy as you propose. Since FreePBX is open source and since you’ve already done all the work why don’t you submit a few pull requests.


(Tom Ray) #11

That won’t be possible because I didn’t touch the FreePBX code to do this. I fully and openly admit that I use FreePBX in a way that it was not designed for and intended for. I multi-tenant it and FreePBX is there to do the lowly grunt work. My modifications would not fit the project as is.


(Igor Dobrosavljevic) #12

I have to know, did my support ticket inspire this post?


(Lorne Gaetz) #13

No. As stated, the community needs a good resource for hacking SIP headers, but to get there a bit of background is needed first.


(Igor Dobrosavljevic) #14

Damn it Lorne, I wanted to feel special.


(Lorne Gaetz) #15

Mr D, you are special! Just like everyone else.


(Igor Dobrosavljevic) #16

Lorne giveth, and Lorne taketh away.


(Itzik) #17

Hi,

I am currently trying this.

Two questions:

1- Can I have something like this?

[macro-dialout-one-predial-hook]
exten => s,1,Noop(Entering user defined context macro-dialout-one-predial-hook in extensions_custom.conf)
exten => s,n,GotoIf($["${AMPUSER}"="5004"]?custom-stuff,s,1)
exten => s,n,MacroExit
exten => s,n(complete),NoOp(Done... Continuing call)
exten => s,n,MacroExit

[custom-stuff]
exten => s,1,Noop(Stating the custom stuff process...)
exten => s,n,TrySystem(doing-stuff-here)
exten => s,n,Goto(macro-dialout-one-predial-hook,s,complete)

Or is it possible to use in [custom-stuff] last line the Return() function to return to the [macro-dialout-one-predial-hook] instead of the Goto ?
(I have tried to setup with Goto(macro-dialout-one-predial-hook,s,complete), and it gives me an error that ‘e’ or ‘t’ isn’t specified or defined in macro-dialout-one-predial-hook not sure what the error exactly was, I can go back and reproduce it to get the full error if necessary)

2- Trying to setup the below for any extension matching 1XX5X

exten => s,n,GotoIf($["${AMPUSER}"="1XX5X"]?custom-stuff,s,1)

But it seems like you cannot use X for 0-9 with the Goto if variable equals in the dialplan. Any workaround?

Thanks for your help


#18

I think you will find that the inclusion of any ‘wild card’ X requires a _ prefix, even if the expression is otherwise ‘closed’ , asterisk is not fully regex compliant.


(Itzik) #19

Thank you. I tried now:

exten => s,n,GotoIf($["${AMPUSER}"="_1XX5X"]?complete)

It still doesn’t work.

-- Executing [s@macro-dialout-trunk-predial-hook:1] Set("SIP/12050-00009f40", "CHANNEL(hangup_handler_push)=hangup-handler,s,1") in new stack
-- Executing [s@macro-dialout-trunk-predial-hook:2] GotoIf("SIP/12050-00009f40", "0?complete") in new stack
-- Executing [s@macro-dialout-trunk-predial-hook:3] MacroExit("SIP/12050-00009f40", "") in new stack

#20

What is the s,1 context? You won’t get to s,n until you pass s,1