Professional Documents
Culture Documents
Kamailio_devel_guide_2011
Kamailio_devel_guide_2011
Kamailio_devel_guide_2011
0 Development Guide
Daniel-Constantin Mierla
<http://www.asipto.com>
Elena-Ramona Modroiu
<http://www.asipto.com>
Copyright © 2011 ASIPTO
Abstract
This book documents the internal architecture of Kamailio SIP Server, providing the details useful to develop extensions in the core or as
a module.
The focus will be on major components of the SIP server, such as memory manager, locking system, parser, database API, configuration
file, MI commands, pseudo-variables and module interface.
Examples, API and the architecture are based on current stable version of Kamailio - 3.2.0 - at October 30, 2011. It is also valid for
development version - 3.3.0 - at this date. The document will be timely updated accordingly, check for updates at www.asipto.com or
www.kamailio.org.
This document is free to use for anybody. The authors are not liable in any way for the consequences you may get due to usage of this
document.
Table of Contents
1. Introduction
1.1. Kamailio SIP Server
1.2. About the authors
1.3. Important
2. Kamailio Architecture
2.1. Kamailio Core
2.2. Kamailio Modules
2.3. SIP Message Processing
3. Locking system
3.1. Simple Locks API
3.1.1. gen_lock_t
3.1.2. lock_alloc(...)
3.1.3. lock_dealloc(...)
3.1.4. lock_init(...)
3.1.5. lock_destroy(...)
3.1.6. lock_get(...)
3.1.7. lock_try(...)
3.1.8. lock_release(...)
3.2. Lock Set API
3.2.1. gen_lock_set_t
3.2.2. lock_set_alloc(...)
3.2.3. lock_set_dealloc(...)
3.2.4. lock_set_init(...)
3.2.5. lock_set_destroy(...)
3.2.6. lock_set_get(...)
3.2.7. lock_set_get(...)
3.2.8. lock_set_release(...)
3.3. Troubleshooting
4. Memory Manager
4.1. Private Memory
4.1.1. pkg_malloc(...)
4.1.2. pkg_free(...)
4.1.3. pkg_realloc(...)
4.2. Shared Memory
4.2.1. shm_malloc(...)
4.2.2. shm_free(...)
4.2.3. shm_realloc(...)
4.3. Troubleshooting
5. Data structures
5.1. str
5.2. struct sip_uri
5.3. struct sip_msg
5.4. struct msg_start
5.5. struct hdr_field
5.6. struct to_body
5.7. struct via_body
6. SIP Parser
6.1. parse_uri(...)
6.2. parse_msg(...)
6.3. parse_headers(...)
6.4. parse_to(...)
6.5. Get Message Body
6.6. Get Header Body
6.7. New Header Parsing Function
7. Transport Layer
7.1. DNS Implementation
9. Database API
9.1. DB1 API Structure
9.2. DB1 API Functions
9.2.1. Function init(...)
9.2.2. Function close(...)
9.2.3. Function use_table(...)
9.2.4. Function query(...)
9.2.5. Function fetch_result(...)
9.2.6. Function raw_query(...)
9.2.7. Function free_result(...)
9.2.8. Function insert(...)
9.2.9. Function delete(...)
9.2.10. Function update(...)
9.2.11. Function replace(...)
9.2.12. Function last_inserted_id(...)
9.2.13. Function insert_update(...)
9.2.14. Function insert_delayed(...)
9.2.15. Function affected_rows(...)
9.3. DB API Data Types
9.3.1. Type db_key_t
9.3.2. Type db_op_t
9.3.3. Type db_type_t
9.3.4. Type db_val_t
9.3.5. Type db_con_t
9.3.6. Type db_row_t
9.3.7. Type db1_res_t
9.4. Macros
9.5. Example of usage
11. Pseudo-variables
11.1. Naming Format
11.2. Data structures
11.2.1. Type pv_value_t
11.2.2. Type pv_name_t
11.2.3. Type pv_index_t
11.2.4. Type pv_param_t
11.2.5. Type pv_spec_t
11.2.6. Type pv_export_t
11.3. Adding a pseudo-variables
12. Transformations
12.1. Naming Format
12.2. Data Structures
12.3. Adding a Transformation
13. Statistics
13.1. Statistic Macros
14. Data Lumps
15. Timer
15.1. Data Types
15.2. Timer API Functions
15.3. Example of usage
15.4. Dedicated timer process
18. Licensing
19. References
List of Figures
Chapter 1. Introduction
The website of the project is http://www.kamailio.org. The source code up to version 1.5.x was hosted on sourceforge.net SVN
repository. Starting with version 3.0.0, the source code is hosted on sip-router.org GIT repository.
In November 2008, the development teams of Kamailio and SIP Express Router (SER) joined again their efforts and started to work
together to integrate the two SIP server applications. The integration concluded with release of v3.0.0, which represents a single source
code tree for both applications. In other words, Kamailio and SER are the same application from source code point of view, the
difference is done by the name chosen to build the application and the modules loaded in the default configuration file.
The number of registered developers and packagers exceeded 90 since the start of the project in 2001. During the last year, over 30
registered developers contributed code to project. The level of contributions and the amount of contributors has an important impact on
the evolution of the project. The book tries to ease the understanding of Kamailio from a developer point of view, giving the kick start
knowledge, it does not intend to be a cookbook. Efforts to improve the documentation in the sources is undertaken and make it
doxygen compliant, making a good developer documentation out of there.
Elena-Ramona Modroiu is one of the co-founders of Kamailio SIP Server project. She got involved in VoIP and SIP while working at
her graduation thesis within SIP Express Router (SER) project at FhG FOKUS Institute. She completed studies at Polytechnic University
of Valencia and Siemens Germany, working now at ASIPTO, being an active developer and member of management board of Kamailio.
The two authored many online tutorials about Kamailio, among them: Kamailio Core Cookbook, Kamailio Transformations Cookbook,
Kamailio Pseudo-Variables Cookbook, Kamailio and Asterisk Integration , Kamailio and FreeSWITCH Integration , SIP Routing in Lua with
Kamailio, Secure VoIP with Kamailio , IPv4 - IPv6 VoIP bridging with Kamailio , Kamailio and FreeRADIUS.
1.3. Important
This document is focusing only to Kamailio specific API, has no intention to teach C programing for Linux and Networking. You, as a
reader, should have already the basic knowledge of C programming.
There are many references to parts of code in the source tree. You must be familiar with the directory structure of Kamailio. It is not our
intention to explain how something was implemented, but how to use existing code to extend Kamailio easily.
The source code remains the best reference for developers. In the last time, the comments around the important functions in Kamailio
have been improved and converted to doxygen format. You should double-check the source code if the prototype of the functions
presented in this document are still valid.
the core - it is the component that provides the low-level functionalities for Kamailio.
Starting with version 3.0.0, Kamailio core includes so called internal libraries. They collect code shared by several modules but
which does not have a general purpose to be part of main core.
the modules - are the components that provides the most of the functionalities that make Kamailio powerful in real world
deployments.
The architecture for Kamailio v1.5.x (or older) is shown in the next figure.
memory manager
locking system
DNS and transport layer management (UDP, TCP, TLS, SCTP)
stateless forwarding
timer API
statistics engine
stateless replying
The locks can be used as simple variables or lock sets (array of simple locks). To improve the speed, behind the locks is, by default,
machine-specific code. If the architecture of the machine is unknown, Kamailio will use SysV semaphores.
To use the locking system in your C code you have to include the headers file: locking.h.
Data types and available functions to do operations with locks are described in the next sections.
3.1.1. gen_lock_t
It is the type to define a lock variable. It must be allocated in shared memory, to be available across Kamailio processes.
...
#include "locking.h"
gen_lock_t lock;
...
3.1.2. lock_alloc(...)
Allocates a lock in shared memory. If your gen_lock_t is not part of a structure allocated in shared memory, you have to use this
function to properly create the lock.
...
gen_lock_t* lock_alloc();
...
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
...
Lock allocation can be skipped if the lock is already in shared memory -- BUT be sure the lock content IS in shared memory.
...
struct s {
int a;
gen_lock_t lock;
} *x;
...
void lock_dealloc(gen_lock_t* lock);
...
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
/* make use of lock */
...
lock_dealloc(lock);
...
3.1.4. lock_init(...)
Initialize the lock. You must call this function before the first set operation on the lock.
...
gen_lock_t* lock_init(gen_lock_t* lock);
...
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
if(lock_init(lock)==NULL)
{
LM_ERR("cannot init the lock\n");
lock_dealloc(lock);
exit;
}
/* make use of lock */
...
lock_dealloc(lock);
...
3.1.5. lock_destroy(...)
Destroy internal attributes of gen_lock_t. You must call it before deallocating a lock to ensure proper clean up (if SysV is used, it calls
the functions to remove the semaphores).
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
if(lock_init(lock)==NULL)
{
LM_ERR("cannot init the lock\n");
lock_dealloc(lock);
exit;
}
/* make use of lock */
...
lock_destroy(lock);
lock_dealloc(lock);
...
3.1.6. lock_get(...)
Perform set operation on a lock. If the lock is already set, the function waits until the lock is unset.
...
void lock_get(gen_lock_t* lock);
...
The parameter must be initialized with lock_init(...) before calling this function.
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
if(lock_init(lock)==NULL)
{
LM_ERR("cannot init the lock\n");
lock_dealloc(lock);
exit;
}
/* make use of lock */
lock_get(lock);
/* under lock protection */
...
lock_destroy(lock);
lock_dealloc(lock);
...
3.1.7. lock_try(...)
Try to perform set operation on a lock. If the lock is already set, the function returns -1, otherwise sets the lock and returns 0. This is a
non-blocking lock_get().
The parameter must be initialized with lock_init(...) before calling this function.
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
if(lock_init(lock)==NULL)
{
LM_ERR("cannot init the lock\n");
lock_dealloc(lock);
exit;
}
/* make use of lock */
if(lock_try(lock)==0) {
/* under lock protection */
...
} else {
/* NO lock protection */
...
}
...
lock_destroy(lock);
lock_dealloc(lock);
...
3.1.8. lock_release(...)
Perform unset operation on a lock. If the lock is set, it will unblock it. You should call it after lock_set(...), after finishing the operations
that needs synchronization and protection against race conditions.
...
void lock_release(gen_lock_t* lock);
...
...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
LM_ERR("cannot allocate the lock\n");
exit;
}
if(lock_init(lock)==NULL)
{
LM_ERR("cannot init the lock\n");
lock_dealloc(lock);
exit;
}
/* make use of lock */
lock_get(lock);
/* under lock protection */
...
lock_release(lock);
...
lock_destroy(lock);
lock_dealloc(lock);
...
3.2. Lock Set API
The lock set is an array of gen_lock_t. To create a lock set, you have to define a variable of type gen_lock_set_t, allocate it in shared
memory specifying the number of locks in the set, then initialize it. You can perform set/unset operations, providing the lock set variable
and destroying the variable when you finish using it.
To use the lock sets in your C code you have to include the headers file: locking.h.
Data types and available functions to do operations with lock sets are described in the next sections.
3.2.1. gen_lock_set_t
It is the type to define a lock set variable. It must be allocated in shared memory, to be available across Kamailio processes.
...
#include "locking.h"
gen_lock_set_t lock_set;
...
3.2.2. lock_set_alloc(...)
Allocates a lock set in shared memory.
...
gen_lock_set_t* lock_set_alloc(int n);
...
The parameter n specifies the number of locks in the set. Return pointer to the lock set, or NULL if the set couldn't be allocated.
...
#include "locking.h"
...
gen_lock_set_t *set;
set = lock_set_alloc(16);
if(set==NULL)
{
LM_ERR("cannot allocate the lock set\n");
exit;
}
...
3.2.3. lock_set_dealloc(...)
Free the memory allocated for a lock set.
...
void lock_set_dealloc(gen_lock_set_t* set);
...
3.2.4. lock_set_init(...)
Initialized the internal attributes of a lock set. The lock set has to be allocated with lock_set_alloc(...). This function must be called
before performing any set/unset operation on lock set.
...
gen_lock_set_t* lock_set_init(gen_lock_set_t* set);
...
The parameter is an allocated lock set. It returns NULL if the lock set couldn't be initialized, otherwise returns the pointer to the lock set.
...
#include "locking.h"
...
gen_lock_set_t *set;
set = lock_set_alloc(16);
if(set==NULL)
{
LM_ERR("cannot allocate the lock set\n");
exit;
}
if(lock_set_init(set)==NULL)
{
LM_ERR("cannot initialize the lock set'n");
lock_set_dealloc(set);
exit;
}
/* make usage of lock set */
...
lock_set_dealloc(set);
...
3.2.5. lock_set_destroy(...)
Destroy the internal structure of the lock set. You have to call it once you finished to use the lock set. The function must be called after
lock_set_init(...)
...
void lock_set_destroy(gen_lock_set_t* set);
...
The parameter is an initialized lock set. After calling this function you should not perform anymore set/unset operations on lock set.
3.2.6. lock_set_get(...)
Set (block) a lock in the lock set. You should call this function after the lock set has been initialized. If the lock is already set, the
function waits until that lock is unset (unblocked).
...
void lock_set_get(gen_lock_set_t* set, int i);
...
First parameter is the lock set. The second is the index withing the set of the lock to be set. First lock in set has index 0. The index
parameter must be between 0 and n-1 (see lock_set_alloc(...).
...
#include "locking.h"
...
gen_lock_set_t *set;
set = lock_set_alloc(16);
if(set==NULL)
{
LM_ERR("cannot allocate the lock set\n");
exit;
}
if(lock_set_init(set)==NULL)
{
LM_ERR("cannot initialize the lock set'n");
lock_set_dealloc(set);
exit;
}
/* make usage of lock set */
lock_set_get(set, 8);
/* under lock protection */
...
lock_set_destroy(set);
lock_set_dealloc(set);
...
3.2.7. lock_set_get(...)
Try to set (block) a lock in the lock set. You should call this function after the lock set has been initialized. If the lock is already set, the
function returns -1, otherwise it sets the lock and returns 0. This is a non-blocking lock_set_get().
...
int lock_set_try(gen_lock_set_t* set, int i);
...
First parameter is the lock set. The second is the index withing the set of the lock to be set. First lock in set has index 0. The index
parameter must be between 0 and n-1 (see lock_set_alloc(...).
3.2.8. lock_set_release(...)
Unset (unblock) a lock in the lock set.
...
void lock_set_release(gen_lock_set_t* set, int i);
...
First parameter is the lock set. The second is the index withing the set of the lock to be unset. First lock in set has index 0. The index
parameter must be between 0 and n-1 (see lock_set_alloc(...).
...
#include "locking.h"
...
gen_lock_set_t *set;
set = lock_set_alloc(16);
if(set==NULL)
{
LM_ERR("cannot allocate the lock set\n");
exit;
}
if(lock_set_init(set)==NULL)
{
LM_ERR("cannot initialize the lock set'n");
lock_set_dealloc(set);
exit;
}
/* make usage of lock set */
lock_set_get(set, 8);
/* under lock protection */
...
lock_set_release(set, 8);
...
lock_set_destroy(set);
lock_set_dealloc(set);
...
3.3. Troubleshooting
A clear sign of issues with the locking is that one or more Kamailio processes eat lot of CPU. If the traffic load does not justify such
behavior and no more SIP messages are processed, the only solution is to troubleshoot and fix the locking error. The problem is that a
lock is set but never unset. A typical case is when returning due to an error and forgetting to release a previously lock set.
To troubleshoot a solution is to use gdb, attach to the process that eats lot of CPU and get the backtrace. You need to get the PID of
that Kamailio process - top or ps tools can be used.
...
# gdb /path/to/kamailio PID
# gdb> bt
...
From the backtrace you should get to the lock that is set and not released. From there you should start the investigation - what are the
cases to set that lock and in which circumstances it does not get released.
The manager is initialized at start-up, creating the chunks for private and shared memory. It is important to know that the shared
memory is not available during configuration parsing, that includes the setting of module parameters.
When the own memory manager cannot be used, Kamailio falls back to the SysV shared memory system.
Shortly, the manager reserves a big chunk of system memory for itself at start-up, then it allocates parts inside the chunk as visible in
the following figure.
To store static values in private memory and have it in all processes without the need to synchronize for accessing it, you must create it
before Kamailio forks.
To use the private memory manager you have to include the file: mem/mem.h.
4.1.1. pkg_malloc(...)
Allocates space in private memory.
...
void* pkg_malloc(unsigned int size);
...
The parameter specifies the size of memory space to be allocated. Returns the pointer to memory if the the operation succeeds, NULL
otherwise.
Example 4.2. Example of usage
...
#include "mem/mem.h"
...
char *p;
p = (char*)pkg_malloc(8*sizeof(char));
if(p==NULL)
{
LM_ERR("cannot allocate pkg memory\n");
exit;
}
...
4.1.2. pkg_free(...)
Free allocated private memory.
...
void *pkg_free(void *p);
...
...
#include "mem/mem.h"
...
char *p;
p = (char*)pkg_malloc(8*sizeof(char));
if(p==NULL)
{
LM_ERR("cannot allocate pkg memory\n");
exit;
}
strcpy(p, "kamailio");
LM_DBG("string value at %p is [%s]\n", p, p);
pkg_free(p);
...
4.1.3. pkg_realloc(...)
Realloc a previously allocated memory chunk. It copies the content of the old memory chunk to the new one. If the space after the old
chunk is free and large enough to scale to the new size, the Kamailio memory manager will set the size of the old chunk to the new size,
marking properly the memory zone, in this way, the copy operation is skipped.
...
void *pkg_realloc(void *p, unsigned int size);
...
The first parameter is the pointer to the memory space that needs to be re-sized. The second parameter is the new size in bytes. The
function return the pointer to the new memory space, or NULL if an error occurred. Beware that the returned pointer may be different
than the old pointer.
pkg_free(p);
...
The shared memory is initialized after the config file is parsed, because it need to know the user and group Kamailio is running under,
for the case when the memory manger uses SysV operations.
To use shared memory functions in your C code you need to include the file: mem/shm_mem.h. When accessing shared memory
data, you need to make sure that you don't have a race between different Kamailio processes, for example protect the access via a lock.
4.2.1. shm_malloc(...)
Allocates space in shared memory.
...
void *shm_malloc(unsigned int size);
...
The parameter specifies the size in bytes of the desired shared memory space. It returns the pointer to shared memory in case of
success, or NULL if an error occurred.
...
#include "mem/shm_mem.h"
...
char *p;
p = (char*)shm_malloc(8*sizeof(char));
if(p==NULL)
{
LM_ERR("cannot allocate shm memory\n");
exit;
}
...
4.2.2. shm_free(...)
Free a shared memory space previously allocated with shm_share(...).
...
void shm_free(void *p);
...
4.2.3. shm_realloc(...)
Realloc a previously allocated shared memory chunk. It copies the content of the old memory chunk to the new one.
...
void *shm_realloc(void *p, unsigned int size);
...
The first parameter is the pointer to the memory space that needs to be re-sized. The second parameter is the new size in bytes. The
function return the pointer to the new memory space, or NULL if an error occurred. Beware that the returned pointer may be different
than the old pointer.
...
#include "mem/shm_mem.h"
...
char *p;
p = (char*)shm_malloc(8*sizeof(char));
if(p==NULL)
{
LM_ERR("cannot allocate shm memory\n");
exit;
}
strcpy(p, "kamailio");
LM_DBG("string value at %p is [%s]\n", p, p);
p = (char*)shm_realloc(p, 16*sizeof(char));
if(p==NULL)
{
LM_ERR("cannot re-allocate shm memory\n");
exit;
}
strcat(p, " server");
LM_DBG("string value at %p is [%s]\n", p, p);
shm_free(p);
...
4.3. Troubleshooting
There are two cases of memory problems:
memory leak - allocating memory at runtime and don't free it when no longer needing. It results in out of memory messages.
Note that such messages might be because of a too small size of the memory for the traffic, data or number of subscribers that
Kamailio has to handle -- you can increase shared memory using -m command line option and private memory using -M command
line option.
memory overwriting - writing more than was allocated for that structure. It results in an segmentation fault, crashing Kamailio.
Kamailio has an internal debugger for memory - it is able to show the chunks allocated in private or shared memory and the file and line
from where it was allocated. In debugging mode it prints all calls for allocations and freeing memory.
To enable the memory debugger you have to recompile Kamailio with MEMDBG=1 option to make tool.
...
make cfg FLAVOUR=kamailio MEMDBG=1 ...
make all
...
Once compiled and installed with memory debugging you have to set memlog parameter to a value lower than debug in configuration
file. You can start Kamailio and try to reproduce the errors again. Once memory leak errors are printed you can either send a RPC
command to the process that printed the messages or stop Kamailio. You get in the syslog file the status of the memory. If you see
memory allocation done from the same place in the sources, many times, at runtime, it is a memory leak. If not, increase the memory
size to fit your load needs and run again -- if you don't get the memory leak errors it was the case of insufficient memory allocated for
Kamailio.
For sending the RPC command, you have to load ctl and cfg_rpc modules, then use sercmd tool:
...
sercmd cfg.set_now_int core mem_dump_pkg [pid]
...
sercmd cfg.set_now_int core mem_dump_shm [pid]
...
...
sercmd> core.shmmem
{
total: 33554432
free: 30817888
used: 2512248
real_used: 2736544
max_used: 2736544
fragments: 1
}
...
sercmd> pkg.stats
{
entry: 0
pid: 60090
rank: 0
used: 217280
free: 3674296
real_used: 520008
}
{
entry: 1
pid: 60091
rank: 1
used: 225160
free: 3666320
real_used: 527984
}
...
It is possible to walk through the list of PKG fragments with gdb. For example, printing used fragment in the range 2000 to 10000:
...
set $i=0
set $a = mem_block->first_frag
while($i<10000)
if($i>2000)
if($a->u.is_free==0)
p *$a
end
end
set $a = ((struct qm_frag*)((char*)($a)+sizeof(struct qm_frag)+((struct qm_frag*)$a)->size+sizeof(struct qm_frag_end)))
set $i = $i + 1
end
...
For memory overwriting a core should be generated. If yes, you can investigate it with gdb.
...
# gdb /path/to/kamailio corefile
...
From the backtrace you should get the file and line where the overwriting happened. In case a core is not generated, check the
messages in the syslog. Look for BUG and error, for head or tail of a memory chunk being overwriting.
5.1. str
SIP is a text-based protocol, therefore lot of operations resume to text manipulation. Kamailio uses references in the SIP message body
most of the time, doing it via an anchor pointer and the length. For that it uses the str structure.
...
struct _str{
char* s; /* pointer to the beginning of string (char array) */
int len; /* string length */
};
...
#include "str.h"
...
str s;
s.s = "kamailio";
s.len = strlen(s.s);
LM_DBG("the string is [%.*s]\n", s.len, s.s);
...
...
struct sip_uri {
str user; /* Username */
str passwd; /* Password */
str host; /* Host name */
str port; /* Port number */
str params; /* URI Parameters */
str headers; /* URI Headers */
unsigned short port_no; /* Port number r*/
unsigned short proto; /* Transport protocol */
uri_type type; /* URI scheme */
/* parameters */
str transport; /* transport parameter */
str ttl; /* ttl parameter */
str user_param; /* user parameter */
str maddr; /* maddr parameter */
str method; /* method parameter */
str lr; /* lr parameter */
str r2; /* specific rr parameter */
/* values */
str transport_val; /* value of transport parameter */
str ttl_val; /* value of ttl parameter */
str user_param_val; /* value of user parameter */
str maddr_val; /* value of maddr parameter */
str method_val; /* value of method parameter */
str lr_val; /* value of lr parameter */
str r2_val; /* value of r2 parameter */
};
...
Members of the structure corresponds to a part of a SIP URI. To get details about the format of SIP URI read RFC3261. Example of SIP
URI:
sip:alice@sipserver.org:5060;transport=tcp
struct receive_info rcv; /* source and dest ip, ports, proto a.s.o*/
/* modifications */
str new_uri; /* changed first line uri, when you change this
* don't forget to set parsed_uri_ok to 0 */
/* current uri */
int parsed_uri_ok; /* 1 if parsed_uri is valid, 0 if not, set it to 0
if you modify the uri (e.g change new_uri)*/
struct sip_uri parsed_uri; /* speed-up > keep here the parsed uri*/
/* flags used by core - allows to set various flags on the message; may
* be used for simple inter-module communication or remembering
* processing state reached */
* processing state reached */
unsigned int msg_flags;
str set_global_address;
str set_global_port;
To fill such structure you can use function parse_msg(...) giving a buffer containing raw text of a SIP message. Most of the attributes
in this structure point directly inside the SIP message buffer.
...
REGISTER sip:sip.test.com SIP/2.0
Via: SIP/2.0/UDP 192.168.1.3:5061;branch=z9hG4bK-d663b80b
Max-Forwards: 70
From: user <sip:u123@sip.test.com>;tag=ea8cef4b108a99bco1
To: user <sip:u123@sip.test.com>
Call-ID: b96fead3-f03493d4@xyz
CSeq: 3720 REGISTER
Contact: user <sip:u123@192.168.1.3:5061>;expires=3600
User-Agent: Linksys/RT31P2-2.0.10(LIc)
Content-Length: 0
Allow: ACK, BYE, CANCEL, INFO, INVITE, NOTIFY, OPTIONS, REFER
Supported: x-sipura
...
...
struct msg_start {
int type; /* Type of the Message - Request or Response (Reply) */
int len; /* length including delimiter */
union {
struct {
str method; /* Method string */
str uri; /* Request URI as raw string */
str version; /* SIP version */
int method_value; /* Internal integer representation of SIP method */
} request;
struct {
str version; /* SIP version */
str status; /* Reply status */
str reason; /* Reply reason phrase */
unsigned int statuscode; /* Integer representation of reply status */
} reply;
}u;
};
...
To parse a buffer containing the first line of a SIP message you have to use the function parse_fline(...).
...
struct hdr_field {
hdr_types_t type; /* Header field type */
str name; /* Header field name */
str body; /* Header field body (may not include CRLF) */
int len; /* length from hdr start until EoHF (incl.CRLF) */
void* parsed; /* Parsed data structures */
struct hdr_field* next; /* Next header field in the list */
struct hdr_field* sibling; /* Next header of same type */
};
...
To parse specific headers in a SIP message you have to use the function parse_headers(...). The function takes as parameter a
bitmask flag that can specify what headers you need to be parsed. For example, to parse the From and To headers:
To optimize the operations with headers, an integer value is assigned to most used headers. This value is stored in attribute type. Here
is the list with the values for header type:
...
enum _hdr_types_t {
HDR_ERROR_T = -1 /* Error while parsing */,
HDR_OTHER_T = 0 /* Some other header field */,
HDR_VIA_T = 1 /* Via header field */,
HDR_VIA1_T = 1 /* First Via header field */,
HDR_VIA2_T = 2 /* only used as flag */,
HDR_TO_T /* To header field */,
HDR_FROM_T /* From header field */,
HDR_CSEQ_T /* CSeq header field */,
HDR_CALLID_T /* Call-Id header field */,
HDR_CONTACT_T /* Contact header field */,
HDR_MAXFORWARDS_T /* MaxForwards header field */,
HDR_ROUTE_T /* Route header field */,
HDR_RECORDROUTE_T /* Record-Route header field */,
HDR_PATH_T /* Path header fiels */,
HDR_CONTENTTYPE_T /* Content-Type header field */,
HDR_CONTENTLENGTH_T /* Content-Length header field */,
HDR_AUTHORIZATION_T /* Authorization header field */,
HDR_EXPIRES_T /* Expires header field */,
HDR_PROXYAUTH_T /* Proxy-Authorization hdr field */,
HDR_SUPPORTED_T /* Supported header field */,
HDR_PROXYREQUIRE_T /* Proxy-Require header field */,
HDR_UNSUPPORTED_T /* Unsupported header field */,
HDR_ALLOW_T /* Allow header field */,
HDR_EVENT_T /* Event header field */,
HDR_ACCEPT_T /* Accept header field */,
HDR_ACCEPTLANGUAGE_T /* Accept-Language header field */,
HDR_ORGANIZATION_T /* Organization header field */,
HDR_PRIORITY_T /* Priority header field */,
HDR_SUBJECT_T /* Subject header field */,
HDR_USERAGENT_T /* User-Agent header field */,
HDR_ACCEPTDISPOSITION_T /* Accept-Disposition hdr field */,
HDR_CONTENTDISPOSITION_T /* Content-Disposition hdr field */,
HDR_DIVERSION_T /* Diversion header field */,
HDR_RPID_T /* Remote-Party-ID header field */,
HDR_REFER_TO_T /* Refer-To header fiels */,
HDR_SESSION_EXPIRES_T /* Session-Expires header field */,
HDR_MIN_SE_T /* Min-SE header field */,
HDR_PPI_T /* P-Preferred-Identity header field */,
HDR_PAI_T /* P-Asserted-Identity header field */,
HDR_PRIVACY_T /* Privacy header field */,
HDR_RETRY_AFTER_T /* Retry-After header field */,
HDR_EOH_T /* Some other header field */
};
...
The attribute parsed may hold the parsed representation of the header body. For example, for Content-Lenght header it contains the
content length value as integer.
...
struct to_body{
int error; /* Error code */
str body; /* The whole header field body */
str uri; /* URI withing the body of the header */
str display; /* Display Name */
str tag_value; /* Value of tag parameter*/
struct sip_uri parsed_uri; /* Parsed URI */
struct to_param *param_lst; /* Linked list of parameters */
struct to_param *last_param; /* Last parameter in the list */
};
...
The str attributes in the structure are referenced to SIP message buffer. To parse a Via header you have to use the function
parse_via(...).
All parsing functions and data structures are in the files from the directory parser. The main file for SIP message parsing is
parser/msg_parser.c with the corresponding header file parser/msg_parser.h.
It does not parse entirely the parts of the SIP message. For most of the SIP headers, it identifies the name and body, it does not parse
the content of header's body. It may happen that a header is malformed and Kamailio does not report any error as there was no request
to parse that header body. However, most used headers are parsed entirely by default. Such headers are top most Via, To, CSeq,
Content-Lenght.
The parser does not duplicate the values, it makes references inside the SIP message buffer it parses. For the parsed structures it
allocates private memory. It keeps the state of parsing, meaning that it has an anchor to the beginning of unparsed part of the SIP
message and stores a bitmask of flags with parsed known headers. If the function to parse a special header is called twice, the second
time will return immediately as it finds in the bitmask that the header was already parsed.
This chapter in not intended to present all parsing functions, you have to check the files in the directory parser. There is kind of naming
convention, so if you need the function to parse the header XYZ, look for the files parser/parse_XYZ.{c,h}. If you don't find it,
second try is to use ctags to locate a parse_XYZ(...) function. If no luck, then ask on Kamailio development mailing list
devel@lists.kamailio.org. Final solution in case on negative answer is to implement it yourself. For example, CSeq header parser is in
parser/parse_cseq.{c,h}.
The next sections will present the parsing functions that give access to the most important parts of a SIP message.
6.1. parse_uri(...)
Example 6.1. Prototype
...
int parse_uri(char *buf, int len, struct sip_uri* uri);
...
uri = "sip:test@mydomain.com";
6.2. parse_msg(...)
A developer does not interfere too much with this function as it is called automatically by Kamailio when a SIP message is received from
the network.
You can use it if you load the content of SIP message from a file or database, or you received on different channels, up to your extension
implementation. You should be aware that it is not enough to call this function and then run the actions from the configuration file.
There are some attributes in the structure sip_msg that are specific to the environment: received socket, source IP and port, ...
...
int parse_msg(char* buf, unsigned int len, struct sip_msg* msg);
...
...
str msg_buf;
struct sip_msg msg;
...
msg_buf.s = "INVITE sip:user@sipserver.com SIP/2.0\r\nVia: ...";
msg_buf.len = strlen(msg_buf.s);
if (parse_msg(buf,len, &msg)!=0) {
LM_ERR("parse_msg failed\n");
return -1;
}
if(msg.first_line.type==SIP_REQUEST)
LM_DBG("SIP method is [%.*s]\n", msg.first_line.u.request.method.len,
msg.first_line.u.request.method.s);
...
6.3. parse_headers(...)
Parse the SIP message until the headers specified by parameter flags flags are found. The parameter next can be used when a header
can occur many times in a SIP message, to continue parsing until a new header of that type is found.
The values that can be used for flags are defined in parser/hf.h.
When searching to get a specific header, all the headers encountered during parsing are hooked in the structure sip_msg.
...
int parse_headers(struct sip_msg* msg, hdr_flags_t flags, int next);
...
6.4. parse_to(...)
Parse a buffer that contains the body of a To header. The function is defined in parser/parse_to.h.
...
char* parse_to(char* buffer, char *end, struct to_body *to_b);
...
The next example shows the parse_from(...) function that makes use of parse_to(...) to parse the body of header From. The
function is located in file parser/parse_from.c.
The structure filled at parsing is hooked in the structure sip_msg, inside the attribute from, which is the shortcut to the header From.
...
return 0;
error:
return -1;
}
...
...
void print_callid_header(struct sip_msg *msg)
{
if(msg==NULL)
return;
if(parse_headers(msg, HDR_CALLID_F, 0)!=0)
{
LM_ERR("error parsing CallID header\n");
return;
}
if(msg->callid==NULL || msg->callid->body.s==NULL)
{
LM_ER("NULL call-id header\n");
return;
}
LM_INFO("Call-ID: %.*s\n", msg->callid->body.len, msg->callid->body.s);
}
...
add source and header files in directory parser naming them parse_hdrname.{c,h}.
if the header is used very often, consider doing speed optimization by allocating a header type and flag. That will allow to identify
the header via integer comparison after the header was parsed and added in headers list in the structure sip_msg.
make sure you add the code to properly clean up the header structure when the structure sip_msg is freed.
make sure that the tm module properly clones the header or it resets the pointers to the header when copying the structure
sip_msg in shared memory.
The core takes care of receiving the messages from the network, the basic validation for them, preparing the environment for a higher
level processing of SIP messages. When developing new extensions, you don't have to care about reading/writing from/to network.
TLS implementation is a module, residing inside modules/tls. It reuses the TCP layer from the core for the management of the
connection, while the code in modules/tls takes care of TLS negotiation and encryption.
If you want to investigate the implementation of transport layers, you can start with:
There is an internal DNS cache which is necessary for doing DNS-based load balancing and failover. It can be disable via core
parameters in the configuration file.
Extending the configuration file can be done by adding a new core parameter or a new core functions. Apart of these, one can add new
routing blocks, keywords or init and runtime instructions.
Starting with release series 3.0, configuration file language has support for preprocessor directives. They provide an easy way to define
tokens to values or enable/disable parts of configuration file.
init statements - this category includes setting the global parameters, loading modules and setting module parameters. These
statements are executed only one time, at startup.
runtime statements - this category includes the actions executed many times, after Kamailio initialization. These statements are
grouped in route blocks, there being different route types that get executed for certain events.
error_route - is executed when some errors (mainly related to message parsing) are encountered
failure_route - is executed when a negative reply was received for a transaction that armed the handler for the failure
event.
branch_route - is executed for each destination branch of the transaction that armed the handler for it.
In the next section we will present how to add a core parameter and add a routing action -- core function. You need to have knowledge
of flex and bison.
Let's follow step by step the definition and usage of the core parameter log_name. It is a string parameter that specifies the value to
be printed as application name in syslog messages.
First is the declaration of the variable in the C code. The log_name is defined in main.c and initialized to 0 (when set to o, it is printed
the Kamailio name (including path) in the syslog).
...
char *log_name = 0;
...
...
LOGNAME log_name
...
The association of a token ID and extending the grammar of the configuration file is done in the bison file: cfg.y.
...
%token LOGNAME
...
assign_stm: ...
| LOGNAME EQUAL STRING { log_name=$3; }
| LOGNAME EQUAL error { yyerror("string value expected"); }
...
The grammar was extended with a new assign statement, that allows to write in the configuration file an expression like:
...
log_name = "kamailio123"
...
When having a line like above one in the configuration file, the variable log_name in C code will be initialized to the string in the right
side of the equal operator.
Behind each core function resides an action structure. This data type is defined in route_struct.h:
...
typedef struct {
action_param_type type;
union {
long number;
char* string;
struct _str str;
void* data;
avp_spec_t* attr;
select_t* select;
} u;
} action_u_t;
struct action{
int cline;
char *cfile;
enum action_type type; /* forward, drop, log, send ...*/
int count;
struct action* next;
action_u_t val[MAX_ACTIONS];
};
...
Each action is identified by a type. The types of actions are defined in same header file. For example, the strip(...) function has the
type STRIP_T, the functions exported by modules have the type MODULE_T.
To each action may be given a set of parameters, so called action elements. In case of functions exported by modules, the first element
is the pointer to the function, next are the parameters given to that function in configuration file.
For debugging and error detection, the action keeps the line number in configuration file where it is used.
...
SETFLAG "setflag"
...
...
First grammar specification says that setflag(...) can have one parameter of type number. The other rule for grammar is to detect
error cases.
Then add a new case in the switch of action types, file action.c, function
...
case SETFLAG_T:
if (a->val[0].type!=NUMBER_ST) {
LOG(L_CRIT, "BUG: do_action: bad setflag() type %d\n",
a->val[0].type );
ret=E_BUG;
goto error;
}
if (!flag_in_range( a->val[0].u.number )) {
ret=E_CFG;
goto error;
}
setflag( msg, a->val[0].u.number );
ret=1;
break;
...
The C function setflag(...) is defined and implemented in flags.{c,h}. It simply sets the bit in flags attribute of sip_msg at the
position given by the parameter.
...
int setflag( struct sip_msg* msg, flag_t flag ) {
msg->flags |= 1 << flag;
return 1;
}
...
We are not done yet. Kamailio does a checking of the actions tree after all configuration file was loaded. It does sanity checks and
optimization for run time. For our case, it does a double-check that the parameter is a number and it is in the range of 0...31 to fit in
the bits size of an integer value. See function fix_actions(...) in route.c.
Next example is given just to show how such fixup can look like, it is no longer used for flag operations functions.
...
case SETFLAG_T:
case RESETFLAG_T:
case ISFLAGSET_T:
if (t->elem[0].type!=NUMBER_ST) {
LM_CRIT("bad xxxflag() type %d\n", t->elem[0].type );
ret=E_BUG;
goto error;
}
if (!flag_in_range( t->elem[0].u.number )) {
ret=E_CFG;
goto error;
}
break;
...
Last thing you have to add is to complete the function print(action(...) with a new case for your action that will be used to print the
actions tree -- for debugging purposes. See it in file route_struct.c.
...
case SETFLAG_T:
LM_DBG("setflag(");
break;
...
From now on, you can use in your configuration file the function setflag(_number_).
Therefore, the interface provides data types and functions that are independent of underlying DB storage. A DB driver module has to
implement the functions specified by the interface and provide a function named db_bind_api(...) to link to the interface functions.
Starting with version 3.0.0, Kamailio has two variants of database APIs, stored as internal libraries, named srdb1 and srdb2. Most of
Kamailio modules are using srdb1, thus for this document the focus will be on how to use this option.
The DB1 interface is implemented in the lib/srdb1 directory. To use it, one has to include the file lib/srdb1/db.h.
...
typedef struct db_func {
unsigned int cap; /* Capability vector of the database transport */
db_use_table_f use_table; /* Specify table name */
db_init_f init; /* Initialize database connection */
db_close_f close; /* Close database connection */
db_query_f query; /* query a table */
db_fetch_result_f fetch_result; /* fetch result */
db_raw_query_f raw_query; /* Raw query - SQL */
db_free_result_f free_result; /* Free a query result */
db_insert_f insert; /* Insert into table */
db_delete_f delete; /* Delete from table */
db_update_f update; /* Update table */
db_replace_f replace; /* Replace row in a table */
db_last_inserted_id_f last_inserted_id; /* Retrieve the last inserted ID
in a table */
db_insert_update_f insert_update; /* Insert into table, update on duplicate key */
db_insert_delayed_f insert_delayed; /* Insert delayed into table */
db_affected_rows_f affected_rows; /* Numer of affected rows for last query */
} db_func_t;
...
The attribute cap is a bitmask of implemented functions, making easy to detect the capabilities of the DB driver module. A module using
the DB API should check at startup that the DB driver configured to be used has the required capabilities. For example, msilo module
need select, delete and insert capabilities. The flags for capabilities are enumerated in the next figure (located in
lib/srdb1/db_cap.h).
...
typedef enum db_cap {
DB_CAP_QUERY = 1 << 0, /* driver can perform queries */
DB_CAP_RAW_QUERY = 1 << 1, /* driver can perform raw queries */
DB_CAP_INSERT = 1 << 2, /* driver can insert data */
DB_CAP_DELETE = 1 << 3, /* driver can delete data */
DB_CAP_UPDATE = 1 << 4, /* driver can update data */
DB_CAP_REPLACE = 1 << 5, /* driver can replace (also known as INSERT OR UPDATE) data */
DB_CAP_FETCH = 1 << 6, /* driver supports fetch result queries */
DB_CAP_LAST_INSERTED_ID = 1 << 7, /* driver can return the ID of the last insert operation */
DB_CAP_INSERT_UPDATE = 1 << 8, /* driver can insert data into database, update on duplicate */
DB_CAP_INSERT_DELAYED = 1 << 9, /* driver can do insert delayed */
DB_CAP_AFFECTED_ROWS = 1 << 10 /* driver can return number of rows affected by last query */
} db_cap_t;
...
Parameters:
_url - database URL. Its format depends on DB driver. For an SQL server like MySQL has to be:
mysql://username:password@server:port/database. For db_text has to be: text:///path/to/db/directory.
The function returns pointer to db1_con_t* representing the connection structure or NULL in case of error.
...
typedef void (*db_close_f) (db1_con_t* _h);
...
Parameters:
...
typedef int (*db_use_table_f)(db1_con_t* _h, const str * _t);
...
Parameters:
_t - table name.
...
typedef int (*db_query_f) (const db1_con_t* _h, const db_key_t* _k, const db_op_t* _op,
const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
const db_key_t _o, db1_res_t** _r);
...
Parameters:
_k - array of column names that will be compared and their values must match.
Note: If _k and _v parameters are NULL and _n is zero, you will get the whole table. If _c is NULL and _nc is zero, you will get all table
columns in the result. Parameter _r will point to a dynamically allocated structure, it is neccessary to call db_free_result function once
you are finished with the result. If _op is 0, equal (=) will be used for all key-value pairs comparisons. Strings in the result are not
duplicated, they will be discarded if you call. Make a copy of db_free_result if you need to keep it after db_free_result. You must call
db_free_result before you can call db_query again!
...
typedef int (*db_fetch_result_f) (const db1_con_t* _h, db1_res_t** _r, const int _n);
...
Parameters:
...
typedef int (*db_raw_query_f) (const db1_con_t* _h, const str* _s, db1_res_t** _r);
...
Parameters:
...
typedef int (*db_free_result_f) (db1_con_t* _h, db1_res_t* _r);
...
Parameters:
Parameters:
...
typedef int (*db_delete_f) (const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
const db_val_t* _v, const int _n);
...
Parameters:
...
typedef int (*db_update_f) (const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv,
const int _n, const int _un);
...
Parameters:
Parameters:
...
typedef int (*db_last_inserted_id_f) (const db1_con_t* _h);
...
Parameters:
The function returns the ID as integer or returns 0 if the previous statement does not use an AUTO_INCREMENT value.
...
typedef int (*db_insert_update_f) (const db1_con_t* _h, const db_key_t* _k,
const db_val_t* _v, const int _n);
...
Parameters:
...
typedef int (*db_insert_delayed_f) (const db1_con_t* _h, const db_key_t* _k,
const db_val_t* _v, const int _n);
...
Parameters:
...
typedef int (*db_affected_rows_f) (const db1_con_t* _h);
...
Parameters:
...
typedef str* db_key_t;
...
...
typedef const char* db_op_t;
...
...
typedef const char* db_op_t;
/** operator less than */
#define OP_LT "<"
/** operator greater than */
#define OP_GT ">"
/** operator equal */
#define OP_EQ "="
/** operator less than equal */
#define OP_LEQ "<="
/** operator greater than equal */
#define OP_GEQ ">="
/** operator negation */
#define OP_NEQ "!="
...
...
typedef struct {
db_type_t type; /* Type of the value */
int nul; /* Means that the column in database has no value */
int free; /* Means that the value should be freed */
/** Column value structure that holds the actual data in a union. */
union {
int int_val; /* integer value */
long long ll_val; /* long long value */
double double_val; /* double value */
time_t time_val; /* unix time_t value */
const char* string_val; /* zero terminated string */
str str_val; /* str type string value */
str blob_val; /* binary object data */
unsigned int bitmap_val; /* Bitmap data type */
} val;
} db_val_t;
...
...
typedef struct {
const str* table; /* Default table that should be used */
unsigned long tail; /* Variable length tail, database module specific */
} db1_con_t;
...
...
typedef struct db_row {
db_val_t* values; /* Columns in the row */
int n; /* Number of columns in the row */
} db_row_t;
...
...
typedef struct db1_res {
struct {
db_key_t* names; /* Column names */
db_type_t* types; /* Column types */
int n; /* Number of columns */
} col;
struct db_row* rows; /* Rows */
int n; /* Number of rows in current fetch */
int res_rows; /* Number of total rows in query */
int last_row; /* Last row */
} db1_res_t;
...
9.4. Macros
The DB API offers a set of macros to make easier to access the attributes in various data structures.
...
create table test (
a int,
b varchar(64)
);
...
The C code:
...
#include "../../dprint.h"
#include "../../lib/srdb1/db.h"
col_a.s = "a";
col_a.len = 1;
col_b.s = "b";
col_b.len = 1;
db_cols[0] = &col_b;
db_keys[0] = &col_a;
db_vals[nr_keys].type = DB_INT;
db_vals[nr_keys].nul = 0;
db_vals[nr_keys].val.int_val = 1;
nr_keys++;
if (RES_ROW_N(db_res)<=0 || RES_ROWS(db_res)[0].values[0].nul != 0)
{
LM_DBG("no value found\n");
if (db_res!=NULL && db_funcs.free_result(db_handle, db_res) < 0)
LM_DBG("failed to free the result\n");
db_funcs.close(db_handle);
return -1;
}
return 0;
}
...
MI - management interface - it is no longer the recommended control interface and it is planned to be obsoleted in the near future
RPC - remote procedure call - a more standardized option to execute commands. It is the recommended control interface.
fifo - (ctl module) - the communication is done via FIFO file using a simple text-based, line oriented protocol.
datagram - (ctl module) - the communication is done via unix socket files or UDP sockets.
tcp - (ctl module) - the communication is done via unix socket files or TCP sockets.
RPC API is very well documented at: http://www.kamailio.org/docs/docbooks/3.2.x/rpc_api/rpc_api.html. We will show next just an
example of implementing a RPC command: pkg.stats - dump usage statistics of PKG (private) memory, implemented in modules_k/kex.
...
/**
*
*/
static const char* rpc_pkg_stats_doc[2] = {
"Private memory (pkg) statistics per process",
0
};
/**
*
*/
int pkg_proc_get_pid_index(unsigned int pid)
{
int i;
for(i=0; i<_pkg_proc_stats_no; i++)
{
if(_pkg_proc_stats_list[i].pid == pid)
return i;
}
return -1;
}
/**
*
*/
static void rpc_pkg_stats(rpc_t* rpc, void* ctx)
{
int i;
int limit;
int cval;
str cname;
void* th;
int mode;
if(_pkg_proc_stats_list==NULL)
{
rpc->fault(ctx, 500, "Not initialized");
return;
}
i = 0;
mode = 0;
cval = 0;
limit = _pkg_proc_stats_no;
if (rpc->scan(ctx, "*S", &cname) == 1)
{
if(cname.len==3 && strncmp(cname.s, "pid", 3)==0)
mode = 1;
else if(cname.len==4 && strncmp(cname.s, "rank", 4)==0)
mode = 2;
else if(cname.len==5 && strncmp(cname.s, "index", 5)==0)
mode = 3;
else {
rpc->fault(ctx, 500, "Invalid filter type");
return;
}
/**
*
*/
rpc_export_t kex_pkg_rpc[] = {
{"pkg.stats", rpc_pkg_stats, rpc_pkg_stats_doc, 0},
{0, 0, 0, 0}
};
/**
*
*/
int pkg_proc_stats_init_rpc(void)
{
if (rpc_register_array(kex_pkg_rpc)!=0)
{
LM_ERR("failed to register RPC commands\n");
return -1;
}
return 0;
}
...
To add new RPC commands to control interface, you have to register them. One option, which is used here, is to build an array with the
new commands and the help messages for each one and the use rpc_register_array(...) function. You have the register the commands
in mod_init() function - in our example it is done via pkg_proc_stats_init(), which is a wrapper function called from mod_init().
pkg.stats commands has optional parameters, which can be used to specify the pid, internal rank or position in internal process table
(index) of the application process for which to dump the private memory statistics. If there is no parameter given, the the statistics for
all processes will be printed.
The command itself is implemented in C function rpc_pkg_stats(...). In the first part, it reads the parameters. You can see there the
search for optional parameter is done by using '*' in the front of the type of parameter (str):
If this parameter is found, then there has to be another one, which this time is no longer optional.
Once input is read, follows the printing of the result in RPC structures. When kex module is loaded, one can use pkg.stats command via
sercmd tool like:
sercmd pkg.stats
sercmd pkg.stats index 2
sercmd pkg.stats rank 4
sercmd pkg.stats pid 8492
MI is built on a tree architecture - the input is parsed completely and stored as a tree in memory. The commands get access to the tree
and build another tree with the response, which is then printed back to the transport layer.
In the past, there were two ways to interact with such applications: via FIFO file and via unix sockets.
MI came and introduced an abstractization between the transport and application levels. All MI commands are available for all available
transports. At this moment the following transports are available:
fifo - (mi_fifo mofule) - the communication is done via FIFO file using a simple text-based, line oriented protocol.
To implement a new command for MI you don't need to interact with the transports. MI will pass to your function a compiled version of
the command, in the form of a tree. The functions walks through the tree, finds the input parameters, executes the actions accordingly,
and returns a new MI tree with the response to the command.
...
struct mi_root my_my_function(struct mi_root *tree, void *param);
...
Parameters:
tree - the tree with the input parameters sent via the transport layer. The struct mi_root is defined in mi/three.h.
...
struct mi_node {
str value; /* value of node (parameter) */
str name; /* name of node (parameter) */
struct mi_node *kids; /* children nodes */
struct mi_node *next; /* sibling nodes */
struct mi_node *last; /* last node */
struct mi_attr *attributes; /* the attributes of the node */
};
struct mi_root {
unsigned int code; /* return code of the command */
str reason; /* reason code */
struct mi_handler *async_hdl; /* handler function for asynchronous replying */
struct mi_node node; /* head of the tree with parameters (nodes) */
};
...
Returns a tree containing the response to be send back for that command or NULL in case of error.
...
typedef struct mi_root* (mi_cmd_f)(struct mi_root*, void *param);
typedef int (mi_child_init_f)(void);
Parameters:
f - function to be called when the command is received from the transport layer
...
#define MI_ASYNC_RPL_FLAG (1<<0) // - the reply to command is asynchronous
#define MI_NO_INPUT_FLAG (1<<1) // - the command does not get any input parameters
...
The function expects to find in the input tree, one flag (state), one number (group) and a string (the address). If not, an reply
containing error message is sent back. If the parameters are ok and the destination is found, the state is changed accordingly and
successful code and message is sent back, in not, an appropriate error code and message is sent back.
...
static struct mi_root* ds_mi_set(struct mi_root* cmd_tree, void* param)
{
str sp;
int ret;
unsigned int group, state;
struct mi_node* node;
node = cmd_tree->node.kids;
if(node == NULL)
return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
sp = node->value;
if(sp.len<=0 || !sp.s)
{
LM_ERR("bad state value\n");
return init_mi_tree(500, "bad state value", 15);
}
state = 0;
if(sp.s[0]=='0' || sp.s[0]=='I' || sp.s[0]=='i') {
/* set inactive */
state |= DS_INACTIVE_DST;
if((sp.len>1) && (sp.s[1]=='P' || sp.s[1]=='p'))
state |= DS_PROBING_DST;
} else if(sp.s[0]=='1' || sp.s[0]=='A' || sp.s[0]=='a') {
/* set active */
if((sp.len>1) && (sp.s[1]=='P' || sp.s[1]=='p'))
state |= DS_PROBING_DST;
} else if(sp.s[0]=='2' || sp.s[0]=='D' || sp.s[0]=='d') {
/* set disabled */
state |= DS_DISABLED_DST;
} else if(sp.s[0]=='3' || sp.s[0]=='T' || sp.s[0]=='t') {
/* set trying */
state |= DS_TRYING_DST;
if((sp.len>1) && (sp.s[1]=='P' || sp.s[1]=='p'))
state |= DS_PROBING_DST;
} else {
LM_ERR("unknow state value\n");
return init_mi_tree(500, "unknown state value", 19);
}
node = node->next;
if(node == NULL)
return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
sp = node->value;
if(sp.s == NULL)
{
return init_mi_tree(500, "group not found", 15);
}
if(str2int(&sp, &group))
{
LM_ERR("bad group value\n");
return init_mi_tree( 500, "bad group value", 16);
}
node= node->next;
if(node == NULL)
return init_mi_tree( 400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
sp = node->value;
if(sp.s == NULL)
{
return init_mi_tree(500,"address not found", 18 );
}
if(ret!=0)
{
return init_mi_tree(404, "destination not found", 21);
}
For FIFO, the structure is line oriented, command being plain text.
...
:_command_name_:_reply_fifo_file_
_parameters_
_empty_line_
...
For the command described in the previous section, it can look like:
...
:ds_set_state:kamailio_fifo_reply
i
2
sip:10.10.10.10:5080
\n
...
some are read-only - most of them are read-only, because they are references inside the original SIP message and that does not
change during config file execution (see Data Lump chapter.)
some are array - even if looks as simple variable name, assigning a value means to add one more in an array - the case for
$avp(name).
So, they were named pseudo-variable. Initially they were introduced in xlog module having a simple mechanism behind. There is a
marker character ($ in this case) to identify the start of pseudo-variable name from the rest of the text. To a pseudo-variable name was
associated a function that returned a string value. That value was replacing the pseudo-variable name in the message printed to syslog.
Lately the concept was extended to include AVPs, to have writable pseudo-variables and to be possible to use them directly in the
configuration file. Also, some of them can have dynamic name and index.
The framework for pseudo-variables is now easy to extend. They can be introduced as core pseudo-variables or exported by modules
(this is preferred option). We will show such case further in the document.
...
marker classname
marker '(' classname ')'
marker '(' classname '[' index ']' ')'
marker '(' classname ( '{' transformation '}' )+ ')'
marker '(' classname '[' index ']' ( '{' transformation '}' )+ ')'
marker classname '(' innername ')'
marker '(' classname '(' innername ')' ')'
marker '(' classname '(' innername ')' '[' index ']' ')'
marker '(' classname '(' innername ')' ( '{' transformation '}' )+ ')'
marker '(' classname '(' innername ')' '[' index ']' ( '{' transformation '}' )+ ')'
...
classname - a string specifying the class of pseudo-variables. A class can have on pseudo-variable, when the innername is
missing. Lot of references to parts of SIP message are single PV class, e.g., $ru - request URI.
Classes with more than one pseudo-variable are AVPs ($avp(name)), references to headers ($hdr(name)), script vars
($var(name)) and several others.
innername - the name specifying the pseudo-variable in a class. It can be dynamic (specified by the value of another pseudo-
variable), but depends on the pseudo-variable class, it is not valid for all classes.
index - index in the array, when the pseudo-variable can have multiple values at the same time. Such pseudo-variables are
header references and AVPs. Also the index can have dynamic value, up to pseudo-variable class implementation.
transformation - kind of function that are applied to the value of the pseudo-variable. A dedicated chapter is included in this
tutorial.
...
typedef struct _pv_value
{
str rs; /* string value */
int ri; /* integer value */
int flags; /* flags about the type of value */
} pv_value_t, *pv_value_p;
...
...
#define PV_VAL_NONE 0 // no actual value -- it is so just at initialization
#define PV_VAL_NULL 1 // the value must be considered NULL
#define PV_VAL_EMPTY 2 // the value is an empty string (deprecated)
#define PV_VAL_STR 4 // the value has the string attribute 'rs' set
#define PV_VAL_INT 8 // the value has the integer attribute 'ri' set
#define PV_TYPE_INT 16 // the value may have both string and integer attribute set, but type is integer
#define PV_VAL_PKG 32 // the value was duplicated in pkg memory, free it accordingly at destroy time
#define PV_VAL_SHM 64 // the value was duplicated in shm memory, free it accordingly at destroy time
...
...
typedef struct _pv_name
{
int type; /* type of name */
union {
struct {
int type; /* type of int_str name - compatibility with AVPs */
int_str name; /* the value of the name */
} isname;
void *dname; /* PV value - dynamic name */
} u;
} pv_name_t, *pv_name_p;
...
...
#define PV_NAME_INTSTR 0 // the name is constant, an integer or string
#define PV_NAME_PVAR 1 // the name is dynamic, a pseudo-variable
#define PV_NAME_OTHER 2 // the name is specific per implementation
...
...
0 // the name is integer
...
typedef struct _pv_index
{
int type; /* type of PV index */
union {
int ival; /* integer value */
void *dval; /* PV value - dynamic index */
} u;
} pv_index_t, *pv_index_p;
...
...
#define PV_IDX_INT 0 // the index is integer value
#define PV_IDX_PVAR 1 // the index is dynamic, a pseudo-variable
#define PV_IDX_ALL 2 // the index specifies to return all values for that pseudo-variables
// - this is up to implementation of pseudo-variable class
...
...
typedef struct _pv_param
{
pv_name_t pvn; /* PV name */
pv_index_t pvi; /* PV index */
} pv_param_t, *pv_param_p;
...
...
typedef int (*pv_getf_t) (struct sip_msg*, pv_param_t*, pv_value_t*);
typedef int (*pv_setf_t) (struct sip_msg*, pv_param_t*, int, pv_value_t*);
...
The types are defined in pvar.h and are used to detect the type of core variables. Sometime is useful to filter out pseudo-variables, for
example when you want to allow only some type as parameter to a function or module, e.g., only AVPs.
...
Practically, to add the pseudo-variable you have to give the classname and implement the functions to:
The optional attributes can be left NULL. iparam is used together with function init_param.
if (msg->new_uri.s!=NULL)
return pv_get_strval(msg, param, res, &msg->new_uri);
return pv_get_strval(msg, param, res, &msg->first_line.u.request.uri);
}
...
int pv_set_ruri(struct sip_msg* msg, pv_param_t *param,
int op, pv_value_t *val)
{
struct action act;
struct run_act_ctx h;
char backup;
if(!(val->flags&PV_VAL_STR))
{
LM_ERR("str value required to set R-URI\n");
goto error;
}
memset(&act, 0, sizeof(act));
act.val[0].type = STRING_ST;
act.val[0].u.string = val->rs.s;
backup = val->rs.s[val->rs.len];
val->rs.s[val->rs.len] = '\0';
act.type = SET_URI_T;
init_run_actions_ctx(&h);
if (do_action(&h, &act, msg)<0)
{
LM_ERR("do action failed\n");
val->rs.s[val->rs.len] = backup;
goto error;
}
val->rs.s[val->rs.len] = backup;
return 0;
error:
return -1;
}
...
value - pointer to a pv_value_t structure. It is out parameter for get function and in parameter for set function.
In the get function, it checks whether there is a new request URI values and return that. If not, returns the URI from the original SIP
message. It takes care that the URI is parsed, so it is valid.
In the set function, it checks to be sure that the value to assign is a string, and then calls the internal SET_URI_T action.
The last step is to add the proper entry in the pseudo-variables table (see modules_k/pv/pv.c) -- remember that this is required only
for pseudo-variables included in core, not for the ones exported by modules.
...
static pv_export_t mod_pvs[] = {
...
{{"ruri", (sizeof("ruri")-1)}, /* */
PVT_RURI, pv_get_ruri, pv_set_ruri,
0, 0, 0, 0},
...
{{0,0}, 0, 0, 0, 0, 0, 0, 0}
};
...
An example of a transformation is to get a substring from the value returned by a pseudo-variable. Another one is to get the length of
the values returned by a pseudo-variable.
apply the operation specified by the first transformation to the value returned by the pseudo-variable
apply the operation specified by the current transformation to the value returned by the previous transformation. Go to next
transformation.
the value returned by the last transformation is returned to script or to calling C code.
Behind each transformation it is a function that does value (pv_value_t) manipulation. It has as input such a value and as output
another value, stored over the input value. The transformation are implemented mainly in Kamailio modules, data structures and API
are in file pvar.h. Many transformations are effectively implemented in modules_k/pv/pv_trans.{c,h}.
...
'{' classname '.' innername ( ',' parameter )* '}'
...
parameter - the parameter to the transformation. There can be transformation with no parameters, one or more parameters.
...
{s.substr,1,2} - return the string with second and the third characters (substring of length 2 from the
second character)
{uri.user} - return the user part of the SIP URI stored in the pseudo-variable on which this transformation
is applied
...
value - pointer to the value (pv_value_t) to apply transformation on it and store the result
There is one function for each transformation class, the exact operation to be applied is given via subtype. The structure trans_t is
what stands behind each transformation occurrence.
Exemplifying with transformation s.len - it returns the length of the string value of a pseudo-variable. The type TR_STRING and
subtype TR_S_LEN are added in file modules_k/pv/pv_trans.h - these are internal IDs used for runtime optimizations.
The function tr_parse_string() in file modules_k/pv/pv_trans.c is implementing the parser of the class s. In this function, if the
inner name is len it sets the subtype accordingly.
...
if(name.len==3 && strncasecmp(name.s, "len", 3)==0)
{
t->subtype = TR_S_LEN;
return p;
}
...
The transformation parser is now extended. Next is the interpreter, the function tr_eval_str(...) - the evaluation function will be
associated to string transformation by the parser function tr_parse_string().
...
switch(subtype)
{
case TR_S_LEN:
if(!(val->flags&PV_VAL_STR))
val->rs.s = int2str(val->ri, &val->rs.len);
val->flags = PV_TYPE_INT|PV_VAL_INT|PV_VAL_STR;
val->ri = val->rs.len;
val->rs.s = int2str(val->ri, &val->rs.len);
break;
...
The content of the pv_value_t variable is replaced with the length of the string representation of its initial value. An example of usage is
shown next - get the length of request URI:
...
$var(x) = $(ru{s.len});
...
To make the transformations available to the core API, you have to register them. You can do it via mod_register(...) function. Note
that this function is executed when the module is loaded, making possible to use the transformation in module parameters and
configuration file operations. See modules_k/pv/pv.c:
...
static tr_export_t mod_trans[] = {
{ {"s", sizeof("s")-1}, /* string class */
tr_parse_string },
{ {"nameaddr", sizeof("nameaddr")-1}, /* nameaddr class */
tr_parse_nameaddr },
{ {"uri", sizeof("uri")-1}, /* uri class */
tr_parse_uri },
{ {"param", sizeof("param")-1}, /* param class */
tr_parse_paramlist },
{ {"tobody", sizeof("tobody")-1}, /* param class */
tr_parse_tobody },
{ { 0, 0 }, 0 }
};
...
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
return register_trans_mod(path, mod_trans);
}
...
The statistics engine is implemented in the files lib/kcore/statistics..{c,h} - practically they are part of internal library kcore. If
you want to extend it, you have to read and understand the code in those file. The purpose of this chapter is to teach how to add new
statistic variables.
You have to include the header file lib/kcore/statistics.h and declare the statistic variable. We exemplify with the statistic
stored_messages from module msilo. In the file modules/msilo/msilo.c.
...
#include "../../lib/kcore/statistics.h"
stat_var* ms_stored_msgs;
...
Next is to register the statistic to the engine, which can done there via register_module_stats(...) function when you have an array of
new statistics.
...
stat_export_t msilo_stats[] = {
{"stored_messages" , 0, &ms_stored_msgs },
...
if (register_module_stats( exports.name, msilo_stats)!=0 ) {
LM_ERR("failed to register core statistics\n");
return -1;
}
...
...
int register_stat( char *module, char *name, stat_var **pvar, int flags);
...
Updating the value of the statistic is done in function m_store(...), once a new message is stored in database.
...
update_stat(ms_stored_msgs, 1);
...
update_stat (stat, val) - add to the statistic value the val. val can be negative as well, resulting in substraction.
reset_stat (stat) - set the value of the statistic to 0
Many are surprised to discover that even they remove a header from the original SIP message, in the configuration file, when they test
later for header existence it is still then. Why? Because the data lumps are behind the remove operations.
The modifications to the original SIP message instructed from configuration file are not applied immediately. They are actually added in
a list of operations to be done. Practically, changing the content of the SIP message from the configuration file translates in creating a
diff (like in diff/patch tools from Unix/Linux systems) and putting it in the list. The diffs are applied after the configuration file is
executed, before sending the SIP message further to the network.
Note that in config of Kamailio 3.x, you can use msg_apply_changes() exported by textopsx module to apply immediately the changes
done to the content of the SIP message.
add content - this diff command is specified by the position in the original SIP message and the value to be added there. The
value can be a static string, or a special marker to be interpreted later, when required information is available. For the later, it is
the case of Record-Route headers where it is needed to set the IP address of the socket that is used to send the message further.
That address is detected just before sending through the socket.
remove content - this diff command is specified by the position in the original SIP message and the length to be deleted.
message lumps - these lumps are associated to the SIP message currently processed. The changes incurred by these lumps are
visible when the SIP message is forwarded.
reply lumps - these lumps can be used when the processed SIP message is a request and you want to add content to the reply to
be sent for that request. Here cannot be lumps that delete content as the SIP reply is to be constructed from the SIP request.
Maybe future releases will touch deeper this subject, depending on the interest. If you want to investigate by yourself, start with files
data_lump.{c,h} and data_lump_rpl.{c,h}.
The timer API is implemented in the files timer.{c,h}. If you want to extend the timer API you have to start with those files. We focus
on how to add new functions to be run by timer. The timer is available after shared memory initialization.
...
typedef unsigned long long utime_t;
Parameters are:
ticks - number of second ticks elapsed at the moment of running the function
...
int register_timer(timer_function f, void* param, unsigned int interval);
...
Parameters:
f - callback function
Parameters:
f - callback function
There are two functions that return the number of ticks elapsed since Kamailio started, one returning time in seconds and the other one
returning the number of internal ticks.
...
unsigned int get_ticks(void);
utime_t get_ticks_raw(void);
...
...
#include "../../timer.h"
...
void m_clean_silo(unsigned int ticks, void *);
...
...
register_timer(m_clean_silo, 0, ms_check_time);
...
/**
* - cleaning up the messages that got reply
* - delete expired messages from database
*/
void m_clean_silo(unsigned int ticks, void *param)
{
msg_list_el mle = NULL, p;
db_key_t db_keys[MAX_DEL_KEYS];
db_val_t db_vals[MAX_DEL_KEYS];
db_op_t db_ops[1] = { OP_LEQ };
int n;
msg_list_check(ml);
mle = p = msg_list_reset(ml);
n = 0;
while(p)
{
if(p->flag & MS_MSG_DONE)
{
#ifdef STATISTICS
if(p->flag & MS_MSG_TSND)
update_stat(ms_dumped_msgs, 1);
else
update_stat(ms_dumped_rmds, 1);
#endif
db_keys[n] = &sc_mid;
db_vals[n].type = DB1_INT;
db_vals[n].nul = 0;
db_vals[n].val.int_val = p->msgid;
LM_DBG("cleaning sent message [%d]\n", p->msgid);
n++;
if(n==MAX_DEL_KEYS)
{
if (msilo_dbf.delete(db_con, db_keys, NULL, db_vals, n) < 0)
LM_ERR("failed to clean %d messages.\n",n);
n = 0;
}
}
if((p->flag & MS_MSG_ERRO) && (p->flag & MS_MSG_TSND))
{ /* set snd time to 0 */
ms_reset_stime(p->msgid);
#ifdef STATISTICS
update_stat(ms_failed_rmds, 1);
#endif
}
#ifdef STATISTICS
if((p->flag & MS_MSG_ERRO) && !(p->flag & MS_MSG_TSND))
update_stat(ms_failed_msgs, 1);
#endif
p = p->next;
}
if(n>0)
{
if (msilo_dbf.delete(db_con, db_keys, NULL, db_vals, n) < 0)
LM_ERR("failed to clean %d messages\n", n);
n = 0;
}
msg_list_el_free_all(mle);
...
The function deletes from database the messages that were succesfully delivered and the messages that were stored for too long time in
database and the recipient was not online or not able to receive them.
You can create as many timer processes as you need - it takes two steps:
in mod_init() tell to timer API how many processes you want to create
in child_init() for MAIN child, fork the timer processes with the callback function
...
/**
* init module function
*/
static int mod_init(void)
{
...
register_dummy_timers(1);
...
}
...
/**
* init module children
*/
static int child_init(int rank)
{
...
if (rank==PROC_MAIN)
{
if(fork_dummy_timer(PROC_TIMER, "MY MOD TIMER", 1 /*socks flag*/,
my_timer_callback, my_timer_param, 1 /*sec*/) < 0) {
LM_ERR("failed to register timer routine as process\n");
return -1; /* error */
}
}
...
}
...
Kamailio modules in a pretty simple concept, are objects that export a set of parameters to control the internals and a set of functions
that can be used in the configuration file. In fact, they are shared library files.
There are no modules to be automatically loaded, the configuration file must explicitly include the directive to load a module.
...
loadmodule "/path/to/module.so"
...
Each module has to export a structure struct module_exports with the name exports.
...
struct module_exports{
char* name; /* null terminated module name */
unsigned int dlflags; /* flags for dlopen */
The comments in the definition are explanatory, each internal structure and data type is detailed in the next sections.
Starting with version 3.0, there are two module interfaces: one specific to Kamailio flavour and the other one specific for SER flavour. A
third one will merge the two in the near future. At this moment, each module has to specify in the Makefile what kind of interface
implements. In this chapter, the focus is on writing modules implementing Kamailio specific module interface.
...
struct cmd_export_ {
char* name; /* null terminated command name */
cmd_function function; /* pointer to the corresponding function */
int param_no; /* number of parameters used by the function */
fixup_function fixup; /* pointer to the function called to "fix" the
parameters */
free_fixup_function
free_fixup; /* pointer to the function called to free the
"fixed" parameters */
int flags; /* Function flags */
};
typedef struct cmd_export_ cmd_export_t;
...
...
struct param_export_ {
char* name; /* null terminated param. name */
modparam_t type; /* param. type */
void* param_pointer; /* pointer to the param. memory location or to function to set the parameter */
};
typedef struct param_export_ param_export_t;
...
USE_FUNC_PARAM - this must be used in combination with one from the above. Means that internally there is a function called
every time the parameter is set, instead of setting the value to a variable.
When using USE_FUNC_PARAM flag, the param_pointer must be set to a function with the following type:
...
typedef int (*param_func_t)( modparam_t type, void* val);
...
Parameters are:
type - the type of value set to the parameter in the config file
First you have to declare in mod_init() how many processes you want to start, the fork new processes in init_child() for
RANK_PROC_MAIN:
...
...
if (rank==PROC_MAIN) {
pid=fork_process(PROC_NOCHLDINIT, "MY PROC DESCRIPTION", 1);
if (pid<0)
return -1; /* error */
if(pid==0){
/* child */
my_process_main_function(...);
}
}
...
return 0;
}
...
register_procs(no) takes as parameter the number of processes to be created. fork_process(...) takes as parameters: flag specifying
wheter to execute child_init() functions for the new process; string with description of the process; flag specifying whether to create
internal unix sockets or not for the new process (needed for TCP/TLS communication, use 1 to be safe always).
Typical module implementations for such functionality are the MI transports module. They require a special process to listen to the MI
transport layer.
...
typedef struct stat_export_ {
char* name; /* null terminated statistic name */
int flags; /* flags */
stat_var** stat_pointer; /* pointer to the variable's mem location *
* NOTE - it's in shm mem */
} stat_export_t;
...
This field is currently in passive mode inside module_exports structure. You have to explicitely register the statistics inside mod_init()
via register_module_stats(). For example in modules_k/msilo:
...
stat_export_t msilo_stats[] = {
{"stored_messages" , 0, &ms_stored_msgs },
{"dumped_messages" , 0, &ms_dumped_msgs },
{"failed_messages" , 0, &ms_failed_msgs },
{"dumped_reminders" , 0, &ms_dumped_rmds },
{"failed_reminders" , 0, &ms_failed_rmds },
{0,0,0}
};
...
static int mod_init(void)
{
...
/* register statistics */
if (register_module_stats( exports.name, msilo_stats)!=0 ) {
LM_ERR("failed to register core statistics\n");
return -1;
}
...
}
...
</programlisting>
<para>
For more see the chapter <emphasis role="strong">Statistics</emphasis>.
</para>
</section>
<section id="c16t_mi_export_t">
<title>mi_export_t type</title>
<para>
The structure to export MI commands. For more see the chapter <emphasis role="strong">Management Interface</emphasis>.
</para>
<programlisting format="linespecific">
...
typedef struct mi_export_ {
char *name; /* name of MI command */
mi_cmd_f *cmd; /* function to be executed for the MI command */
unsigned int flags; /* flags associated to the MI command */
void *param; /* parameter to be given to the MI command */
mi_child_init_f *init_f; /* function to be executed at MI process initialization in order to
* have function 'cmd' working properly */
}mi_export_t;
...
16.6. pv_export_t
The structure to export pseudo-variables from module. See the chapter Pseudo Variables for detailed description.
...
typedef struct _pv_export {
str name; /* class name of PV */
pv_type_t type; /* type of PV */
pv_getf_t getf; /* function to get the value */
pv_setf_t setf; /* function to set the value */
pv_parse_name_f parse_name; /* function to parse the inner name */
pv_parse_index_f parse_index; /* function to parse the index of PV */
pv_init_param_f init_param; /* function to init the PV spec */
int iparam; /* parameter for the init function */
} pv_export_t;
...
...
typedef int (*cmd_function)(struct sip_msg*, char*, char*, char*, char*, char*, char*);
typedef int (*fixup_function)(void** param, int param_no);
typedef int (*free_fixup_function)(void** param, int param_no);
typedef int (*response_function)(struct sip_msg*);
typedef void (*destroy_function)();
typedef int (*init_function)(void);
typedef int (*child_init_function)(int rank);
...
Description:
cmd_function - is the type for functions implementing the commands exported to configuration file
fixup_function - is the type for function to be used to pre-compile the parameters at startup
free_fixup_function - is the type for function to be used to free the structure resulted after pre-compile processing
response_function - is the type for the functions to be register to automatically be called when a SIP reply is received by Kamailio
destroy_function - is the type for the function to be executed at shut down time, to clean up the resources used during run time
(e.g., shared memory, locks, connections to database)
init_function - is the type for the function to be executed at start up, before forking children processes
child_init_function - is the type for the function to be executed for each children process, immediately after forking
It means that in the configuration file you can give only char* values as parameter. Kamailio provides the mechanisms to convert the
values to something more meaningful for the module, during Kamailio start up, via fixup functions.
When calling from the configuration file, the structure sip_msg is not given as parameter, it is added by the config interpreter when
calling the C function. The name of the function available in the configuration file may be different that the name of the C function.
There can be different C functions behind the same config file function, when the number of the parameters is different.
Here are the fixup functions. These functions convert from plain null-terminated strings to what the developer needs. Such a function
gets a pointer to the initial value and the index of the parameter for that function. Inside it the value can processed and replaced with a
new structure.
Next is a fixup function that converts the string value given in configuration file to an unsigned integer.
...
s.s = (char*)*param;
s.len = strlen(s.s);
if(str2int(&s, &ui)==0)
{
pkg_free(*param);
*param=(void *)(unsigned long)ui;
return 0;
}
LM_ERR("bad number <%s>\n", (char *)(*param));
return E_CFG;
}
/**
* fixup for functions that get one parameter
* - first parameter is converted to unsigned int
*/
int fixup_uint_null(void** param, int param_no)
{
if(param_no != 1)
{
LM_ERR("invalid parameter number %d\n", param_no);
return E_UNSPEC;
}
return fixup_uint(param);
}
...
fixup_uint(...) is a helper function, fixup_uint_null(...) is a fixup function that can be used for config function that get one char*
parameter that need to be interpreted as unsigned integer.
The files mod_fix.{c,h} implement a set of common fixup functions you can. Check that files before implementing a new fixup
function.
Recent work is focusing to add free fixup functions, that will help to clean up properly at shut down and use exported functions
dynamically at run time from linked applications.
Prior starting writing code, check with developers whether that functionality is already implemented. Also try to identify whether the
extension fits better in an existing module or needs to be created a new one.
when writing a DB driver module, the module name should start with db_.
when writing a MI transport, the module name should start with mi_
when writing a Presence Server extension, the name should start with presence_
when writing a PUA extension, the name should start with pua_
...
mkdir modules/my_new_module
...
For example, modules_k/regex implements Kamailio module interface and depends on external libpcre library and internal kmi library.
Its Makefile looks like:
...
include ../../Makefile.defs
auto_gen=
NAME=regex.so
ifeq ($(BUILDER),)
PCREDEFS=-I$(LOCALBASE)/include -I/usr/local/include -I/opt/include \
-I/usr/sfw/include
PCRELIBS=-L$(LOCALBASE)/lib -L/usr/local/lib -L/usr/sfw/lib \
-L/opt/lib -lpcre
else
PCREDEFS = $(shell pcre-config --cflags)
PCRELIBS = $(shell pcre-config --libs)
endif
DEFS+=$(PCREDEFS)
LIBS=$(PCRELIBS)
DEFS+=-DOPENSER_MOD_INTERFACE
SERLIBPATH=../../lib
SER_LIBS+=$(SERLIBPATH)/kmi/kmi
include ../../Makefile.modules
...
The NAME is specifying the name of shared object file for module. Follows a section where it tries to discover the location of external
shared library libpcre and its compile time flags. These are added to Makefile variables DEFS and LIBS. To the DEFS variable has to be
added also the type of interface - for Kamailio this is -DOPENSER_MOD_INTERFACE. For SER module interface it is -
DSER_MOD_INTERFACE.
The internal library dependencies are provided via SER_LIBS variable, as a space separated list of the paths to libraries. In this case it is
a dependency on library kmi, located at ../../lib/kmi/.
First and last lines include common Makefiles needed to build the core and modules - they have to be preserved as they are in this
example.
A very simple and pretty standard template to start building you module Makefile:
...
include ../../Makefile.defs
auto_gen=
NAME=my_new_module.so
LIBS=
DEFS+=-DOPENSER_MOD_INTERFACE
include ../../Makefile.modules
...
my_new_module.c
my_new_module_mod.c
In this file you must include the macro MODULE_VERSION to allow Kamailio to detect whether core and the module are same version
and compiled with same flags. You just simply add next line after all header files includes.
...
MODULE_VERSION
...
We exemplify the parameters exported by the modules modules_k/cfgutils and modules_k/pv - showing one integer parameter,
one string parameter and another string parameter that is set via a function.
For the parameters stored directly in a variable, you have to declare C variables of type int or char*.
...
static int initial = 10;
...
In the config, one can set many times the value for a parameter. In case of parameters stored in a variable, the last one is taken. For
those that use a function, it is up to the implementation what to do with each value. Actually here is the real benefit of using a function.
The param_set_shvar(...) function sets the initial value for a shared config file variable $shv(name). The function is implemented in
the file modules_k/pv/pv_shv.c. Check the readme of the module to see the format of the parameter value - it includes the name of
the shared variable as well as the value. So the function parses the value given from the configuration file, splits in name and value and
store in a local structure.
...
if(!shm_initialized()!=0)
{
LM_ERR("shm not initialized - cannot set value for PVs\n");
goto error;
}
s.s = (char*)val;
if(s.s == NULL || s.s[0] == '\0')
goto error;
p = s.s;
while(*p && *p!='=') p++;
if(*p!='=')
goto error;
s.len = p - s.s;
if(s.len == 0)
goto error;
p++;
flags = 0;
if(*p!='s' && *p!='S' && *p!='i' && *p!='I')
goto error;
if(*p=='s' || *p=='S')
flags = VAR_VAL_STR;
p++;
if(*p!=':')
goto error;
p++;
isv.s.s = p;
isv.s.len = strlen(p);
if(flags != VAR_VAL_STR) {
if(str2sint(&isv.s, &ival)<0)
goto error;
isv.n = ival;
}
if(mode==0) {
pkv = add_var(&s);
if(pkv==NULL)
goto error;
if(set_var_value(pkv, &isv, flags)==NULL)
goto error;
} else {
shv = add_shvar(&s);
if(shv==NULL)
goto error;
if(set_shvar_value(shv, &isv, flags)==NULL)
goto error;
}
return 0;
error:
LM_ERR("unable to set shv parame [%s]\n", s.s);
return -1;
}
...
So, actually, setting the parameter setshv for module pv produces a set of operations behind.
...
modparam("pv", "shvset", "debug=i:1")
...
The main purpose of this function is to check the sanity of the module parameter, load data from storage systems, initialize the
structured to be used at runtime. Here is the example from modules_k/cfgutils module:
...
if (!hash_file) {
LM_INFO("no hash_file given, disable hash functionality\n");
} else {
if (MD5File(config_hash, hash_file) != 0) {
LM_ERR("could not hash the config file");
return -1;
}
LM_DBG("config file hash is %.*s", MD5_LEN, config_hash);
}
probability=(int *) shm_malloc(sizeof(int));
if (!probability) {
LM_ERR("no shmem available\n");
return -1;
}
*probability = initial_prob;
...
First in the function is registering MI commands and then is the handling of the config hashing. If the appropriate parameter is not set,
it is initialized to the config file used by Kamailio. Then is computed the hash value that will be used for comparison, later at runtime.
Next is checking the probability parameter and create the variable in share memory to store it. At the end it initializes the variables for
global flags and lock sets.
In this function must be added the operations that has to be taken for each worker or special processes only once during the runtime, at
the start up time. Example of such operations are to open the connection to database, set the intial values for local variables per
process.
The function gets as parameter the rank of the child process. The rank is a positive number if it is a worker process and negative for
special processes like timer processes or TCP attendant. The defines with these special ranks are in file sr_module.h.
As an example, we show the child_init function of the module speeddial. The operations there are for opening the connection to
database.
...
db_handle = db_funcs.init(&db_url);
if (!db_handle)
{
LM_ERR("failed to connect database\n");
return -1;
}
return 0;
...
...
static void mod_destroy(void)
{
if (probability)
shm_free(probability);
if (gflags)
shm_free(gflags);
if(_cfg_lock_set!=NULL)
{
lock_set_destroy(_cfg_lock_set);
lock_set_dealloc(_cfg_lock_set);
}
}
...
As the parameter is given as string, but it is actually an integer value, a fixup function is used. It is in the list of the fixup functions
exported by mod_fix.h. The fixup function fixup_uint_null(...) is shown few sections above.
...
static cmd_export_t cmds[]={
...
{"sleep", (cmd_function)m_sleep, 1, fixup_uint_null, 0,
ANY_ROUTE},
...
{0, 0, 0, 0, 0, 0}
};
...
...
In the C function, as shown in the above example, the fitst parameter is cased to integer, because the fixup function replaced the
original string value with the integer representation.
This function return all the time 1 (TRUE in the configuration file). The function in C (m_sleep(...)) has a different name than the one
that can be used in the Kamailio configuration file (sleep(...)). Next shows how this function can be called in the configuration file to
introduce a pause of 3 seconds.
...
sleep("3");
...
The module pv exports most of the pseudo-variables. We will exemplify with $time(name), showing how a class of pseudo-variables can
have inner name. For that it exports the name parsing function pv_parse_time_name(...).
To increase the execution speed and not compare strings all the time, the name is kept internally as integer. At runtime, depending on
the value, the appropriate attribute is returned.
...
switch(in->len)
{
case 3:
if(strncmp(in->s, "sec", 3)==0)
sp->pvp.pvn.u.isname.name.n = 0;
else if(strncmp(in->s, "min", 3)==0)
sp->pvp.pvn.u.isname.name.n = 1;
else if(strncmp(in->s, "mon", 3)==0)
sp->pvp.pvn.u.isname.name.n = 4;
else goto error;
break;
case 4:
if(strncmp(in->s, "hour", 4)==0)
sp->pvp.pvn.u.isname.name.n = 2;
else if(strncmp(in->s, "mday", 4)==0)
sp->pvp.pvn.u.isname.name.n = 3;
else if(strncmp(in->s, "year", 4)==0)
sp->pvp.pvn.u.isname.name.n = 5;
else if(strncmp(in->s, "wday", 4)==0)
sp->pvp.pvn.u.isname.name.n = 6;
else if(strncmp(in->s, "yday", 4)==0)
sp->pvp.pvn.u.isname.name.n = 7;
else goto error;
break;
case 5:
if(strncmp(in->s, "isdst", 5)==0)
sp->pvp.pvn.u.isname.name.n = 8;
else goto error;
break;
default:
goto error;
}
sp->pvp.pvn.type = PV_NAME_INTSTR;
sp->pvp.pvn.u.isname.type = 0;
return 0;
error:
LM_ERR("unknown PV time name %.*s\n", in->len, in->s);
return -1;
}
...
static struct tm _cfgutils_ts;
static unsigned int _cfgutils_msg_id = 0;
if(msg==NULL || param==NULL)
return -1;
if(_cfgutils_msg_id != msg->id)
{
pv_update_time(msg, &t);
pv_update_time(msg, &t);
_cfgutils_msg_id = msg->id;
if(localtime_r(&t, &_cfgutils_ts) == NULL)
{
LM_ERR("unable to break time to attributes\n");
return -1;
}
}
switch(param->pvn.u.isname.name.n)
{
case 1:
return pv_get_uintval(msg, param, res, (unsigned int)_cfgutils_ts.tm_min);
case 2:
return pv_get_uintval(msg, param, res, (unsigned int)_cfgutils_ts.tm_hour);
case 3:
return pv_get_uintval(msg, param, res, (unsigned int)_cfgutils_ts.tm_mday);
case 4:
return pv_get_uintval(msg, param, res,
(unsigned int)(_cfgutils_ts.tm_mon+1));
case 5:
return pv_get_uintval(msg, param, res,
(unsigned int)(_cfgutils_ts.tm_year+1900));
case 6:
return pv_get_uintval(msg, param, res,
(unsigned int)(_cfgutils_ts.tm_wday+1));
case 7:
return pv_get_uintval(msg, param, res,
(unsigned int)(_cfgutils_ts.tm_yday+1));
case 8:
return pv_get_sintval(msg, param, res, _cfgutils_ts.tm_isdst);
default:
return pv_get_uintval(msg, param, res, (unsigned int)_cfgutils_ts.tm_sec);
}
}
...
Functions in pseudo-variable API return 0 in case of success and <0 in case of error.
One of the MI commands exported by module pv is the shv_set. It can be used to set the value of a shared variable via MI. The
command takes three parameters: name of the shared variable, the type of the value and the value. The C wrapper of the command is
the function mi_shvar_set(...).
...
node = cmd_tree->node.kids;
if(node == NULL)
return init_mi_tree( 400, MI_SSTR(MI_MISSING_PARM_S));
name = node->value;
if(name.len<=0 || name.s==NULL)
{
LM_ERR("bad shv name\n");
return init_mi_tree( 500, MI_SSTR("bad shv name"));
}
shv = get_shvar_by_name(&name);
if(shv==NULL)
return init_mi_tree(404, MI_SSTR("Not found"));
node = node->next;
if(node == NULL)
return init_mi_tree(400, MI_SSTR(MI_MISSING_PARM_S));
sp = node->value;
if(sp.s == NULL)
return init_mi_tree(500, MI_SSTR("type not found"));
flags = 0;
if(sp.s[0]=='s' || sp.s[0]=='S')
flags = VAR_VAL_STR;
node= node->next;
if(node == NULL)
return init_mi_tree(400, MI_SSTR(MI_MISSING_PARM_S));
sp = node->value;
if(sp.s == NULL)
{
return init_mi_tree(500, MI_SSTR("value not found"));
}
if(flags == 0)
{
if(str2sint(&sp, &ival))
{
LM_ERR("bad integer value\n");
return init_mi_tree( 500, MI_SSTR("bad integer value"));
}
isv.n = ival;
} else {
isv.s = sp;
}
lock_shvar(shv);
if(set_shvar_value(shv, &isv, flags)==NULL)
{
unlock_shvar(shv);
LM_ERR("cannot set shv value\n");
return init_mi_tree( 500, MI_SSTR("cannot set shv value"));
}
unlock_shvar(shv);
LM_DBG("$shv(%.*s) updated\n", name.len, name.s);
return init_mi_tree( 200, MI_OK_S, MI_OK_LEN);
}
...
The next example is from module xmpp. The function will start the process that is listening on a pipe for messages coming from SIP
side and create connections to the XMPP servers. Based on configuration option, the module will act as a XMPP component or server.
Remember that you have to declare how many new processes you want to start in mod_init() and use fork_process() in child_init() to
effectively start the new processes.
...
if (rank==PROC_MAIN) {
pid=fork_process(PROC_NOCHLDINIT, "XMPP Manager", 1);
if (pid<0)
return -1; /* error */
if(pid==0){
/* child */
/* initialize the config framework */
if (cfg_child_init())
return -1;
xmpp_process(1);
}
}
return 0;
}
...
...
struct module_exports exports = {
"cfgutils",
DEFAULT_DLFLAGS, /* dlopen flags */
cmds, /* exported functions */
params, /* exported parameters */
0, /* exported statistics */
mi_cmds, /* exported MI functions */
mod_items, /* exported pseudo-variables */
0, /* extra processes */
mod_init, /* module initialization function */
0, /* response function*/
mod_destroy, /* destroy function */
0 /* per-child init function */
};
...
support for many module interfaces - each module write can choose what module interface to implement and export
support for internal libraries - code that usually resided in the core, but was not for general purpose, just shared by several
modules, can be now part of an internal library
several fields in module exports became passive - this is a result of moving some code in internal libraries, thus they are not
handled by the core anymore
Since you developed the module for Kamailio v1.x, it is clear you have implemented the Kamailio-specific module interface. What you
need to do: edit module Makefile and add -DOPENSER_MOD_INTERFACE to DEFS variable:
...
DEFS+=-DOPENSER_MOD_INTERFACE
...
Some examples of code that was during the past in core and now is part of internal libraries:
Database API
MI API
Statistics API
To get your module compiling, you have to update the paths to include directives. Internal libraries are located in directories inside
lib/. For example, what was in v1.x as:
...
#include "../../db/db.h"
...
is in v3.x:
...
#include "../../lib/srdb1/db.h"
...
You may need also to update the names of data structures and types, or API functions.
In the Makefile, you have to list the interal library dependencies. For example, a module that connects to database and exports MI
commands has in Makefile:
...
SERLIBPATH=../../lib
SER_LIBS+=$(SERLIBPATH)/srdb1/srdb1
SER_LIBS+=$(SERLIBPATH)/srdb1/kmi
SER_LIBS+=$(SERLIBPATH)/kcore/kcore
...
Internal kcore library collects code from old v1.x that didn't meet the requirements of the new architecture for the core v3.x. It also
includes the code for statistics API - it most of the cases, you may need to link old Kamailio modules to it.
Regarding the passive fields in the module exports, practically they are:
MI commands
Extra processes
Statistics
To get them registered to the core framework, you have to use now dedicated functions in mod_init() and child_init(). See the specific
chapters in this guide that approach these topics for real examples of how to do it for each one.
An internal library is automatically loaded at runtime if there is a module in config file that requires code from it
core is smaller - more suitable for embedded devices, as well as it is more stable
overall footprint is smaller - duplicated code in several modules can be collected in an internal library
development flexibility - code offering same functionality by a different implementation can co-exist, allowing to switch and test
which one is better, without adding/removing code from code or modules
When adding a new internal library, simply create a folder for it and place the Makefile, source code and header files inside it.
17.2. Library Makefile
The Makefile for internal library specify name, version and external dependencies for it.
...
include ../../Makefile.defs
auto_gen=
NAME:=trie
MAJOR_VER=1
MINOR_VER=0
BUGFIX_VER=0
LIBS=
include ../../Makefile.libs
...
The above example is from trie library, which will result on Linux in an object file libtrie.1.0.0.so.
Other Makefile variables such as DEFS or SER_LIBS can be used for libraries in the same manner as for modules. For example, an
internal library can depend on another internal library - simply add the dependency to SER_LIBS variable.
A good practice is to declare only static global variables and export functions that give access (read/write) to them. Exported functions
(non-static functions) should be prefixed by a token that tries to suggest the library and build an unique name. If you look inside trie
library, the lib/tree/dtree.c file, all the functions are prefixed with dtree_.
To use a library, include the files with the prototypes of the functions that you need in your module and then use them where you need.
In the Makefile of the module, you have to add the dependency on the respective library by setting accordingly the variable
SER_LIBS. See Module Development section for more details on this topic.
Note that starting with v3.0.0, the contributions done to core components and main modules (tm, sl, auth) have to be done under BSD
license. This was decided when integration of Kamailio and SER projects started in order to avoid conflicts between original developers
and new ones.
If the code includes parts having other license make sure it is compatible with the GPLv2 and the way it is used now does not violate the
original license and the copyright.
Each C source file that is added under GPLv2 must include a header as follows:
...
/**
* Copyright (C) YEARS OWNER
*
* This file is part of Kamailio, a free SIP server.
*
* kamailio 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
*
* kamailio 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
*/
...
Each C source file that is added under BSD license must include a header as follows:
...
/**
* Copyright (C) YEARS OWNER
*
* This file is part of Kamailio, a free SIP server.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
...
The tutorial is going to be included in the Kamailio repository, if you want to post messages with improvements or mistakes you can
write emails to <sr-dev [at] lists [dot] sip-router [dot] org> or use the documentation tracker from sip-router.org.