SIP Express Router v0.8.8 - Developer's Guide | ||
---|---|---|
<<< Previous | The Server Startup | Next >>> |
The server is configured through a configuration file. The configuration file is C-Shell like script which defines how incoming requests should be processed. The file cannot be interpreted directly because that would be very slow. Instead of that the file is translated into an internal binary representation. The process is called compilation and will be described in the following sections.
![]() | The following sections only describe how the internal binary representation is being constructed from the config file. The way how the binary representation is used upon a request arrival will be described later. |
The compilation can be divided in several steps:
Lexical analysis is process of converting the input (the configuration file in this case) into a stream of tokens. A token is a set of characters that 'belong' together. A program that can turn the input into stream of tokens is called scanner. For example, when scanner encounters a number in the config file, it will produce token NUMBER.
There is no need to implement the scanner from scratch, it can be done automatically. There is a utility called flex. Flex accepts a configuration file and generates scanner according to the configuration file. The configuration file for flex consists of several lines - each line describing one token. The tokens are described using regular expressions. For more details, see flex manual page or info documentation.
Flex input file for the SER config file is in file cfg.lex. The file is processed by flex when the server is being compiled and the result is written in file lex.yy.c. The output file contains the scanner implemented in the C language.
The second stage of configuration file processing is called syntactical analysis. Purpose of syntactical analysis is to check if the configuration file has been well formed, doesn't contain syntactical errors and perform various actions at various stages of the analysis. Program performing syntactical analysis is called parser.
Structure of the configuration file is described using grammar. Grammar is a set of rules describing valid 'order' or 'combination' of tokens. If the file isn't conformable with it's grammar, it is syntactically invalid and cannot be further processed. In that case an error will be issued and the server will be aborted.
There is a utility called yacc. Input of the utility is a file containing the grammar of the configuration file, in addition to the grammar, you can describe what action the parser should do at various stages of parsing. For example, you can instruct the parser to create a structure describing an IP address every time it finds an IP address in the configuration file and convert the address to its binary representation.
For more information see yacc documentation.
yacc creates the parser when the server is being compiled from the sources. Input file for yacc is cfg.y. The file contains grammar of the config file along with actions that create the binary representation of the file. Yacc will write its result into file cfg.tab.c. The file contains function yyparse which will parse the whole configuration file and construct the binary representation. For more information about the bison input file syntax see bison documentation.
The configuration file consist of three sections, each of the sections will be described separately.
Route Statement - The statement describes how incoming requests will be processed. When a request is received, commands in one or more "route" sections will be executed step by step. The config file must always contain one main "route" statement and may contain several additional "route" statements. Request processing always starts at the beginning of the main "route" statement. Additional "route" statements can be called from the main one or another additional "route" statements (It it simmilar to function calling).
Assign Statement - There are many configuration variables across the server and this statement makes it possible to change their value. Generally it is a list of assignments, each assignment on a separate line.
Module Statement - Additional funcionality of the server is available through separate modules. Each module is a shared object that can be loaded at runtime. Modules can export functions, that can be called from the configuration file and variables, that can be configured from the config file. The module statement makes it possible to load modules and configure them. There are two commands in the statement - loadmodule and modparam. The first can load a module. The second one can configure module's internal variables.
In the following sections we will describe in detail how the three sections are being processed upon server startup.
The following grammar snippet describes how the route statement is constructed
route_stm = "route" "{" actions "}" { $$ = push($3, &rlist[DEFAULT_RT]); } actions = actions action { $$ = append_action($1, $2}; } | action { $$ = $1; } action = cmd SEMICOLON { $$ = $1; } | SEMICOLON { $$ = 0; } cmd = "forward" "(" host ")" { $$ = mk_action(FORWARD_T, STRING_ST, NUMBER_ST, $3, 0) | ... |
A config file can contain one or more "route" statements. "route" statement without number will be executed first and is called the main route statement. There can be additional route statements identified by number, these additional route statements can be called from the main route statement or another additional route statements.
Each route statement consists of a set of actions. Actions in the route statement are executed step by step in the same order in which they appear in the config file. Actions in the route statement are delimited by semicolon.
Each action consists of one and only one command (cmd in the grammar). There are many types of commands defined. We don't list all of them here because the list would be too long and all the commands are processed in the same way. Therefore we show only one example (forward) and interested readers might look in cfg.y file for full list of available commands.
Each rule in the grammar contains a section enclosed in curly braces. The section is the C code snippet that will be executed every time the parser recognizes that rule in the config file.
For example, when the parser finds forward command, mk_action function (as specified in the grammar snippet above) will be called. The function creates a new structure with type field set to FORWARD_T representing the command. Pointer to the structure will be returned as the return value of the rule.
The pointer propagates through action rule to actions rule. Actions rule will create linked list of all commands. The linked list will be then inserted into rlist table. (Function push in rule route_stm). Each element of the table represents one "route" statement of the config file.
Each route statement of the configuration file will be represented by a linked list of all actions in the statement. Pointers to all the lists will be stored in rlist array. Additional route statements are identified by number. The number also serves as index to the array.
When the core is about to execute route statement with number n, it will look in the array at position n. If the element at position n is not null then there is a linked list of commands and the commands will be executed step by step.
Reply-Route statement is compiled in the same way. Main differences are:
Reply-Route statement is executed when a SIP REPLY comes (not ,SIP REQUEST).
Only subset of commands is allowed in the reply-route statement. (See file cfg.y for more details).
Reply-route statement has it's own array of linked-lists.
The server contains many configuration variables. There is a section of the config file in which the variables can be assigned new value. The section is called The Assign Statement. The following grammar snippet describes how the section is constructed (only one example will be shown):
assign_stm = "children" '=' NUMBER { children_no=$3; } | "children" '=' error { yyerror("number expected"); } ... |
The number in the config file is assigned to children_no variable. The second statement will be executed if the parameter is not number or is in invalid format and will issue an error and abort the server.
The module statement allows module loading and configuration. There are two commands:
loadmodule - Load the specified module in form of a shared object. The shared object will be loaded using dlopen.
modparam - It is possible to configure a module using this command. The command accepts 3 parameters: module name, variable name and variable value.
The following grammar snippet describes the module statement:
module_stm = "loadmodule" STRING { DBG("loading module %s\n", $2); if (load_module($2)!=0) { yyerror("failed to load module"); } } | "loadmodule" error { yyerror("string expected"); } | "modparam" "(" STRING "," STRING "," STRING ")" { if (set_mod_param($3, $5, STR_PARAM, $7) != 0) { yyerror("Can't set module parameter"); } } | "modparam" "(" STRING "," STRING "," NUMBER ")" { if (set_mod_param($3, $5, INT_PARAM, (void*)$7) != 0) { yyerror("Can't set module parameter"); } } | MODPARAM error { yyerror("Invalid arguments"); } |
When the parser finds loadmodule command, it will execute statement in curly braces. The statement will call load_module function. The function will load the specified filename using dlopen. If dlopen was successfull, the server will look for exports structure describing the module's interface and register the module. For more details see module section.
If the parser finds modparam command, it will try to configure the specified variable in the specified module. The module must be loaded using loadmodule before modparam for the module can be used ! Function set_mod_param will be called and will configure the variable in the specified module.
<<< Previous | Home | Next >>> |
Built-in Module Initialization | Up | Interface Configuration |