[asterisk-dev] Methodologies for validating dialplan

asterisk at phreaknet.org asterisk at phreaknet.org
Thu Jan 6 08:24:50 CST 2022

On 1/5/2022 6:06 PM, Mark Murawski wrote:
> Hi!
> Throwing my .02 in here.  Adjust to .10 for inflation!
> On 1/4/22 14:53, asterisk at phreaknet.org wrote:
>> .... However, there are a lot of dialplan problems that represent 
>> potentially valid syntax that will cause an error at runtime, such as 
>> branching to somewhere that doesn't exist. The dialplan will reload 
>> with no errors, since there isn't a syntax issue, but at runtime, the 
>> call will fail (and most likely crash). I found over the years that a 
>> lot of these were often simple typos or issues that were easily fixed 
>> but wasted a lot of time in finding solely in the "test, test, test" 
>> approach. Another common grievance I hear time to time about the 
>> dialplan is most issues are caught at runtime, not "compile time" 
>> (i.e. dialplan reload).
> I love it!
> And... take an example from a basic cli call that has a validator 
> built-in:
> static char *meetme_kick_cmd(struct ast_cli_entry *e, int cmd, struct 
> ast_cli_args *a)
> {
>         switch (cmd) {
>         case CLI_INIT:
>                 e->command = "meetme kick";
>                 e->usage =
>                         "Usage: meetme kick <confno> all|<userno>\n"
>                         "       Kick a conference or a user in a 
> conference.\n";
>                 return NULL;
>         case CLI_GENERATE:
>                 return complete_meetmecmd_mute_kick(a->line, a->word, 
> a->pos, a->n);
>         }
>         if (a->argc != 4) {
>                 return CLI_SHOWUSAGE;
>         }
>         return meetme_cmd_helper(a);
> }
> This design pattern has self-contained validation.  And it's up to the 
> function to validate as much or as little as it wants to.
> We would need a new ast_register_application_xml2 and 
> ast_custom_function_register2 to have a new design pattern for 
> self-validating functions and applications.
> Each application would know what kind of parameters it takes, and 
> what's valid and not valid
> new-stle for say... Goto Application
> static int gosub_exec(int cmd, struct ast_channel *chan, const char 
> *data)
> {
>     if (cmd == APP_PARAMS_VALIDATE)
>         ... do some validation!
> ideally gosub knows it's being passed an exten/context/priority and 
> can call ast_exists_extension at dialplan parse time via the validator 
> flow

I think your line of thinking here makes a lot of sense. However, I 
don't think it makes sense to do validation in the actual execution 
function like CLI handlers, as you've shown.

I think an enhanced application (and function) register function would 
make sense, accepting another function to do the validation, e.g.:

ast_register_application_xml_validate(app, app_exec, app_validate);

Some of these validation functions can be quite involved, and separating 
them out that way makes more sense.

I slept on this overnight, and here are some other thoughts:

The current approach on the review is to have an external module 
(currently res_pbx_analyze) use core PBX APIs to crawl the dialplan and 
execute callbacks to find fallthroughs and missing audio files. 
Obviously, there are a lot of other things we could look for as well. 
Currently, my approach is looking for "types" of problems, rather than 
analyzing specific applications, but now that I think about it, the 
latter is really a subset of the former. Each application really has 
*one* type of validation that it's doing... Goto/Gosub etc. will check 
the location... MusicOnHold will check the MOH class... 
Playback/Background/Read will check the audio file... Voicemail would 
check the voicemail box, and so on... Dial would validate that a 
registered channel tech is being used, etc. (validating that any options 
use also exist could also be another component).

Some of these naturally fall into groups, but a good way to deal with 
this might be CLI aliases, e.g.:

dialplan analyze fallthrough => dialplan analyze Goto, dialplan analyze 
GotoIf, dialplan analyze Gosub, dialplan analyze GosubIf

dialplan analyze audio => dialplan analyze Playback, dialplan analyze 
ControlPlayback, dialplan analyze BackGround, dialplan analyze Read

Obviously, the validation functions for some of these are going to be 
almost identical. To deal with code duplication, each validation 
function could call the same function within the core, even though the 
entry point would be different.

Of course, for registering applications in the same module (like Goto 
and GotoIf), the same validation function could directly be use for 
both. We'd want to use the same function though from within app_stack 
for Gosub/GosubIf, most likely, so maybe for rare cases of multiple 
modules sharing validation functions, those could go in a core file like 
pbx_validate that contains common-to-many-modules validation functions. 
The vast majority of modules that do this, though, I expect, would have 
their own validation function within the module.

(By the way, I'm not set on dialplan analyze... still debating whether 
that or dialplan inspect or dialplan validate would make the most sense...)

Another reason I think doing validation in module is a better approach. 
For one, not all the internals of modules are exposed outside of it. For 
instance, say we want to verify that MusicOnHold() and 
StartMusicOnHold(), if they contain an argument, contain the name of a 
valid in-memory MOH class. This is only exposed within res_tonedetect 
currently. To do validation in another module would mean that needs to 
be exposed somehow. Obviously, this can get very messy.

If it's done in-module, then it just works out cleanly. So, we could do 
away with a dedicated external module and simply register validation 
functions from within particular modules.

> This would be a major undertaking to add validation for every function 
> and every application but since dialplan is so ubiquitous and is 
> probably never going away... would make huge inroads to making sure 
> everything is as correct as possible at parse/load time.

I don't really see this as something that would be done for every single 
module necessarily, but some that come to mind that could benefit:

Goto, GotoIf, Gosub, GosubIf, ExecIf, Verbose, Log, Dial, VoiceMail, 
ConfBridge, Playback, Read, BackGround, etc.

> There needs to be an externally-facing way to call 
> function/application validators for those of us who write dynamic 
> dialplan generators.  Maybe an AMI command?
Considering it would just be executing the registered validation 
function for each app, function, etc. should be doable.

At the moment, I'd envisioned it mainly be CLI commands, first and 
foremost, since that goes along nicely with dialplan reload. And, as I 
said above, there could be CLI aliases for each "group" of validation 
functions that naturally go well together, to find specific categories 
of errors. There could also be a "dialplan analyze all" to run all 
registered validation functions and report any problems.

> Strict: false    <-- allow for generic 'does it look okay' testing vs 
> does the destination actually exist 
I'm with you until here... this I'm not sure I understand the point as 
much of the "generic" validation. If we're not validating the 
destination actually exists, in this case, then we're not really doing 
much at all. At that point, it's basically counting commas and reporting 
whether there's no more than 2.
> And then more of a full validator via a new application and AMI Command
> ValidateDialplanFile(/tmp/new_extensions.conf,/tmp/path_to_errors.log); 

As far as specific error logging, currently I'm just throwing WARNINGs 
that would get sent out to all the relevant consoles and log files. A 
simple grep might be able to filter all of those out.

More information about the asterisk-dev mailing list