Copyright © 2001, 2002 FhG Fokus
The document describes the SIP Express Router internals and algorithms. It describes overall server architecture, request processing, configuration, memory management, interprocess locking, module interface and selected modules in detail.
The document is intended mainly for module developers wishing to implement a new module for the server. Other people like developers of SIP related software or students might be interested too.
This documentation is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For more details see the file COPYING in the source distribution of SER.
receive_msg Functionhdr_fieldsip_urivia_bodyip_addrlumplump_rplmsg_startsip_msgdo_action Functionsr_modulemodule_exportsmodparamset_mod_paramfind_param_exportbind_dbmoddb_initdb_closedb_querydb_free_querydb_insertdb_deletedb_updatedb_use_table The main function in file main.c
is the first function called upon server startup. It's purpose is to initialize the server and
enter main loop. The server initialization will be described in the following sections.
Particular initialization steps are described in order in which they appear in main function.
The first step in the initialization process is the installation of new signal handlers. We need
our own signal handlers to be able to do graceful shutdown, print server statistics and so on. There is
only one signal handler function which is function sig_usr in
file main.c.
The following signals are handled by the function: SIGINT, SIGPIPE, SIGUSR1, SIGCHLD, SIGTERM, SIGHUP and SIGUSR2.
SER utilizes the getopt
function to parse command line parameters.
The function is extensively described in the man pages.
SER contains a fast 32-bit parser. The parser uses precalculated hash table that needs to be
filled in upon startup. The initialization is done here, there are two functions that do the
job. Function init_hfname_parser initializes hash table
in header field name parser and function init_digest_parser
initializes hash table in digest authentication parser. The parser's internals will be described
later.
To make SER even faster we decided to re-implement memory allocation routines. The new
malloc
better fits our needs and speeds up the server a lot. The memory management subsystem needs
to be initialized upon server startup. The initialization mainly creates internal data
structures and allocates memory region to be partitioned.
![]() | The memory allocation code must be initialized BEFORE any of its function is called ! |
Various subsystems of the server must be called periodically regardless of the incoming
requests. That's what timer is for. Function init_timer
initializes the timer subsystem. The function is called from main.c
and can be found in timer.c The timer subsystem will
be described later.
![]() | Timer subsystem must be initialized before config file is parsed ! |
SER has built-in support for FIFO control. It means that the
running server can accept
commands over a FIFO special file (a named pipe). Function register_core_fifo
initializes FIFO subsystem and registers basic commands, that are processed by the core
itself. The function can be found in file fifo_server.c.
The FIFO server will be described in another chapter.
Modules can be either loaded dynamically at runtime or compiled in statically. When a module
is loaded at runtime, it is registered
[1]
immediately with the
core. When the module is compiled in statically, the registration[1]
must be performed during the server startup. Function
register_builtin_modules does the job.
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 similar 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 functionality 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 successful, 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.
The server will try to obtain list of all configured interfaces of the host it is running on. If it fails the server tries to convert hostname to IP address and will use interface with the IP address only.
Function add_interfaces will add all configured interfaces to the
array.
Try to convert all interface names to IP addresses, remove duplicates...
When configured so, SER becomes a daemon during startup. A process is called daemon
when it hasn't associated controlling terminal. See function
daemonize in file
main.c for more details.
The function does the following:
chroot is performed if necessary. That ensures that the server will have access to a particular directory and its subdirectories only.
Server's working directory is changed if the new working directory was specified (usually it is /).
If command line parameter -g was used, the server's group ID is changed to that value.
If command line parameter -u was used, the server's user ID is changed to that value.
Perform fork, let the parent process exit. This ensures that we are not a group leader.
Perform setsid to become a session leader and drop the controlling terminal.
Fork again to drop group leadership.
Create a pid file.
Close all opened file descriptors.
The whole config file was parsed, all modules were loaded already and can
be initialized now. A module can tell the core that it needs to be initialized
by exporting mod_init function.
mod_init function of all loaded modules
will be called now.
After the whole routing list was parsed, there might be still places that can be further processed to speed-up the server. For example, several commands accept regular expression as one of their parameters. The regular expression can be compiled too and processing of compiled expression will be much faster.
Another example might be string as parameter of a function. For example if you call
append_hf("Server: SIP Express Router\r\n") from the routing
script, the function will
append a new header field after the last one. In this case, the function needs to know length
of the string parameter. It could call strlen every time it is
called, but that is not a very good idea because strlen would
be called every time a message is processed and that is not necessary.
Instead of that the length of the string parameter could be precalculated upon server startup, saved
and reused later. The processing of the request will be faster because
append_hf doesn't need
to call strlen every time, I can just reuse the saved value.
This can be used also for string to int conversions, hostname lookups, expression evaluation and so on.
This process is called Routing List Fixing and will be done as one of last steps of the server startup.
Every loaded module can export one or more functions. Each such function can have associated a fixup function, which should do fixing as described in this section. All such fixups of all loaded modules will be called here. That makes it possible for module functions to fix their parameters too if necessary.
If compiled-in, the core can produce some statistics about itself and traffic processed. The statistics
subsystem gets initialized here, see function init_stats.
UDP socket initialization depends on dont_fork variable. If this variable is set (only one process will be processing incoming requests) and there are multiple listen interfaces, only the first one will be used. This mode is mainly for debugging.
If the variable is not set, then sockets for all configured interfaces will be created and initialized.
See function udp_init in file
udp_server.c for more details.
The rest of the initialization process depends on value of dont_fork variable. dont_fork is a global variable defined in main.c. We will describe both variants separately.
If dont_fork variable is set, the server will be operating in special mode. There will be only one process processing incoming requests. This is very slow and was intended mainly for debugging purposes. The main process will be processing all incoming requests itself.
The server still needs additional children:
One child is for the timer subsystem, the child will be processing timers independently of the main process.
FIFO server will spawn another child if enabled. The child will be processing all commands coming through the fifo interface.
If SNMP support was enabled, another child will be created.
The following initialization will be performed in dont fork mode.
(look into function main_loop in file
main.c.
Another child will be forked for the timer subsystem.
Initialize the FIFO server if enabled, this will fork another child. For more info about the FIFO server, see section The FIFO server.
Call init_child(0).
The function performs per-child specific initialization of all loaded modules.
A module can be initialized though mod_init function.
The function is called BEFORE the server forks and thus is common for all
children.
If there is anything, that needs to be initialized in every child separately (for example
if each child needs to open its own file descriptor), it cannot be done in
mod_init.
To make such initialization possible, a module can export another initialization function
called init_child. The function will be called in
all children AFTER fork of the server.
And since we are in "dont fork" mode and there will no children processing
requests (remember the main process will be processing all requests), the
init_child wouldn't be called.
That would be bad, because child_init might do some
initialization that must be done otherwise modules might not work properly.
To make sure that module initialization is complete we will call
init_child here for the main process even if we
are not going to fork.
That's it. Everything has been initialized properly and as the last step we will call
udp_rcv_loop which is the main loop function. The function
will be described later.
dont_fork is not set. That means that the server will fork children and the children will be processing incoming requests. How many children will be created depends on the configuration (children variable). The main process will be sleeping and handling signals only.
The main process will then initialize the FIFO server. The FIFO server needs another child to handle communication over FIFO and thus another child will be created. The FIFO server will be described in more detail later.
Then the main process will perform another fork for the timer attendant. The child will take care of timer lists and execute specified function when a timer hits.
The main process is now completely initialized, it will sleep in pause
function until a signal comes and call handle_sigs when such
condition occurs.
The following initialization will be performed by each child separately:
Each child executes init_child function. The function
will sequentially call child_init functions of all loaded modules.
Because the function is called in each child separately, it can initialize per-child
specific data. For example if a module needs to communicate with database, it must open
a database connection. If the connection would be opened in mod_init
function, all the children would share the same connection and locking would be necessary
to avoid conflicts. On the other hand if the connection was opened in
child_init function, each child will have its own
connection and concurrency conflicts will be handled by the database server.
And last, but not least, each child executes udp_rcv_loop function
which contains the main loop logic.
Upon startup, all children execute recvfrom function. The process will
enter the kernel mode. When there is no data to be processed at the moment, the kernel will put the process on
list of processes waiting for data and the process will be put asleep.
When data to be processed was received, the first process on the list will be removed from the list and woken
up. After the process finished processing of the data, it will call recvfrom
again and will be put by the kernel at the end of the list.
When next data arrives, the first process on the list will be removed, processes the data and will be put on the end of the list again. And so on...
The main loop logic can be found in function udp_rcv_loop in file
udp_server.c.
The message is received using recvfrom function. The received data is
stored in buffer and zero terminated.
If configured so, basic sanity checks over the received message will be performed.
The message is then processed by receive_msg function and
recvfrom is called again.
receive_msg FunctionThe function can be found in receive.c file.
In the server, a request or response is represented by sip_msg
structure. The structure is allocated in this function. The original message is stored in
buf
attribute of the structure and is zero terminated. Then, another copy of the received message will be
created and the copy will be stored in orig field. The original copy will be not
modified during the server operation. All changes will be made to the copy in buf
field. The second copy of the message will be removed in the future.
The message will be parsed (function parse_msg). We don't
need the whole message header to be parsed at this stage. Only the first line and first Via
header need to be parsed. The server needs to know if the message is request or response - hence
the first line. The server also needs the first Via to be able to add its own Via - hence
the first Via. Nothing else will be parsed at the moment - this saves time. (Message parser
as well as sip_msg structure will be described later).
A module may register callbacks. Each callback have associated an event, that will trigger the callback. One such callback is pre-script callback. Such callback will be called immediately before the routing part of the config file will be executed. If there are such callbacks registered, they will be executed now.
As the next step we will determine type of the message. If the message being processed is a REQUEST then basic sanity checks will be performed (make sure that there is the first Via and parsing was successful) and the message will be passed to routing engine. The routing engine is one of the most complicated parts of the server and will be in detail described in chapter The Routing Engine.
If the message is a RESPONSE, it will be simply forwarded to its destination.
After all, post-script callbacks will be executed if any and the structure representing the message will be released.
Processing of the message is done now and the process is ready for another SIP message.
The server shutdown can be triggered by sending a signal to the server. The server will behave differently upon receiving various types of signals, here is a brief summary:
SIGINT, SIGPIPE, SIGTERM, SIGCHLD will terminate the server.
SIGUSR1 will print statistics and let the server continue.
SIGHUP, SIGUSR2 will be ignored.
There is only one common signal handler for all signals - function sig_usr
in file main.c.
In normal mode of operation (dont_fork variable is not set), the main server is not processing any
requests, it calls pause function and will be waiting for signals only. What
happens when a signal arrives is shown in the previous paragraph.
When in normal mode (dont_fork is not set), the signal handler of the main process will
only store number of the signal received.
All the processing logic will be executed by the main process outside the signal handler (function
handle_sigs) The function will be called immediately after the signal
handler finish. The main
process usually does some cleanup and running such things outside the signal handler is much
more safe than doing it from the handler itself. Children only print statistics and exit
or ignore the signal completely, that is quite safe and can be done directly from the signal handler of
children.
When dont_fork is set, all the cleanup will be done directly from the signal handler, because there is only one process - the main process. This is not so safe as the previous case, but this mode should be used for debugging only and such shortcoming doesn't harm in that case.
Upon receipt of SIGINT, SIGPIPE or SIGTERM destroy_modules will be called.
Each module may register so-called destroy function if it needs to do some
cleanup when the server is terminating (flush of cache to disk for example).
destroy_modules will call destroy function of all loaded modules.
If you need to terminate the server and all of its children, the best way how to do it is to send SIGTERM to the main process, the main process will in turn send the same signal to its children.
The main process and its children are in the same process group. Therefore the main process can kill all its children simply by sending a signal to pid 0, sending to pid 0 will send the signal to all processes in the same process group as the sending process. This is how the main process will terminate all children when it is going to shut down.
If one child exited during normal operation, the whole server will be shut down. This is better than let the server continue - a dead child might hold a lock and that could block the whole server, such situation cannot be avoided easily. Instead of that it is better to shutdown the whole server and let it restart.
There are some data structures that are important and widely used in the server. We will describe them in detail in this section.
![]() | There are many more structures and types defined across the server and modules. We will describe only the most important and common data structure here. The rest will be described in other sections if needed. |
One of our main goals was to make SER really fast. There are many functions across
the server that need to work with strings. Usually these functions need to know string length. We wanted
to avoid using of strlen because the function is relatively slow. It
must scan the whole string and find the first occurrence of zero character. To avoid this, we created
str type. The type has 2 fields, field s is pointer
to the beginning of the string and field len is length of the string. We then
calculate length of the string only once and later reuse saved value.
![]() | str structure is quite important because it is widely used in SER (most functions accept str instead of char*). |
str Type Declaration
struct _str{
char* s;
int len;
};
typedef struct _str str; The declaration can be found in header file str.h.
![]() | Because we store string lengths, there is no need to zero terminate them. Some strings in the
server are still zero terminated, some are not. Be careful when using functions like
printf("%.*s", mystring->len, mystring->s);
That ensures that the string will be printed correctly even if there is no zero character at
the end.
|
hdr_fieldThe structure represents a header field of a SIP message. A header field consist of name and body separated by a double colon. For example: "Server: SIP Express Router\r\n" is one header field. "Server" is header field name and "SIP Express Router\r\n" is header field body.
The structure is defined in file hf.h under parser subdirectory.
Structure Declaration
struct hdr_field {
int type; /* Header field type */
str name; /* Header field name */
str body; /* Header field body */
void* parsed; /* Parsed data structures */
struct hdr_field* next; /* Next header field in the list */
};Field Description:
type - Type of the header field, the following header field
types are defined (and recognized by the parser):
HDR_VIA1, HDR_VIA2, HDR_TO, HDR_FROM, HDR_CSEQ, HDR_CALLID, HDR_CONTACT, HDR_MAXFORWARDS, HDR_ROUTE, HDR_RECORDROUTE, HDR_CONTENTTYPE, HDR_CONTENTLENGTH, HDR_AUTHORIZATION, HDR_EXPIRES, HDR_PROXYAUTH, HDR_WWWAUTH, HDR_SUPPORTED, HDR_REQUIRE, HDR_PROXYREQUIRE, HDR_UNSUPPORTED, HDR_ALLOW, HDR_EVENT, HDR_OTHER.
Their meaning is self explanatory. HDR_OTHER marks header field not recognized by the parser.
name - Name of the header field (the part before colon)
body - body of the header field (the part after colon)
parsed - Each header field body can be further parsed. The field
contains pointer to parsed structure if the header field was parsed already. The pointer is
of type void* because it can point to different types of structure depending on
the header field type.
next - Pointer to the next header field in linked list.
sip_uriThis structure represents parsed SIP URI.
struct sip_uri {
str user; /* Username */
str passwd; /* Password */
str host; /* Host name */
str port; /* Port number */
str params; /* Parameters */
str headers;
};Field Description:
user - Username if found in the URI.
passwd - Password if found in the URI.
host - Hostname of the URI.
params - Parameters of the URI if any.
headers - See the SIP RFC.
via_bodyThe structure represents parsed Via header field. See file parse_via.h under parser subdirectory for more details.
struct via_body {
int error;
str hdr; /* Contains "Via" or "v" */
str name;
str version;
str transport;
str host;
int port;
str port_str;
str params;
str comment;
int bsize; /* body size, not including hdr */
struct via_param* param_lst; /* list of parameters*/
struct via_param* last_param; /*last via parameter, internal use*/
/* shortcuts to "important" params*/
struct via_param* branch;
struct via_param* received;
struct via_body* next; /* pointer to next via body string if
compact via or null */
};Field Description:
error - The field contains error code when the parser was unable
to parse the header field.
hdr- Header field name, it can be "Via" or "v"
in this case.
name - Protocol name ("SIP" in this case).
version - Protocol version (for example "2.0").
transport - Transport protocol name ("TCP", "UDP"
and so on).
host - Hostname or IP address contained in the Via header field.
port - Port number as integer.
port_str - Port number as string.
params - Unparsed parameters (as one string containing all the parameters).
comment - Comment.
bsize - Size of the body (not including hdr).
param_lst - Linked list of all parameters.
last_param - Last parameter in the list.
branch - Branch parameter.
received - Received parameter.
next - If the Via is in compact form (more Vias in the same header
field), this field contains pointer to the next Via.
ip_addrThe structure represents IPv4 or IPv6 address. It is defined in ip_addr.h.
struct ip_addr{
unsigned int af; /* address family: AF_INET6 or AF_INET */
unsigned int len; /* address len, 16 or 4 */
/* 64 bits aligned address */
union {
unsigned int addr32[4];
unsigned short addr16[8];
unsigned char addr[16];
}u;
};lumpThe structure describes modifications that should be made to the message before the message will be sent.
The structure will be described in more detail later in chapter SIP Message Modifications.
lump_rplThe structure represents text that should be added to reply. List of such data is kept in the request and processed when the request is being turned into reply.
The structure will be described in more detail later in chapter SIP Message Modifications.
msg_startThe structure represents the first line of a SIP request or response.
The structure is defined in file parse_fline.h under parser subdirectory.
Structure Declaration
struct msg_start {
int type; /* Type of the Message - Request/Response */
union {
struct {
str method; /* Method string */
str uri; /* Request URI */
str version; /* SIP version */
int method_value; /* Parsed method */
} request;
struct {
str version; /* SIP version */
str status; /* Reply status */
str reason; /* Reply reason phrase */
unsigned int statuscode; /* Status code */
} reply;
}u;
};Description of Request Related Fields:
type - Type of the message - REQUEST or RESPONSE.
method - Name of method (same as in the message).
uri - Request URI.
version - Version string.
method_value - Parsed method. Field method which is of
type str will be converted to integer and stored here. This is good for comparison,
integer comparison is much faster then string comparison.
Description of Response Related Fields:
version - Version string.
status - Response status code as string.
reason - Response reason string as in the message.
statuscode - Response status code converted to integer.
sip_msgThis is the most important structure in the whole server. This structure represents a SIP message. When a message is received, it is immediately converted into this structure and all operations are performed over the structure. After the server finished processing, this structure is converted back to character array buffer and the buffer is sent out.
Structure Declaration:
struct sip_msg {
unsigned int id; /* message id, unique/process*/
struct msg_start first_line; /* Message first line */
struct via_body* via1; /* The first via */
struct via_body* via2; /* The second via */
struct hdr_field* headers; /* All the parsed headers*/
struct hdr_field* last_header; /* Pointer to the last parsed header*/
int parsed_flag; /* Already parsed header field types */
/* Via, To, CSeq, Call-Id, From, end of header*/
/* first occurrence of it; subsequent occurrences
* saved in 'headers'
*/
struct hdr_field* h_via1;
struct hdr_field* h_via2;
struct hdr_field* callid;
struct hdr_field* to;
struct hdr_field* cseq;
struct hdr_field* from;
struct hdr_field* contact;
struct hdr_field* maxforwards;
struct hdr_field* route;
struct hdr_field* record_route;
struct hdr_field* content_type;
struct hdr_field* content_length;
struct hdr_field* authorization;
struct hdr_field* expires;
struct hdr_field* proxy_auth;
struct hdr_field* www_auth;
struct hdr_field* supported;
struct hdr_field* require;
struct hdr_field* proxy_require;
struct hdr_field* unsupported;
struct hdr_field* allow;
struct hdr_field* event;
char* eoh; /* pointer to the end of header (if found) or null */
char* unparsed; /* here we stopped parsing*/
struct ip_addr src_ip;
struct ip_addr dst_ip;
char* orig; /* original message copy */
char* buf; /* scratch pad, holds a modified message,
* via, etc. point into it
*/
unsigned int len; /* message len (orig) */
/* modifications */
str new_uri; /* changed first line uri*/
int parsed_uri_ok; /* 1 if parsed_uri is valid, 0 if not */
struct sip_uri parsed_uri; /* speed-up > keep here the parsed uri*/
struct lump* add_rm; /* used for all the forwarded
* requests */
struct lump* repl_add_rm; /* used for all the forwarded replies */
struct lump_rpl *reply_lump; /* only for locally generated replies !!!*/
char add_to_branch_s[MAX_BRANCH_PARAM_LEN];
int add_to_branch_len;
/* index to TM hash table; stored in core to avoid unnecessary calcs */
unsigned int hash_index;
/* allows to set various flags on the message; may be used for
* simple inter-module communication or remembering processing state
* reached
*/
flag_t flags;
};Field Description:
id - Unique ID of the message within a process context.
first_line - Parsed first line of the message.
via1 - The first Via - parsed.
via2 - The second Via - parsed.
headers - Linked list of all parsed headers.
last_header - Pointer to the last parsed header (parsing is incremental,
that means that the parser will stop if all requested headers were found and next time it will
continue at the place where it stopped previously. Therefore this field will not point to the
last header of the message if the whole message hasn't been parsed yet).
parsed_flag - Already parsed header field types (bitwise OR).
The following fields are set to zero if the corresponding header field was not found in the message or hasn't been parsed yet. (These fields are called hooks - they always point to the first occurrence if there is more than one header field of the same type).
h_via1 - Pointer to the first Via header field.
h_via2 - Pointer to the second Via header field.
callid - Pointer to the first Call-ID header field.
to - Pointer to the first To header field.
cseq - Pointer to the first CSeq header field.
from - Pointer to the first From header field.
contact - Pointer to the first Contact header field.
maxforwards - Pointer to the first Max-Forwards header field.
route - Pointer to the first Route header field.
record_route - Pointer to the first Record-Route header field.
content_type - Pointer to the first Content-Type header field.
content_length - Pointer to the first Content-Length header field.
authorization - Pointer to the first Authorization header field.
expires - Pointer to the first Expires header field.
proxy_auth - Pointer to the first Proxy-Authorize header field.
www_auth - Pointer to the first WWW-Authorize header field.
supported - Pointer to the first Supported header field.
require - Pointer to the first Require header field.
proxy_require - Pointer to the first Proxy-Require header field.
unsupported - Pointer to the first Unsupported header field.
allow - Pointer to the first Allow header field.
event - Pointer to the first Event header field.
The following fields are mostly used internally by the server and should be modified through dedicated functions only.
eoh - Pointer to the End of Header or null if not found yet (the field
will be set if and only if the whole message was parsed already).
unparsed - Pointer to the first unparsed character in the message.
src_ip - Sender's IP address.
dst_ip - Destination's IP address.
orig - Original (unmodified) message copy, this field will hold
unmodified copy of the message during the whole message lifetime.
buf - Message scratch-pad (modified copy of the message) - All modifications
made to the message will be done here.
len - Length of the message (unmodified).
new_uri - New Request-URI to be used when forwarding the message.
parsed_uri_ok - 1 if parsed_uri is
valid, 0 if not.
parsed_uri - The original parsed Request URI, sometimes
it might be necessary to revert changes made to the Request URI and therefore we
store the original URI here.
add_rm - Linked list describing all modifications that will be made to
REQUEST before it will be forwarded. The list will be processed when the request
is being converted to character array (i.e. immediately before the request will be send out).
repl_add_rm - Linked list describing all modifications
that will be made to
REPLY before it will be forwarded. the list will be processed when the reply
is being converted to character array (i.e. immediately before the request will be send out).
reply_lump - This is list of data chunks that should be appended to
locally generated reply, i.e. when the server is generating local reply out of the request. A local
reply is reply generated by the server. For example, when processing of a request fails for some
reason, the server might generate an error reply and send it back to sender.
add_to_branch_s - String to be appended to branch parameter.
add_to_branch_len - Length of the string.
hash_index - Index to a hash table in TM module.
flags - Allows to set various flags on the message. May be used
for simple inter-module communication or remembering processing state reached.
In a previous section we discussed how routing part of a config file gets translated into binary representation. In this section, we will discuss how the binary representation is used during message processing.
Upon a SIP message receipt, the server performs some basic sanity checks and converts
the message into sip_msg structure. After that the Routing Engine will start
processing the message.
The routing engine can be found in file action.c.
The main function is run_actions. The
function accepts two parameters. The first parameter is list of actions to
be processed (Remember, the config file gets translated into array of linked
lists. Each linked list in the array represents one "route" part of the config
file). The second parameter is sip_msg structure
representing the message to be processed.
Upon a receipt of a request, the linked list representing the main route part will be processed so the first parameter will be rlist[0]. (The linked list of main route part is always at index 0).
The function will then sequentially call
do_action function for each element
of the linked list. Return value of the function is important. If the function
returns 0, processing of the list will be stopped. By returning 0 a command
can indicate that processing of the message should be stopped and the message will be dropped.
Modules may export so-called on_break handlers. on_break handler is a function, that will be called when processing of the linked-list is interrupted (ret == 0). All such handlers will be called when processing of the linked-list is finished and ret == 0.
do_action Function do_action function is core of the
routing engine. There is a big switch
statement. Each case of the statements is one command handled by the
server core itself.
The following commands are handled by the SER core
itself:
drop,
forward,
send,
log,
append_branch,
len_gt,
setflag,
resetflag,
isflagset,
error,
route,
exec,
revert_uri,
set_host,
set_hostport,
set_user,
set_userpass,
set_port,
set_uri,
prefix,
strip,
if,
module.
Each of the commands is represented by a case statement in the switch.
(For example, if you are interested in implementation of
drop command,
look at "case DROP_T:" statement in the function.
The respective commands will be described now.
drop - This command is very
simple, it simply returns 0 which will result in abortion of
processing of the request. No other commands after
drop will be executed.
forward - The function will
forward the message further. The message will be either forwarded
to the Request URI of the message or to
IP or host given as parameter.
In the first case, host in the Request URI
must be converted into corresponding IP address.
Function mk_proxy converts
hostname to corresponding IP address.
The message is then sent out using
forward_request function.
In the second case, hostname was converted to IP
address in fixup i.e. immediately after the config file was compiled
into its binary representation. The first parameter is pointer to
proxy structure created in the fixup and
therefore we only need to call
forward_request here to
forward the message further.
send - This functions sends
the message to a third-party host. The message will be sent out
as is - i.e. without Request URI and Via
altering.
Hostname or IP address of the third-party host is specified as a parameter of the function.
The message will be sent out using
udp_send directly.
log - The message given as
a parameter will be logged using system logger. It can be either
syslog or stderr
(depends on configuration). The message is logged
using LOG which is a macro
defined in dprint.h header
file.
append_branch - Append a new
URI for forking.
More than one destinations may be associated with a single SIP request. If the server was configured so, it will use all the destinations and fork the request.
The server keeps an array of all destinations, that should be
used when forking. The array and related functions can be found
in file dset.c. There is
function append_branch which adds a new destination to the set.
This command simply calls
append_branch function and
adds a new destination to the destination set.
len_gt - The command accepts
one number as a parameter. It then compares the number with length
of the message. If the message length is greater or equal then
the number then 1 will be returned otherwise the function returns -1.
setflag - Sets a flag in the
message. The command simply
calls setflags function that
will set the flag. Fore more information see file
flag.c.
resetflag - Same as command
setflag - only resetflag
will be called instead of setflag.
isflagset - Test if the flag
is set or not.
error - Log a message with
NOTICE log level.
route - Execute another
route statement.
As we have mentioned already, there can be more that one route statement in the config file. One of them is main (without number), the other are additional. This command makes it possible to execute an additional route statement.
The command accepts one parameter which is route statement number.
First sanity checks over the parameter will be performed. If
the checks passed, function
run_actions will be called.
The function accepts two parameters. The first one is linked list
to execute, the second one is sip_msg
structure representing the message to be processed.
As you might remember, each route statement was compiled into linked list of commands to be executed and head of the linked list was stored in rlist array. For example, head of linked list representing route statement with number 4 will be stored at position 4 in the array (position 0 is reserved for the main route statement).
So the command will simply call
run_actions(rlist[a->p1.number], msg)
and that will execute route statement with number given as parameter.
exec - Execute a shell command.
The command accepts one parameter of type char*. The string given as
parameter will be passed to system
function which will in turn execute
/bin/sh -c <string>.
revert_uri - Revert changes made to
the Request URI.
If there is a new URI stored in
new_uri of sip_msg structure,
it will be freed. The original Request URI will be used
when forwarding the message.
If there is a valid URI in
parsed_uri field of sip_msg
structure (indicated by parsed_uri_ok field), it
will be freed too.
set_host - Change hostname of Request
URI to value given as parameter.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
set_hostport - change hostname and port
of Request URI to value given as string parameter.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
set_user - Set username part of Request
URI to string given as parameter.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
set_userpass - Set username and password
part of Request URI to string given as parameter.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
set_port - Set port of Request
URI to value given as parameter.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
set_uri - Set a new Request
URI.
If there is a URI in new_uri field,
it will be freed. If there is a valid URI in
parsed_uri field, it will be freed too.
Then URI given as parameter will be stored in
new_uri field. (If new_uri
contains a URI it will be used instead of Request
URI when forwarding the message).
prefix - Set the parameter as username
prefix.
The string will be put immediately after "sip:" part of the Request URI.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
strip - Remove first n characters of
username in Request URI.
If there is a URI in new_uri
field, it will be modified, otherwise the original Request
URI will be modified.
if - if Statement.
There is an expression associated with the command and one or two linked lists of commands. The expression is a regular expression compiled into binary form in the fixup when the config file was compiled.
The expression will be evaluated now. If the result is > 0, the first linked
list will be executed using run_action
function. The linked list represents command enclosed in curly braces of
if command.
Otherwise, if there is the second list, it will be executed in the same way. The
second list represents commands of else
statement.
module - Execute a function exported by
a module.
When a command in a route statement is not recognized by the core itself (i.e. it is not one of commands handled by the core itself), list of exported functions of all loaded modules will be searched for a function with corresponding name and number of parameters.
If the function was found, module command
(this one) will be created and pointer to the function will be stored in
p1.data field.
So, this command will simply call function whose pointer is in
p1.data field and will pass 2 parameters to the
function. If one or both of the parameters were not used, 0 will be passed
instead.
Return value of the function will be returned as return value of
module command.
This command makes SER pretty extensible while the core itself is still reasonably small and clean. Additional functionality is put in modules and loaded only when needed.
In this section we will discuss internals of the SIP message header parser implemented in the server. Message parsing is very important and one of the most time consuming operations of a SIP server. We have been trying to make the parser as fast as possible.
A header field parser can be either in the server core or in a module. By convention, parser that is needed by the core itself or is needed by at least two modules will be in the core. Parsers contained in modules will be not described in this section.
There is a parser subdirectory that contains all the parsers and related stuff.
The following parsers can be found under parser subdirectory:
A SIP message consists of message header and optional message body. The header is separated from the body with a empty line (containing CRLF only).
Message header consists of the first line and one or more header fields. The first line determines type of the message. Header fields provide additional information that is needed by clients and servers to be able to process the message.
Each header field consists of header field name and header field body. Header field name is delimited from header field body by a colon (":"). For example, "Server: SIP Express Router" - in this case "Server" is header field name and "SIP Express Router" is header field body.
First Line Parser - Parses the first line of a SIP message.
Header Name Parser- Parsers Name part of a header field (part before colon).
To Header Parser - Parses body of To header field.
From Header Parser - Parses body of From header field.
CSeq Header Parser - Parses body of CSeq header field.
Event Header Parser - Parses body of Event header field.
Expires Header Parser - Parses body of Expires header field.
Via Header Parser - Parses body of Via header field.
Contact Header Parser - Parses body of Contact header field.
Digest Parser - Parses digest response.
The server implements what we call incremental parsing. It means that a header field will be not parsed unless it is really needed. There is a minimal set of header that will be parsed every time. The set includes:
The first line - the server must know if the message is request or response
Via header field - Via will be needed for sure. We must add ourself to Via list when forwarding the message.
Purpose of the parser is to parse the first line of a SIP
message. The first line is represented by msg_start
structure define in file parse_fline.h
under parser subdirectory.
The main function of the first line parser is
parse_first_line, the function will fill in
msg_start structure.
Follow inline comments in the function if you want to add support for a new message type.
The purpose of the header field type parser is to recognize type of a header field. The following types of header field will be recognized:
Via, To, From, CSeq, Call-ID, Contact, Max-Forwards, Route, Record-Route, Content-Type, Content-Length, Authorization, Expires, Proxy-Authorization, WWW-Authorization, supported, Require, Proxy-Require, Unsupported, Allow, Event.
All other header field types will be marked as HDR_OTHER.
Main function of header name parser is parse_hname2. The
function can be found in file parse_hname.c. The function
accepts pointers to begin and end of a header field and fills in hdf_field
structure. name field will point to the header field name,
body field will point to the header field body and
type field will contain type of the header field if known and HDR_OTHER
if unknown.
The parser is 32-bit, it means, that it processes 4 characters of header field name at time. 4 characters of a header field name are converted to an integer and the integer is then compared. This is much faster than comparing byte by byte. Because the server is compiled on at least 32-bit architectures, such comparison will be compiled into one instruction instead of 4 instructions.
We did some performance measurement and 32-bit parsing is about 3 times faster for a typical SIP message than corresponding automaton comparing byte by byte. Performance may vary depending on the message size, parsed header fields and header fields type. Test showed that it was always as fast as corresponding 1-byte comparing automaton.
Since comparison must be case insensitive in case of header field names, it is necessary to convert it
to lower case first and then compare. Since converting byte by byte would slow down the parser a lot, we
have implemented a hash table, that can again convert 4 bytes at once. Since set of keys that need to be
converted to lowercase is known (the set consists of all possible 4-byte parts of all recognized header
field names) we can precalculate size of the hash table to be synonym-less. That will simplify (and speed
up) the lookup a lot. The hash table must be initialized upon the server startup (function
init_hfname_parser).
The header name parser consists of several files, all of them are under parser subdirectory. Main file is parse_hname2.c - this files contains the parser itself and functions used to initialize and lookup the hash table. File keys.h contains automatically generated set of macros. Each macro is a group of 4 bytes converted to integer. The macros are used for comparison and the hash table initialization. For example, for Max-Forwards header field name, the following macros are defined in the file:
#define _max__ 0x2d78616d /* "max-" */ #define _maX__ 0x2d58616d /* "maX-" */ #define _mAx__ 0x2d78416d /* "mAx-" */ #define _mAX__ 0x2d58416d /* "mAX-" */ #define _Max__ 0x2d78614d /* "Max-" */ #define _MaX__ 0x2d58614d /* "MaX-" */ #define _MAx__ 0x2d78414d /* "MAx-" */ #define _MAX__ 0x2d58414d /* "MAX-" */ #define _forw_ 0x77726f66 /* "forw" */ #define _forW_ 0x57726f66 /* "forW" */ #define _foRw_ 0x77526f66 /* "foRw" */ #define _foRW_ 0x57526f66 /* "foRW" */ #define _fOrw_ 0x77724f66 /* "fOrw" */ #define _fOrW_ 0x57724f66 /* "fOrW" */ #define _fORw_ 0x77524f66 /* "fORw" */ #define _fORW_ 0x57524f66 /* "fORW" */ #define _Forw_ 0x77726f46 /* "Forw" */ #define _ForW_ 0x57726f46 /* "ForW" */ #define _FoRw_ 0x77526f46 /* "FoRw" */ #define _FoRW_ 0x57526f46 /* "FoRW" */ #define _FOrw_ 0x77724f46 /* "FOrw" */ #define _FOrW_ 0x57724f46 /* "FOrW" */ #define _FORw_ 0x77524f46 /* "FORw" */ #define _FORW_ 0x57524f46 /* "FORW" */ #define _ards_ 0x73647261 /* "ards" */ #define _ardS_ 0x53647261 /* "ardS" */ #define _arDs_ 0x73447261 /* "arDs" */ #define _arDS_ 0x53447261 /* "arDS" */ #define _aRds_ 0x73645261 /* "aRds" */ #define _aRdS_ 0x53645261 /* "aRdS" */ #define _aRDs_ 0x73445261 /* "aRDs" */ #define _aRDS_ 0x53445261 /* "aRDS" */ #define _Ards_ 0x73647241 /* "Ards" */ #define _ArdS_ 0x53647241 /* "ArdS" */ #define _ArDs_ 0x73447241 /* "ArDs" */ #define _ArDS_ 0x53447241 /* "ArDS" */ #define _ARds_ 0x73645241 /* "ARds" */ #define _ARdS_ 0x53645241 /* "ARdS" */ #define _ARDs_ 0x73445241 /* "ARDs" */ #define _ARDS_ 0x53445241 /* "ARDS" */
As you can see, Max-Forwards name was divided into three 4-byte chunks: Max-, Forw, ards. The file contains macros for every possible lower and upper case character combination of the chunks. Because the name (and therefore chunks) can contain colon (":"), minus or space and these characters are not allowed in macro name, they must be substituted. Colon is substituted by "1", minus is substituted by underscore ("_") and space is substituted by "2".
When initializing the hash table, all these macros will be used as keys to the hash table. One of each upper and lower case combinations will be used as value. Which one ?
There is a convention that each word of a header field name starts with a upper case character. For example, most of user agents will send "Max-Forwards", messages containing some other combination of upper and lower case characters (for example: "max-forwards", "MAX-FORWARDS", "mAX-fORWARDS") are very rare (but it is possible).
Considering the previous paragraph, we optimized the parser for the most common case. When all header fields have upper and lower case characters according to the convention, there is no need to do hash table lookups, which is another speed up.
For example suppose we are trying to figure out if the header field name is Max-Forwards and the header field name is formed according to the convention (i.e. "Max-Forwards"):
Get the first 4 bytes of the header field name ("Max-"), convert it to an integer and compare to "_Max__" macro. Comparison succeeded, continue with the next step.
Get next 4 bytes of the header field name ("Forw"), convert it to an integer and compare to "_Forw_" macro. Comparison succeeded, continue with the next step.
Get next 4 bytes of the header field name ("ards"), convert it to an integer and compare to "_ards_" macro. Comparison succeeded, continue with the next step.
If the following characters are spaces and tabs followed by a colon (or colon directly
without spaces and tabs), we found Max-Forwards header field name and can set
type field to HDR_MAXFORWARDS. Otherwise (other characters
than colon, spaces and tabs) it is some other header field and set
type field to HDR_OTHER.
As you can see, there is no need to do hash table lookups if the header field was formed according to the convention and the comparison was very fast (only 3 comparisons needed !).
Now lets consider another example, the header field was not formed according to the convention, for example "MAX-forwards":
Get the first 4 bytes of the header field name ("MAX-"), convert it to an integer and compare to "_Max__" macro.
Comparison failed, try to lookup "MAX-" converted to integer in the hash table. It was found, result is "Max-" converted to integer.
Try to compare the result from the hash table to "_Max__" macro. Comparison succeeded, continue with the next step.
Compare next 4 bytes of the header field name ("forw"), convert it to an integer and compare to "_Max__" macro.
Comparison failed, try to lookup "forw" converted to integer in the hash table. It was found, result is "Forw" converted to integer.
Try to compare the result from the hash table to "Forw" macro. Comparison succeeded, continue with the next step.
Compare next 4 bytes of the header field name ("ards"), convert it to integer and compare to "ards" macro. Comparison succeeded, continue with the next step.
If the following characters are spaces and tabs followed by a colon (or colon directly
without spaces and tabs), we found Max-Forwards header field name and can set
type field to HDR_MAXFORWARDS. Otherwise (other characters
than colon, spaces and tabs) it is some other header field and set
type field to HDR_OTHER.
In this example, we had to do 2 hash table lookups and 2 more comparisons. Even this variant is still very fast, because the hash table lookup is synonym-less, lookups are very fast.
Purpose of this parser is to parse body of To header field. The parser can be found in file parse_to.c under parser subdirectory.
Main function is parse_to but there is no need to call the
function explicitly. Every time the parser finds a To header field, this function will be called
automatically. Result of the parser is to_body structure. Pointer to the
structure will be stored in parsed field of
hdr_field structure. Since the pointer is void*, there is a
convenience macro get_to in file
parse_to.h that will do the necessary type-casting and will
return pointer to to_body structure.
The parser itself is a finite state machine that will parse To body according to the grammar defined
in RFC3261 and store result in to_body structure.
The parser gets called automatically from function get_hdr_field
in file msg_parser.c. The function first creates and initializes
an instance of to_body structure, then calls
parse_to function with the structure as a parameter and if
everything went OK, puts the pointer to the structure in parsed field of
hdr_field structure representing the parsed To header field.
The newly created structure will be freed when the message is being destroyed, see function
clean_hdr_field in file hf.c
for more details.
to_bodyThe structure represents parsed To body. The structure is declared in parse_to.h file.
Structure Declaration:
struct to_param{
int type; /* Type of parameter */
str name; /* Name of parameter */
str value; /* Parameter value */
struct to_param* next; /* Next parameter in the list */
};
struct to_body{
int error; /* Error code */
str body; /* The whole header field body */
str uri; /* URI */
str tag_value; /* Value of tag */
struct to_param *param_lst; /* Linked list of parameters */
struct to_param *last_param; /* Last parameter in the list */
}; Structure to_param is a temporary structure representing
a To URI parameter. Right now only TAG parameter will be marked
in type field. All other parameters will have the same
type.
Field Description:
error - Error code will be put here when parsing of To
body fails.
body - The whole header field body.
uri - URI of the To header field.
tag_value - Value of tag parameter if present.
param_lst - Linked list of all parameters.
last_param - Pointer to the last parameter in the linked list.
This parser is only a wrapper to the To header field parser. Since bodies of both header fields are identical, From parser only calls To parser.
The wrapper can be found in file parse_from.c under
parser subdirectory. There is only one function called
parse_from_header. The function accepts one parameter which
is pointer to structure representing the From header field to be parsed. The function creates an
instance of to_body structure and initializes it. It then calls
parse_to function and if everything went OK, the pointer to
the newly created structure will be put in parsed field of the structure
representing the parsed header field.
The newly created structure will be freed when the whole message is being destroyed. (See To header field parser description for more details).
From parser must be called explicitly !
If the main parser finds a From header field, it will not parse the header field body
automatically. It is up to you to call the
parse_from_header when you want to parse a From
header field body.
Purpose of this parser is to parse body of CSeq header field. The parser can be found in file parse_cseq.c under parser subdirectory.
Main function is parse_cseq but there is no need to call the
function explicitly. Every time the parser finds a CSeq header field, this function will be called
automatically. Result of the parser is cseq_body structure. Pointer to the
structure will be stored in parsed field of
hdr_field structure. Since the pointer is void*, there is a
convenience macro get_cseq in file
parse_cseq.h that will do the necessary type-casting and will
return pointer to cseq_body structure.
The parser will parse CSeq body according to the grammar defined in RFC3261 and
store result in cseq_body structure.
The parser gets called automatically from function get_hdr_field
in file msg_parser.c. The function first creates and initializes
an instance of cseq_body structure, then calls
parse_cseq function with the structure as a parameter and if
everything went OK, puts the pointer to the structure in parsed field of
hdr_field structure representing the parsed CSeq header field.
The newly created structure will be freed when the message is being destroyed, see function
clean_hdr_field in file hf.c
for more details.
cseq_bodyThe structure represents parsed CSeq body. The structure is declared in parse_cseq.h file.
Structure Declaration:
struct cseq_body{
int error; /* Error code */
str number; /* CSeq number */
str method; /* Associated method */
};Field Description:
error - Error code will be put here when parsing of CSeq
body fails.
number - CSeq number as string.
method - CSeq method.
Purpose of this parser is to parse body of an Event Header field. The parser can be found in file parse_event.c under parser subdirectory.
![]() | This is NOT fully featured Event body parser ! The parser was written for Presence Agent module only and thus can recognize Presence package only. No subpackages will be recognized. All other packages will be marked as "OTHER". The parser should be replace by a more generic parser if subpackages or parameters should be parsed too. |
Main function is parse_event in file
parse_event.c. The function will create an instance of
event_t structure and call the parser. If everything went OK, pointer
to the newly created structure will be stored in parsed field of
hdr_field structure representing the parsed header field.
As usually, the newly created structure will be freed when the whole message is being destroyed.
See function clean_hdr_field in file
hf.c.
The parser will be not called automatically when the main parser finds an Event header field.
It is up to you to call the parser when you really need the body of the header field to be
parsed (call parse_event function).
event_tThe structure represents parsed Event body. The structure is declared in parse_event.h file.
Structure Declaration:
#define EVENT_OTHER 0
#define EVENT_PRESENCE 1
typedef struct event {
str text; /* Original string representation */
int parsed; /* Parsed variant */
} event_t;Field Description:
text - Package name as text.
parsed - Package name as integer. It will be EVENT_PRESENCE
for presence package and EVENT_OTHER for rest.
The parser parses body of Expires header field. The body is very simple, it consists of number only. so the parser only removes any leading tabs and spaces and converts the number from string to integer. That's it.
The parser can be found in file parse_expires.c under
parser subdirectory. Main function is
parse_expires. The function is not called automatically
when an Expires header field was found. It is up to you to call the function if you need the body
to be parsed.
The function creates a new instance of exp_body_t structure and calls the
parser. If everything went OK, pointer to the newly created structure will be saved in
parsed field of the hdr_field structure representing
the parsed header field.
exp_body_tThe structure represents parsed Expires body. The structure is declared in parse_expires.h file.
Structure Declaration:
typedef struct exp_body {
str text; /* Original text representation */
int val; /* Parsed value */
} exp_body_t;Field Description:
text - Expires value as text.
val - Expires value as integer.
Purpose of this parser is to parse body of Via header field. The parser can be found in file parse_via.c under parser subdirectory.
Main function is parse_via but there is no need to call the
function explicitly. Every time the parser finds a Via header field, this function will be called
automatically. Result of the parser is via_body structure. Pointer to the
structure will be stored in parsed field of
hdr_field structure representing the parsed header field.
The parser itself is a finite state machine that will parse Via body according to the grammar defined
in RFC3261 and store result in via_body structure.
The parser gets called automatically from function get_hdr_field
in file msg_parser.c. The function first creates and initializes
an instance of via_body structure, then calls
parse_via function with the structure as a parameter and if
everything went OK, puts the pointer to the structure in parsed field of
hdr_field structure representing the parsed Via header field.
The newly created structure will be freed when the message is being destroyed, see function
clean_hdr_field in file hf.c
for more details.
Structure via_body is described in section
Structure via_body.
The parser is located under parser/contact subdirectory. The parser is not called automatically when the main parser finds a Contact header field. It is your responsibility to call the parser if you want a Contact header field body to be parsed.
Main function is parse_contact in file
parse_contact.c. The function accepts one parameter which
is structure hdr_field representing the header field to be parsed. A single
Contact header field may contain multiple contacts, the parser will parse all of them and will create
linked list of all such contacts.
The function creates and initializes an instance of contact_body structure.
Then function contact_parser will be called. If everything went
OK, pointer to the newly created structure will be stored in parsed field
of the hdr_field structure representing the parsed header field.
Function contact_parser will then check if the contact is star, if
not it will call parse_contacts function that will parse all
contacts of the header field.
Function parse_contacts can be found in file
contact.c. It extracts URI and parses all
contact parameters.
The Contact parameter parser can be found in file cparam.c.
The following structures will be created during parsing:
![]() | Mind that none of string in the following structures is zero terminated ! Be very careful when processing the strings with functions that require zero termination (printf for example) ! |
typedef struct contact_body {
unsigned char star; /* Star contact */
contact_t* contacts; /* List of contacts */
} contact_body_t; This is the main structure. Pointer to instance of this structure will be stored in
parsed field of structure representing the header field to be parsed.
The structure contains two field:
star field - This field will contain 1 if the Contact
was star (see RFC3261 for more details).
contacts field - This field contains pointer to linked
list of all contacts found in the Contact header field.
typedef struct contact {
str uri; /* contact uri */
cparam_t* q; /* q parameter hook */
cparam_t* expires; /* expires parameter hook */
cparam_t* method; /* method parameter hook */
cparam_t* params; /* List of all parameters */
struct contact* next; /* Next contact in the list */
} contact_t;This structure represents one Contact (Mind that there might be several contacts in one Contact header field delimited by a comma). Its fields have the following meaning:
uri - This field contains pointer to begin of
URI and its length.
q - This is a hook to structure representing q parameter.
If there is no such parameter, the hook contains 0.
expires - This is a hook to structure representing expires
parameter. If there is no such parameter, the hook contains 0.
method - This is a hook to structure representing method
parameter. If there is no such parameter, the hook contains 0.
params - Linked list of all parameters.
next - Pointer to the next contact that was in the same
header field.
typedef enum cptype {
CP_OTHER = 0, /* Unknown parameter */
CP_Q, /* Q parameter */
CP_EXPIRES, /* Expires parameter */
CP_METHOD /* Method parameter */
} cptype_t;This is an enum of recognized types of contact parameters. Q parameter will have type set to CP_Q, Expires parameter will have type set to CP_EXPIRES and Method parameter will have type set to CP_METHOD. All other parameters will have type set to CP_OTHER.
/*
* Structure representing a contact
*/
typedef struct cparam {
cptype_t type; /* Type of the parameter */
str name; /* Parameter name */
str body; /* Parameter body */
struct cparam* next; /* Next parameter in the list */
} cparam_t;This structure represents a contact parameter. Field description follows:
type - Type of the parameter, see cptype
enum for more details.
name - Name of the parameter (i.e. the part before
"=").
body - Body of the parameter (i.e. the part after
"=").
next - Next parameter in the linked list.
Purpose of this parser is to parse digest response. The parser can be found under parser/digest subdirectory. There might be several header fields containing digest response, for example Proxy-Authorization or W