KAMAILIO (OPENSER) Devel Guide

KAMAILIO (OPENSER) Devel Guide

Daniel-Constantin Mierla

Elena-Ramona Modroiu

Abstract

This book documents the internal architecture of KAMAILIO (OPENSER) SIPServer, providing the details useful to develop extensions in thecore or as a module.

The focus will be on major components of the SIP server, such asmemory manager, locking system, parser, database API,configuration file, MI commands, pseudo-variablesand module interface.

Examples, API and the architecture are based on current developmentversion of KAMAILIO (OPENSER) - 1.4.0 -at April 2, 2008. Some may change until 1.4.0 is released asstable. The document will be timely updated accordingly, check forupdates atwww.asipto.com orwww.kamailio.org.

This document is free to use for anybody. The authors are notliable in any way for the consequences you may get due to usageof this document.


Table of Contents

1. Introduction
1.1. KAMAILIO (OPENSER) SIP Server 1.2. About the authors 1.3. Important
2. KAMAILIO (OPENSER) Architecture
2.1. KAMAILIO (OPENSER) Core 2.2. KAMAILIO (OPENSER) 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_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_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
8. Extending configuration file
8.1. Adding a core parameter 8.2. Adding a core function
8.2.1. Extending the grammar 8.2.2. Extending the interpreter
9. Database API
9.1. DB API Structure 9.2. DB 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.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 db_res_t
9.4. Macros 9.5. Example of usage
10. Management Interface
10.1. MI Command Function 10.2. Register MI Command 10.3. Example of MI Command Function 10.4. MI FIFO Command
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
16. Module Development
16.1. module_exports type 16.2. cmd_export_t type 16.3. param_export_t type 16.4. proc_export_t type 16.5. stat_export_t type 16.6. mi_export_t type 16.7. pv_export_t 16.8. Functions Types 16.9. Command Functions
16.9.1. Return Values 16.9.2. Fixup Functions
16.10. Developing a new module
16.10.1. Naming the module 16.10.2. Makefile 16.10.3. Main File 16.10.4. Add Module Parameter 16.10.5. Module Init Function 16.10.6. Module Child Init Function 16.10.7. Module Destroy Function 16.10.8. Add Command Function 16.10.9. Add Pseudo-Variable 16.10.10. Add MI Command 16.10.11. Add Extra Process 16.10.12. CFGUTILS module_exports
17. Licensing 18. References 19. Contact Details

Chapter 1. Introduction

1.1. KAMAILIO (OPENSER) SIP Server

July 28, 2008, the project name changed from OPENSER to KAMAILIO.In June 2005, KAMAILIO (OPENSER) was born as asplit from SIP Express Router project of FhG FOKUS Institute, Berlin, Germany.The newly created project was aiming to create an open development environment to build robust and scalable open source SIP server.

The website of the project is http://www.kamailio.org.The source code is hosted on sourceforge.net SVNrepository.

Today KAMAILIO (OPENSER) SIP Server is a reference implementation, featuring hundreds of VoIP servicesworld wide, being developed by people around the world. It is included in official distributionsof several Linux and BSD flavors.

The number of registered developers and packagers exceeded 25. The level of contributions and the amount of contributors has an important impact on the evolution of the project. The book triesto ease the understanding of KAMAILIO (OPENSER) from a developer point of view, giving the kick startknowledge, it does not intend to be a cookbook. Efforts to improve the documentation in the sources is undertaken and make it doxygen compliant, makinga good developer documentation out of there.

1.2. About the authors

Daniel-Constantin Mierla is one of the co-founders ofKAMAILIO (OPENSER) SIP Server project. He is involved in VoIP and SIP since beginning of 2002,at FhG FOKUS Institute, Berlin, Germany, being involved as core developer in theSIP Express Router project. Currently he is employed byASIPTO, an KAMAILIO (OPENSER)-focusedcompany. Daniel is an active OPENSER developer and member of management board.

Elena-Ramona Modroiu is one of the co-founder of KAMAILIO (OPENSER) SIP Server project. She got involved in VoIP and SIP while working at hergraduation thesis within SIP Express Router project at FhG FOKUS Institute. She completedstudies at Polytechnic University of Valencia and Siemens Germany, working now atASIPTO,being an active developer and member of management board of KAMAILIO (OPENSER).

The two authored many online tutorials about KAMAILIO (OPENSER) that include KAMAILIO (OPENSER)Core Cookbook, KAMAILIO (OPENSER) Transformations,KAMAILIO (OPENSER) Pseudo-variables, KAMAILIO (OPENSER) and Asterisk Integration,KAMAILIO (OPENSER) and FreeRADIUS.

1.3. Important

This document is focusing only to KAMAILIO (OPENSER) specific API, has no intention to teachC programing for Linux and Networking. You, as a reader, should have already thebasic knowledge of C programming.

Do not contact the authors to ask about standard C functions or variables.

There are many references to parts of code in the source tree. You must be familiarwith the directory structure of KAMAILIO (OPENSER). It is not our intention to explain howsomething was implemented, but how to use existing code to extend KAMAILIO (OPENSER) easily.

The source code remains the best reference for developers. In the last time, the commentsaround the important functions in KAMAILIO (OPENSER) have been improved and converted todoxygen format. You should double-check the sourcecode if the prototype of the functions presented in this document are still valid.

Daily updated doxygen documentation is available athttp://devel.openser.org/doxygen/.

Chapter 2. KAMAILIO (OPENSER) Architecture

KAMAILIO (OPENSER) has a modular architecture. As a big picture, there are two main categories:

  • the core - it is the component that provides the low-levelfunctionalities for KAMAILIO (OPENSER)

  • the modules - are the components that provides the mostof the functionalities that make KAMAILIO (OPENSER) powerful in real world deployments.

Figure 2.1. KAMAILIO (OPENSER) Architecture

KAMAILIO (OPENSER) Architecture

2.1. KAMAILIO (OPENSER) Core

The core includes:

  • memory manager

  • SIP message parser

  • locking system

  • DNS and transport layer management (UDP, TCP, TLS, SCTP)

  • configuration file parser and interpreter

  • database abstraction layer (DB API)

  • management interface (MI) API

  • stateless forwarding

  • pseudo-variables and transformations engines

  • statistics engine

  • timer API

2.2. KAMAILIO (OPENSER) Modules

With the modules you can get functionalities such as:

  • user location management

  • accounting, authorization and authentication

  • text and regular expression operations

  • stateless replying

  • stateful processing

  • instant messaging and presence extensions

  • RADIUS support

  • database connectors

  • MI transports

  • CPL interpreter

  • sms and xmpp gateways

  • NAT traversal

  • Perl and Java SIP Servlet extensions

2.3. SIP Message Processing

The execution of KAMAILIO (OPENSER) configuration file is triggered when receiving a SIP message from the network. The processing flow is different fora SIP request or a SIP reply.

Figure 2.2. SIP Request Processing

SIP Request Processing

Figure 2.3. SIP Reply Processing

SIP Reply Processing

The document contains a chapter dedicated to Configuration File that explains its structure and the typesof routing blocks.

Chapter 3. Locking system

KAMAILIO (OPENSER) provides a custom locking system which has a simple interface for development.Its root element is a mutex semaphore, that can be set (locked) or unset (unlocked). The restof synchronization mechanisms available in SysV and POSIX not being needed.

The locks can be used as simple variables or lock sets (array of simple locks). To improvethe speed, behind the locks is, by default, machine-specific code. If the architecture of themachine is unknown, KAMAILIO (OPENSER) will use SysV semaphores.

3.1. Simple Locks API

Basically, to create a lock, you have to define a variable of typegen_lock_t, allocate it in shared memory and initialize it.Then you can perform set/unset operations, destroying the variable when you finish using it.

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 nextsections.

3.1.1. gen_lock_t

It is the type to define a lock variable. It must be allocated in shared memory, to beavailable across KAMAILIO (OPENSER) processes.

Example 3.1. Defining a lock

...
#include "locking.h"

gen_lock_t lock;
...
				

3.1.2. lock_alloc(...)

Allocates a lock in shared memory. If your gen_lock_tis not part of a structure allocated in shared memory, you have to use this function toproperly create the lock.

Example 3.2. Prototype

...
gen_lock_t* lock_alloc();
...
				

It returns the pointer to a shared memory structure.

Example 3.3. Example of usage

...
#include "locking.h"
...
gen_lock_t *lock;
lock = lock_alloc();
if(lock==NULL)
{
	LM_ERR("cannot allocate the lock\n");
	exit;
}
...
				

3.1.3. lock_dealloc(...)

Free the shared memory allocated for lock.

Example 3.4. Prototype

...
void lock_dealloc(gen_lock_t* lock);
...
				

The parameter is a variable returned by lock_alloc(...).

Example 3.5. Example of usage

...
#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.

Example 3.6. Prototype

...
gen_lock_t* lock_init(gen_lock_t* lock);
...
				

It returns the parameter if there is no error, NULLif error occurred.

Example 3.7. Example of usage

...
#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 mustcall it before deallocating a lock to ensure proper clean up (if SysV is used, it callsthe functions to remove the semaphores).

Example 3.8. Prototype

...
void lock_destroy(gen_lock_t* lock);
...
				

The parameter is a lock initialized with lock_init(...).

Example 3.9. Example of usage

...
#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 untilthe lock is unset.

Example 3.10. Prototype

...
void lock_get(gen_lock_t* lock);
...
				

The parameter must be initialized with lock_init(...)before calling this function.

Example 3.11. Example of usage

...
#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_release(...)

Perform unset operation on a lock. If the lock is set, it will unblock it. Youshould call it after lock_set(...), after finishingthe operations that needs synchronization and protection against race conditions.

Example 3.12. Prototype

...
void lock_release(gen_lock_t* lock);
...
				

The parameter must be initialized before calling this function.

Example 3.13. Example of usage

...
#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 alock set, you have to define a variable of typegen_lock_set_t, allocate it in shared memory specifying thenumber 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 nextsections.

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 beavailable across KAMAILIO (OPENSER) processes.

Example 3.14. Defining a lock

...
#include "locking.h"

gen_lock_set_t lock_set;
...
				

3.2.2. lock_set_alloc(...)

Allocates a lock set in shared memory.

Example 3.15. Prototype

...
gen_lock_set_t* lock_set_alloc(int n);
...
				

The parameter n specifies the numberof locks in the set. Return pointer to the lock set, or NULL if the set couldn'tbe allocated.

Example 3.16. Example of usage

...
#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.

Example 3.17. Prototype

...
void lock_set_dealloc(gen_lock_set_t* set);
...
				

The parameter has to be a lock set allocated withlock_set_alloc(...).

Example 3.18. Example of usage

...
#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;
}
...
lock_set_dealloc(set);
...
				

3.2.4. lock_set_init(...)

Initialized the internal attributes of a lock set. The lock set has to be allocatedwith lock_set_alloc(...). This function must becalled before performing any set/unset operation on lock set.

Example 3.19. Prototype

...
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 beinitialized, otherwise returns the pointer to the lock set.

Example 3.20. Example of usage

...
#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 onceyou finished to use the lock set. The function must be called afterlock_set_init(...)

Example 3.21. Prototype

...
void lock_set_destroy(gen_lock_set_t* set);
...
				

The parameter is an initialized lock set. After calling this function you should notperform anymore set/unset operations on lock set.

Example 3.22. Example of usage

...
#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_destroy(set);
lock_set_dealloc(set);
...
				

3.2.6. lock_set_get(...)

Set (block) a lock in the lock set. You should call this function after the lock sethas been initialized. If the lock is already set, the function waits until that lock isunset (unblocked).

Example 3.23. Prototype

...
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 tobe set. First lock in set has index 0. The index parameter must be between0 and n-1 (seelock_set_alloc(...).

Example 3.24. Example of usage

...
#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_release(...)

Unset (unblock) a lock in the lock set.

Example 3.25. Prototype

...
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 tobe unset. First lock in set has index 0. The index parameter must be between0 and n-1 (seelock_set_alloc(...).

Example 3.26. Example of usage

...
#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 (OPENSER) processes eat lot of CPU.If the traffic load does not justify such behavior and no more SIP messages are processed, the onlysolution is to troubleshoot and fix the locking error. The problem is that a lock is set but neverunset. A typical case is when returning due to an error and forgetting to release a previously lockset.

To troubleshoot a solution is to use gdb, attach to the processthat eats lot of CPU and get the backtrace. You need to get the PID of that KAMAILIO (OPENSER) process -top or ps tools can be used.

...
# gdb /path/to/openser PID
...
		

From the backtrace you should get to the lock that is set and not released. From there you shouldstart the investigation - what are the cases to set that lock and in which circumstances it does notget released.

Chapter 4. Memory Manager

As KAMAILIO (OPENSER) is a multi-process application, the usage of shared memory isrequired in many scenarios. The memory manager tries to simplify the work withshared and private memory, providing a very simple programming interface.The internal design took in consideration speed optimizations, somethingvery important for a real-time communication server.

The manager is initialized at start-up, creating the chunks for private and sharedmemory. It is important to know that the shared memory is not available duringconfiguration parsing, that includes the setting of module parameters.

When the own memory manager cannot be used, KAMAILIO (OPENSER) falls back to the SysV sharedmemory 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.

Figure 4.1. KAMAILIO (OPENSER) Memory Management

KAMAILIO (OPENSER) Memory Management

4.1. Private Memory

This type of memory is specific per process, no synchronization is needed toaccess structures allocated in it. It should be used for variables that do notneed to be visible in other KAMAILIO (OPENSER) processes or for temporary operations.

To store static values in private memory and have it in all processes withoutthe need to synchronize for accessing it, you must create it before KAMAILIO (OPENSER)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.

Example 4.1. Prototype

...
void* pkg_malloc(unsigned int size);
...
				

The parameter specifies the size of memory space to be allocated. Returns the pointer tomemory 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.

Example 4.3. Prototype

...
void *pkg_free(void *p);
...
				

The parameter is the pointer to the memory to be freed.

Example 4.4. 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;
}
strcpy(p, "openser");
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 oldmemory chunk to the new one. If the space after the old chunk is free and large enoughto scale to the new size, the KAMAILIO (OPENSER) memory manager will set the size of the old chunkto the new size, marking properly the memory zone, in this way, the copy operationis skipped.

Example 4.5. Prototype

...
void *pkg_realloc(void *p, unsigned int size);
...
				

The first parameter is the pointer to the memory space that needs to be re-sized. Thesecond parameter is the new size in bytes. The function return the pointer to the newmemory space, or NULL if an error occurred. Beware that the returned pointer may bedifferent than the old pointer.

Example 4.6. 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;
}
strcpy(p, "openser");
LM_DBG("string value at %p is [%s]\n", p, p);
p = (char*)pkg_realloc(p, 16*sizeof(char));
if(p==NULL)
{
	LM_ERR("cannot re-allocate pkg memory\n");
	exit;
}
strcat(p, " server");
LM_DBG("string value at %p is [%s]\n", p, p);

pkg_free(p);
...
				

4.2. Shared Memory

The data stored in shared memory is visible in all KAMAILIO (OPENSER) modules. It is the spacewhere user location records are stored, the TM structures for stateful processing, routingrules for the dispatcher or the lcr module, and many more.

The shared memory is initialized after the config file is parsed, because it need to knowthe user and group KAMAILIO (OPENSER) is running under, for the case when the memory manger uses SysVoperations.

To use shared memory functions in your C code you need to include the file:mem/shm_mem.h. When accessing shared memory data, youneed to make sure that you don't have a race between different KAMAILIO (OPENSER) processes, for example protect the access via a lock.

4.2.1. shm_malloc(...)

Allocates space in shared memory.

Example 4.7. Prototype

...
void *shm_malloc(unsigned int size);
...
				

The parameter specifies the size in bytes of the desired shared memory space. It returnsthe pointer to shared memory in case of success, or NULL if an error occurred.

Example 4.8. Example of usage

...
#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(...).

Example 4.9. Prototype

...
void shm_free(void *p);
...
				

The parameter is the pointer to the shared memory space to be freed.

Example 4.10. Example of usage

...
#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, "openser");
LM_DBG("string value at %p is [%s]\n", p, p);
shm_free(p);
...
				

4.2.3. shm_realloc(...)

Realloc a previously allocated shared memory chunk. It copies the content of the oldmemory chunk to the new one.

Example 4.11. Prototype

...
void *shm_realloc(void *p, unsigned int size);
...
				

The first parameter is the pointer to the memory space that needs to be re-sized. Thesecond parameter is the new size in bytes. The function return the pointer to the newmemory space, or NULL if an error occurred. Beware that the returned pointer may bedifferent than the old pointer.

Example 4.12. Example of usage

...
#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, "openser");
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. Itresults in out of memory messages. Note that suchmessages might be because of a too small size of the memory for the traffic, data or numberof subscribers that KAMAILIO (OPENSER) has to handle.

  • memory overwriting - writing more than was allocated for that structure. It results inan segmentation fault, crashing KAMAILIO (OPENSER).

KAMAILIO (OPENSER) has an internal debugger for memory - it is able to show the chunks allocatedin private or shared memory and the file and line from where it was allocated. Indebugging mode it prints all calls for allocations and freeing memory.

To enable the memory debugger you have to recompile KAMAILIO (OPENSER). Before that you have to edit thefile Makefile.defs and remove the defineF_MALLOC (-DF_MALLOC)and add the define DBG_QM_MALLOC(-DDBG_QM_MALLOC).

Once compiled and installed with memory debugging you have to set memlogparameter to a value lower than debug in configuration file. You canstart KAMAILIO (OPENSER) and try to reproduce the errors again. Once memory leak errors are printed you can eithersend a signal USR1 to the process that printed the messagesor stop KAMAILIO (OPENSER). You get in the syslog file the status of the memory. If you see memory allocationdone from the same place in the sources, many times, at runtime, it is a memory leak. If not, increasethe memory size to fit your load needs and run again -- if you don't get the memory leak errors it wasthe case of insufficient memory allocated for KAMAILIO (OPENSER).

For memory overwriting a core should be generated. If yes, you can investigate it withgdb.

...
# gdb /path/to/openser corefile
...
		

From the backtrace you should get the file and line where the overwriting happened. In casea core is not generated, check the messages in the syslog. Look forBUG and error, forhead or tail of a memory chunk being overwriting.

Chapter 5. Data structures

In this chapter we focus on most used data structures insideKAMAILIO (OPENSER) sources. Most of them relate to SIP message structure.Other important data structures are explained in the chaptersdetailing specific components or functionalities -- for example,see Database API orPseudo-variables chapters.

5.1. str

SIP is a text-based protocol, therefore lot of operationsresume to text manipulation. KAMAILIO (OPENSER) uses references in theSIP message body most of the time, doing it via an anchorpointer and the length. For that it uses thestr structure.

The str structure is definedin file str.h.

Example 5.1. Definition

...
struct _str{
	char* s; /* pointer to the beginning of string (char array) */
	int len; /* string length */
};

typedef struct _str str;
...
			

Example 5.2. Example of usage

...
#include "str.h"
...
str s;
s.s = "openser";
s.len = strlen(s.s);
LM_DBG("the string is [%.*s]\n", s.len, s.s);
...
			

5.2. struct sip_uri

This is the structure holding a parsed SIP URI. You can fill itby calling parse_uri(...) function.

The structure is defined in file parser/msg_parser.h.

Example 5.3. Definition

...	
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 aboutthe format of SIP URI read RFC3261. Example of SIP URI:

sip:alice@sipserver.org:5060;transport=tcp

5.3. struct sip_msg

This is the main structure related to a SIP message. When a SIP message is received fromthe network, it is parsed in such structure. The pointer to this structure is given asparameter to all functions exported by modules to be used in the configuration file.

The structure is defined in file parser/msg_parser.h.

Example 5.4. Definition

...
struct sip_msg {
	unsigned int id;               /* message id, unique/process*/
	struct msg_start first_line;   /* Message first line */
	struct via_body* via1;         /* The first via */
	struct via_body* via2;         /* The second via */
	struct hdr_field* headers;     /* All the parsed headers*/
	struct hdr_field* last_header; /* Pointer to the last parsed header*/
	hdr_flags_t parsed_flag;       /* Already parsed header field types */

	/* Via, To, CSeq, Call-Id, From, end of header*/
	/* pointers to the first occurrences of these headers;
	 * everything is also saved in 'headers' (see above)
	 */

	/* shorcuts to known headers */
	struct hdr_field* h_via1;
	struct hdr_field* h_via2;
	struct hdr_field* callid;
	struct hdr_field* to;
	struct hdr_field* cseq;
	struct hdr_field* from;
	struct hdr_field* contact;
	struct hdr_field* maxforwards;
	struct hdr_field* route;
	struct hdr_field* record_route;
	struct hdr_field* path;
	struct hdr_field* content_type;
	struct hdr_field* content_length;
	struct hdr_field* authorization;
	struct hdr_field* expires;
	struct hdr_field* proxy_auth;
	struct hdr_field* supported;
	struct hdr_field* proxy_require;
	struct hdr_field* unsupported;
	struct hdr_field* allow;
	struct hdr_field* event;
	struct hdr_field* accept;
	struct hdr_field* accept_language;
	struct hdr_field* organization;
	struct hdr_field* priority;
	struct hdr_field* subject;
	struct hdr_field* user_agent;
	struct hdr_field* content_disposition;
	struct hdr_field* accept_disposition;
	struct hdr_field* diversion;
	struct hdr_field* rpid;
	struct hdr_field* refer_to;
	struct hdr_field* session_expires;
	struct hdr_field* min_se;
	struct hdr_field* ppi;
	struct hdr_field* pai;
	struct hdr_field* privacy;

	struct sdp_info* sdp; /* parsed SDP body */

	char* eoh;        /* pointer to the end of header (if found) or null */
	char* unparsed;   /* here we stopped parsing*/
	
	struct receive_info rcv; /* source and dest ip, ports, proto a.s.o*/

	char* buf;        /* scratch pad, holds a unmodified message,
                           *  via, etc. point into it */
	unsigned int len; /* message len (orig) */

	/* modifications */

	str new_uri; /* changed first line uri, when you change this
                  * don't forget to set parsed_uri_ok to 0 */

	str dst_uri; /* Destination URI, must be forwarded to this URI if len!=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*/

	/* the same for original uri */
	int parsed_orig_ruri_ok;
	struct sip_uri parsed_orig_ruri;

	struct lump* add_rm;       /* used for all the forwarded requests/replies */
	struct lump* body_lumps;     /* Lumps that update Content-Length */
	struct lump_rpl *reply_lump; /* only for localy generated replies !!!*/

	/* whatever whoever want to append to branch comes here */
	char add_to_branch_s[MAX_BRANCH_PARAM_LEN];
	int add_to_branch_len;
	
	/* index to TM hash table; stored in core to avoid 
	 * unnecessary calculations */
	unsigned int  hash_index;

	/* flags used from script */
	flag_t flags;

	/* 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 */
	unsigned int msg_flags;

	str set_global_address;
	str set_global_port;

	/* force sending on this socket */
	struct socket_info* force_send_socket;

	/* create a route HF out of this path vector */
	str path_vec;
};
...
			

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 structurepoint directly inside the SIP message buffer.

Example of a SIP message:

...
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
...
		

5.4. struct msg_start

The structure corresponds to a parsed representation of the first line in a SIP message.It is defined in file parser/parse_fline.h.

Example 5.5. Definition

...
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 usethe function parse_fline(...).

5.5. struct hdr_field

The structure holding a parsed SIP header. It is defined in fileparser/hf.h.

Example 5.6. Definition

...
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 functionparse_headers(...). The function takes as parametera bitmask flag that can specify what headers you need to be parsed. For example, to parsethe From and To headers:

parse_headers(msg, HDR_FROM_F|HDR_TO_F, 0);

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 listwith 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 */
};
...
		

If the type of hdr_field structure is HDR_TO_T it isthe parsed To header.

The attribute parsed may hold the parsed representationof the header body. For example, for Content-Lenghtheader it contains the content length value as integer.

5.6. struct to_body

The structure holds a parsed To header. Same structure is used for From header and the otherheaders that have same structure conform to IETF RFCs. The structure is defined in fileparser/parse_to.h.

Example 5.7. Definition

...
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 */
};
...
			

To parse a To header you have to use function parse_to(...).

5.7. struct via_body

The structure holds a parsed Via header. It is defined in fileparse_via.h.

Example 5.8. Definition

...
struct via_body { 
	int error;          /* set if an error occurred during parsing */
	str hdr;        /* header name "Via" or "v" */
	str name;       /* protocol name */
	str version;    /* protocol version */
	str transport;  /* transport protocol */
	str host;       /* host part of Via header */
	unsigned short proto; /* transport protocol as integer*/
	unsigned short port;  /* port number as integer */
	str port_str;         /* port number as string*/
	str params;           /* parameters */
	str comment;          /* comment */
	unsigned int bsize;           /* body size, not including hdr */
	struct via_param* param_lst;  /* list of parameters*/
	struct via_param* last_param; /*last via parameter, internal use*/

	/* shortcuts to "important" params*/
	struct via_param* branch;     /* branch parameter */
	str tid;                      /* transaction id, part of branch */
	struct via_param* received;   /* received parameter */
	struct via_param* rport;      /* rport parameter */
	struct via_param* i;          /* i parameter */
	struct via_param* alias;      /* alias see draft-ietf-sip-connect-reuse-00 */
	struct via_param* maddr;      /* maddr parameter */
	struct via_body* next;        /* pointer to next via body string if compact Via or null */
};
...
			

The str attributes in the structure are referenced to SIP message buffer. To parse a Viaheader you have to use the function parse_via(...).

Chapter 6. SIP Parser

KAMAILIO (OPENSER) includes its own implementation of SIP parser. It is knownas lazy orincremental parser. That meansit parses until it founds the required elements or encounters endsof SIP message.

All parsing functions and data structures are in the files from thedirectory parser. The main filefor SIP message parsing isparser/msg_parser.c with thecorresponding header fileparser/msg_parser.h.

It does not parse entirely the parts of the SIP message. For most ofthe SIP headers, it identifies the name and body, it does not parsethe content of header's body. It may happen that a header is malformedand KAMAILIO (OPENSER) does not report any error as there was no request toparse that header body. However, most used headers are parsed entirelyby default. Such headers are top most Via, To, CSeq, Content-Lenght.

The parser does not duplicate the values, it makes references insidethe SIP message buffer it parses. For theparsed structures it allocatesprivate memory. It keeps the state of parsing, meaning that it hasan anchor to the beginning of unparsed part of the SIP message andstores a bitmask of flags with parsed known headers. If the functionto parse a special header is called twice, the second time will returnimmediately as it finds in the bitmask that the header was alreadyparsed.

This chapter in not intended to present all parsing functions, youhave to check the files in the directoryparser. There is kind of namingconvention, so if you need the function to parse the headerXYZ, look for the filesparser/parse_XYZ.{c,h}. If youdon't find it, second try is to usectags to locate aparse_XYZ(...) function. If noluck, then ask on KAMAILIO (OPENSER) development mainling listdevel@lists.openser.org. Finalsolution in case on negative answer is to implement it yourself. Forexample, CSeq header parser is inparser/parse_cseq.{c,h}.

The next sections will present the parsing functionsthat 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);
...
			

Example 6.2. Example of usage

...
...
			

6.2. parse_msg(...)

A developer does not interfere too much with this function as it is calledautomatically by KAMAILIO (OPENSER) 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. Youshould be aware that it is not enough to call this function and then run theactions from the configuration file. There are some attributes in the structuresip_msg that are specific to the environment:received socket, source IP and port, ...

Example 6.3. Prototype

...
int parse_msg(char* buf, unsigned int len, struct sip_msg* msg);
...
			

Return 0 if parsing was OK, >0 if error occurred.

Example 6.4. Example of usage

...
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 flagsflags are found. The parameternext can be used when a header canoccur many times in a SIP message, to continue parsing until a new headerof that type is found.

The values that can be used for flags aredefined in parser/hf.h.

When searching to get a specific header, all the headers encountered during parsingare hooked in the structure sip_msg.

Example 6.5. Prototype

...
int parse_headers(struct sip_msg* msg, hdr_flags_t flags, int next);
...
			

Return 0 if parsing was sucessful, >0 in case of error.

Example 6.6. Example of usage

...
if(parse_headers(msg, HDR_CALLID_F, 0)!=0)
{
	LM_ERR("error parsing CallID header\n");
	return -1;
}
...
			

6.4. parse_to(...)

Parse a buffer that contains the body of a To header. The function isdefined in parser/parse_to.h.

Example 6.7. Prototype

...
char* parse_to(char* buffer, char *end, struct to_body *to_b);
...
			

Return 0 in case of success, >0 in case of error.

The next example shows the parse_from(...)function that makes use of parse_to(...) toparse the body of header From. The functionis located in file parser/parse_from.c.

The structure filled at parsing is hooked in the structuresip_msg, inside the attributefrom, which is the shortcut to theheader From.

Example 6.8. Example of usage

...

int parse_from_header( struct sip_msg *msg)
{
	struct to_body* from_b;

	if ( !msg->from && ( parse_headers(msg,HDR_FROM_F,0)==-1 || !msg->from)) {
		LM_ERR("bad msg or missing FROM header\n");
		goto error;
	}

	/* maybe the header is already parsed! */
	if (msg->from->parsed)
		return 0;

	/* first, get some memory */
	from_b = pkg_malloc(sizeof(struct to_body));
	if (from_b == 0) {
		LM_ERR("out of pkg_memory\n");
		goto error;
	}

	/* now parse it!! */
	memset(from_b, 0, sizeof(struct to_body));
	parse_to(msg->from->body.s,msg->from->body.s+msg->from->body.len+1,from_b);
	if (from_b->error == PARSE_ERROR) {
		LM_ERR("bad from header\n");
		pkg_free(from_b);
		set_err_info(OSER_EC_PARSER, OSER_EL_MEDIUM, "error parsing From");
		set_err_reply(400, "bad From header");
		goto error;
	}
	msg->from->parsed = from_b;

	return 0;
error:
	return -1;
}

...
			

6.5. Get Message Body

Next example shows how to get the content of SIP message body in a 'str'variable.

Example 6.9. Example of usage

...
int get_msg_body(struct sip_msg *msg, str *body)
{
	/* 'msg' is a pointer to a valid struct sip_msg */

	/* get message body
	- after that whole SIP MESSAGE is parsed
	- calls internally parse_headers(msg, HDR_EOH_F, 0)
	*/
	body->s = get_body( msg );
	if (body->s==0) 
	{
		LM_ERR("cannot extract body from msg\n");
		return -1;
	}

	/* content-length (if present) must be already parsed */
	if (!msg->content_length) 
	{
		LM_ERR("no Content-Length header found!\n");
		return -1;
	}
	body->len = get_content_length( msg );
	return 0;
}
...
			

6.6. Get Header Body

You can see in the next example how to access the body of headerCall-ID.

Example 6.10. Example of usage

...
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);
}
...
			

6.7. New Header Parsing Function

The section gives the guidelines to add a new function for parsinga header.

  • add source and header files in directory parsernaming them parse_hdrname.{c,h}.

  • if the header is used very often, consider doing speed optimization by allocatinga header type and flag. That will allow to identify the header via integer comparisonafter the header was parsed and added in headerslist in the structure sip_msg.

  • make sure you add the code to properly clean up the header structure when thestructure sip_msg is freed.

  • make sure that the tm module properly clonesthe header or it resets the pointers to the header when copying the structuresip_msg in shared memory.

Chapter 7. Transport Layer

As a developer, the interaction with the transport layer is lowerand lower. It is already implemented the support for UDP, TCP, TLS and SCTP.From the modules, you can use the API exported by sland tm modules to send stateless replies, or tosend stateful requests/replies. Sending stateless requests can be done with the functionsfrom core, exported in file forward.h.

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 newextensions, you don't have to care about reading/writing from/to network.

If you want to investigate the implementation of transport layers, you can start with:

  • udp_*.{c,h} for UDP

  • tcp_*.{c,h} for TCP

  • tls/*.{c,h} for TLS

  • sctp_*.{c,h} for SCTP

7.1. DNS Implementation

KAMAILIO (OPENSER) follows the requirements specified in RFC3263 for server and service location.That includes support for NAPTR andSRV records as well.

To investigate the implementation related to DNS, start with filesresolve.{c,h}.

Chapter 8. Extending configuration file

flex andbisonare used to parse the configuration file and build the actions tree that are executed at runtime for each SIP message. Bison is the GNU implementationcompatible with Yacc (Yet Another Compiler Compiler), but also Yacc or Byacc can be used instead of it.

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.

The config file include two main categories of statements:

  • init statements - this category includes setting the globalparameters, loading modules and setting module parameters. These statements are executed onlyone time, at startup.

  • runtime statements - this category includes the actionsexecuted many times, after KAMAILIO (OPENSER) initialization. These statements are grouped in routeblocks, there being different route types that get executed for certain events.

    • route - is executed when a SIP request is received

    • onreply_route - is executed when a SIP reply isreceived

    • error_route - is executed when some errors(mainly related to message parsing) are encountered

    • failure_route - is executed when a negative replywas received for a transaction that armed the handler for the failure event.

    • branch_route - is executed for each destination branchof 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 andbison.

8.1. Adding a core parameter

Some of the core parameters correspond to global variables in KAMAILIO (OPENSER) sources. Others induceactions to be taken during statup.

Let's follow step by step the definition and usage of the core parameterlog_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_nameis defined in main.c and initialized to0 (when set to o,it is printed the KAMAILIO (OPENSER) name (including path) in the syslog).

...
char *log_name = 0;
...
		

Next is to define the token in the flex file:cfg.lex.

...
LOGNAME		log_name
...
		

The association of a token ID and extending the grammar of the configuration file is donein 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 configurationfile an expression like:

...
log_name = "openser123"
...
		

When having a line like above one in the configuration file, the variablelog_name in C code will be initialized to the stringin the right side of the equal operator.

8.2. Adding a core function

To introduce new functions in KAMAILIO (OPENSER) core that are exported to the configuration filethe grammar have to be extended (bison andflex files), the interpreter must be enhanced to beable to run the new actions.

Behind each core function resides an action structure. This data type is defined inroute_struct.h:

...
typedef struct action_elem_ {
	int type;             /* type of the element */
	union {
		long number;      /* element is a long number */
		char* string;     /* element is a null-terminated string */
		void* data;       /* element is pointer to a custom type data */
		str s;            /* element is str value */
		pv_spec_t* item;  /* element is a pseudo-variable */
	} u;
} action_elem_t, *action_elem_p;

/* increase MAX_ACTION_ELEMS to support more module function parameters
   if you change this define, you need also to change the assignment in
   the action.c file */
#define MAX_ACTION_ELEMS	7
struct action{
	int type;                             /* type of action: forward, drop, log, send ...*/
	action_elem_t elem[MAX_ACTION_ELEMS]; /* the elements of this action: parameters, etc. */
	int line;                             /* line in cofig file where the action is used */
	struct action* next;                  /* next action in config file */
};
...
		

Each action is identified by a type. The types of actions are defined in same headerfile. For example, the strip(...) function hasthe type STRIP_T, the functions exported bymodules have the type MODULE_T.

To each action may be given a set of parameters, so called action elements. In case offunctions exported by modules, the first element is the pointer to the function, next are theparameters given to that function in configuration file.

For debugging and error detection, the action keeps the line number in configuration filewhere it is used.

Next we discuss how setflag(...) config function wasimplemented.

8.2.1. Extending the grammar

Define the token in flex file:cfg.lex.

...
SETFLAG			"setflag"
...
			

Assign a token ID and extend the bison grammar.

...
%token SETFLAG
...
cmd:	...

		| SETFLAG LPAREN NUMBER RPAREN {mk_action2($$, SETFLAG_T, NUMBER_ST, 0,
					(void *)$3, 0 ); }
		| SETFLAG error { $$=0; yyerror("missing '(' or ')'?"); }

...
			

First grammar specification says that setflag(...)can have one parameter of type number. The otherrule for grammar is to detect error cases.

8.2.2. Extending the interpreter

First step is to add a new action type in route_struct.h.

Then add a new case in the switch of action types, fileaction.c, function

...
case SETFLAG_T:
	ret = setflag( msg, a->elem[0].u.number );
	break;
...
			

The C function setflag(...) is defined and implementedin flags.{c,h}. It simply sets the bit in flagsattribute of sip_msg at the position given bythe parameter.

...
int setflag( struct sip_msg* msg, flag_t flag ) {
	msg->flags |= 1 << flag;
	return 1;
}
...
			

We are not done yet. KAMAILIO (OPENSER) does a checking of the actions tree after all configurationfile was loaded. It does sanity checks and optimization for run time. For our case, it doesa double-check that the parameter is a number and it is in the range of0...31 to fit in the bits size of an integer value. Seefunction fix_actions(...) inroute.c.

...
		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 functionprint(action(...) with a new case for your actionthat will be used to print the actions tree -- for debugging purposes. See it in fileroute_struct.c.

...
		case SETFLAG_T:
				LM_DBG("setflag(");
				break;
...
			

From now on, you can use in your configuration file the functionsetflag(_number_).

Don't forget to add documentation inOpenSER Core Cookbook.

Chapter 9. Database API

Internally, KAMAILIO (OPENSER) uses a reduced set of SQL operations to accessthe records on the storage system. This allowed to write DB driver modulesfor non-SQL storage systems, such as db_text-- a tiny DB engine using text files.

Therefore, the interface provides data types and functions that are independentof underlying DB storage. A DB driver module has to implement the functions specifiedby the interface and provide a function named db_bind_api(...)to link to the interface functions.

The DB interface is implemented in the db directory. Touse it, one has to include the file db/db.h.

9.1. DB API Structure

It is the structure that gets filled when binding to a DB driver module. It links tothe interface functions implemented in the module.

Example 9.1. Definition

...
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_func_t;
...
			

The attribute cap is a bitmask of implementedfunctions, making easy to detect the capabilities of the DB driver module. A moduleusing the DB API should check at startup that the DB driver configured to be usedhas the required capabilities. For example, msilomodule need select,delete andinsert capabilities. The flags for capabilitiesare enumerated in the next figure (located in db/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 and update on duplicate */
} db_cap_t;
...
		

9.2. DB API Functions

9.2.1. Function init(...)

Parse the DB URL and open a new connection to database.

Example 9.2. Function type

...
typedef db_con_t* (*db_init_f) (const str* _url);
...
				

Parameters:

  • _url - database URL. Its format dependson 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 db_con_t* representing the connection structure or NULLin case of error.

9.2.2. Function close(...)

The function closes previously open connection and frees all previously allocatedmemory. The function db_close must be the very last function called.

Example 9.3. Function type

...
typedef void (*db_close_f) (db_con_t* _h); 
...
				

Parameters:

  • _h - db_con_t structure representing thedatabase connection.

The function returns nothing.

9.2.3. Function use_table(...)

Specify table name that will be used for subsequent operations (insert, delete,update, query).

Example 9.4. Function type

...
typedef int (*db_use_table_f)(db_con_t* _h, const str * _t);
...
				

Parameters:

  • _h - database connection handle.

  • _t - table name.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.4. Function query(...)

Query table for specified rows. This function implements the SELECT SQL directive.

Example 9.5. Function type

...
typedef int (*db_query_f) (const db_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, db_res_t** _r);
...
				

Parameters:

  • _h - database connection handle.

  • _k - array of column names that will becompared and their values must match.

  • _op - array of operators to be used withkey-value pairs.

  • _v - array of values, columns specified in _k parameter must match these values.

  • _c - array of column names that youare interested in.

  • _n - number of key-value pairs to matchin _k and _v parameters.

  • _nc - number of columns in _c parameter.

  • _o - order by statement for query.

  • _r - address of variable where pointer tothe result will be stored.

The function 0 if everything is OK, otherwise returns value negative value.

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!

9.2.5. Function fetch_result(...)

Fetch a number of rows from a result.

Example 9.6. Function type

...
typedef int (*db_fetch_result_f) (const db_con_t* _h, db_res_t** _r, const int _n);
...
				

Parameters:

  • _h - database connection handle.

  • _r - structure for the result.

  • _n - the number of rows that should be fetched.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.6. Function raw_query(...)

This function can be used to do database specific queries. Please use this function only if needed, as this creates portability issues for the different databases. Also keep in mind that you need to escape all external data sources that you use.You could use the escape_common and unescape_common functions in the core for this task.

Example 9.7. Function type

...
typedef int (*db_raw_query_f) (const db_con_t* _h, const str* _s, db_res_t** _r);
...
				

Parameters:

  • _h - database connection handle.

  • _s - the SQL query.

  • _r - structure for the result.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.7. Function free_result(...)

Free a result allocated by db_query.

Example 9.8. Function type

...
typedef int (*db_free_result_f) (db_con_t* _h, db_res_t* _r);
...
				

Parameters:

  • _h - database connection handle.

  • _r - pointer to db_res_t structure todestroy.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.8. Function insert(...)

Insert a row into the specified table.

Example 9.9. Function type

...
typedef int (*db_insert_f) (const db_con_t* _h, const db_key_t* _k,
				const db_val_t* _v, const int _n);
...
				

Parameters:

  • _h - database connection handle.

  • _k - array of keys (column names).

  • _v - array of values for keys specifiedin _k parameter.

  • _n - number of keys-value pairs int _k and _v parameters.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.9. Function delete(...)

Delete a row from the specified table.

Example 9.10. Function type

...
typedef int (*db_delete_f) (const db_con_t* _h, const db_key_t* _k, const db_op_t* _o,
				const db_val_t* _v, const int _n);
...
				

Parameters:

  • _h - database connection handle.

  • _k - array of keys (column names) that will be matched.

  • _o - array of operators to be used with key-value pairs.

  • _v - array of values that the row mustmatch to be deleted.

  • _n - number of keys-value pairs int _k and _v parameters.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.10. Function update(...)

Update some rows in the specified table.

Example 9.11. Function type

...
typedef int (*db_update_f) (const db_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:

  • _h - database connection handle.

  • _k - array of keys (column names)that will be matched.

  • _o - array of operators to be used with key-value pairs.

  • _v - array of values that the row must match to be modified.

  • _uk - array of keys (column names) that will be modified.

  • _uv - new values for keys specified in _k parameter.

  • _n - number of key-value pairs in_v parameters.

  • _un - number of key-value pairs in_uv parameters.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.11. Function replace(...)

Insert a row and replace if one already exists.

Example 9.12. Function type

...
typedef int (*db_replace_f) (const db_con_t* handle, const db_key_t* keys,
				const db_val_t* vals, const int n);
...
				

Parameters:

  • _h - database connection handle.

  • _k - array of keys (column names).

  • _v - array of values for keys specifiedin _k parameter.

  • _n - number of keys-value pairs int _k and _v parameters.

The function 0 if everything is OK, otherwise returns value negative value.

9.2.12. Function last_inserted_id(...)

Retrieve the last inserted ID in a table.

Example 9.13. Function type

...
typedef int (*db_last_inserted_id_f) (const db_con_t* _h);
...
				

Parameters:

  • _h - structure representing databaseconnection

The function returns the ID as integer or returns 0 if the previous statementdoes not use an AUTO_INCREMENT value.

9.2.13. Function insert_update(...)

Insert a row into specified table, update on duplicate key.

Example 9.14. Function type

...
typedef int (*db_insert_update_f) (const db_con_t* _h, const db_key_t* _k,
				const db_val_t* _v, const int _n);
...
				

Parameters:

  • _h - database connection handle.

  • _k - array of keys (column names).

  • _v - array of values for keys specifiedin _k parameter.

  • _n - number of keys-value pairs int _k and _v parameters.

The function 0 if everything is OK, otherwise returns value negative value.

9.3. DB API Data Types

9.3.1. Type db_key_t

This type represents a database key (column). Every time you need to specifya key value, this type should be used.

Example 9.15. Definition

...
typedef str* db_key_t;
...
				

9.3.2. Type db_op_t

This type represents an expression operator uses for SQL queries.

Example 9.16. Definition

...
typedef const char* db_op_t;
...
				

9.3.3. Type db_type_t

Each cell in a database table can be of a different type. To distinguish among these types, the db_type_t enumeration is used. Every value of the enumeration represents one datatype that is recognized by the database API.

Example 9.17. Definition

...
typedef enum {
	DB_INT,        /* represents an 32 bit integer number      */
	DB_DOUBLE,     /* represents a floating point number       */
	DB_STRING,     /* represents a zero terminated const char* */
	DB_STR,        /* represents a string of 'str' type        */
	DB_DATETIME,   /* represents date and time                 */
	DB_BLOB,       /* represents a large binary object         */
	DB_BITMAP      /* an one-dimensional array of 32 flags     */
	} db_type_t;
...
				

9.3.4. Type db_val_t

This structure represents a value in the database. Several datatypes arerecognized and converted by the database API. These datatypes are automatically recognized, converted from internal database representation and stored in the variable of corresponding type.Modules that want to use this values needs to copy them to another memorylocation, because after the call to free_result there are not more available.If the structure holds a pointer to a string value that needs to be freed because the module allocated new memory for it then the free flag must beset to a non-zero value. A free flag of zero means that the string data mustbe freed internally by the database driver.

Example 9.18. Definition

...
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              */
		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;
...
				

9.3.5. Type db_con_t

This structure represents a database connection, pointer to this structureare used as a connection handle from modules uses the db API.

Example 9.19. Definition

...
typedef struct {
	const str* table;      /* Default table that should be used              */
	unsigned long tail;    /* Variable length tail, database module specific */
	} db_con_t;
...
				

9.3.6. Type db_row_t

Structure holding the result of a query table function. It represents onerow in a database table. In other words, the row is an array of db_val_tvariables, where each db_val_t variable represents exactly one cell in thetable.

Example 9.20. Definition

...
typedef struct db_row {
	db_val_t* values;  /* Columns in the row */
	int n;             /* Number of columns in the row */
	} db_row_t;
...
				

9.3.7. Type db_res_t

This type represents a result returned by db_query function (see below).The result can consist of zero or more rows (see db_row_t description).

Note: A variable of type db_res_treturned by db_query function uses dynamically allocated memory, don't forgetto call db_free_result if you don't need the variable anymore. You willencounter memory leaks if you fail to do this! In addition to zero or morerows, each db_res_t object contains also an array of db_key_t objects. Theobjects represent keys (names of columns).

Example 9.21. Definition

...
typedef struct db_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                        */
	} db_res_t;
...
				

9.4. Macros

The DB API offers a set of macros to make easier to access the attributes in various datastructures.

Macros for db_res_t:

  • RES_NAMES(res) - get the pointer to column names

  • RES_COL_N(res) - get the number of columns

  • RES_ROWS(res) - get the pointer to rows

  • RES_ROW_N(res) - get the number of rows

Macros for db_val_t:

  • ROW_VALUES(row) - get the pointer to values in the row

  • ROW_N(row) - get the number of values in the row

Macros for db_val_t:

  • VAL_TYPE(val) - get/set the type of a value

  • VAL_NULL(val) - get/set the NULL flag for a value

  • VAL_INT(val) - get/set the integer value

  • VAL_STRING(val) - get/set the null-terminated string value

  • VAL_STR(val) - get/set the str value

  • VAL_DOUBLE(val) - get/set the double value

  • VAL_TIME(val) - get/set the time value

  • VAL_BLOB(val) - get/set the blob value

9.5. Example of usage

A simple example of doing a select. The table is named test and has two columns.

...
create table test (
    a int,
	b varchar(64)
);
...
		

The C code:

...

#include "../../dprint.h"
#include "../../db/db.h"

db_func_t db_funcs;      /* Database API functions */
db_con_t* db_handle=0;   /* Database connection handle */

int db_example(char *db_url)
{
	str table;
	str col_a;
	str col_b;
	int nr_keys=0;
	db_key_t db_keys[1];
	db_val_t db_vals[1];
	db_key_t db_cols[1];
	db_res_t* db_res = NULL;

    /* Bind the database module */
	if (db_bind_mod(&db_url, &db_funcs))
	{
		LM_ERR("failed to bind database module\n");
		return -1;
	}
	/* Check for SELECT capability */
	if (!DB_CAPABILITY(db_funcs, DB_CAP_QUERY))
	{
		LM_ERR("Database modules does not "
			"provide all functions needed here\n");
		return -1;
	}
	/* Connect to DB */
	db_handle = db_funcs.init(&db_url);
	if (!db_handle)
	{
		LM_ERR("failed to connect database\n");
		return -1;
	}

	/* Prepare the data for the query */
	table.s = "test";
	table.len = 4;

	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++;

	/* execute the query */
	/* -- select b from test where a=1 */
	db_funcs.use_table(db_handle, &table);
	if(db_funcs.query(db_handle, db_keys, NULL, db_vals, db_cols,
		nr_keys /*no keys*/, 1 /*no cols*/, NULL, &db_res)!=0)
	{
		LM_ERR("failed to query database\n");
		db_funcs.close(db_handle);
		return -1;
	}

	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;
	}

	/* Print the first value */
	if(RES_ROWS(db_res)[0].values[0].type == DB_STRING)
		LM_DBG("first value found is [%s]\n",
			(char*)RES_ROWS(db_res)[0].values[0].val.string_val);
	else if(RES_ROWS(db_res)[0].values[0].type == DB_STR)
		LM_DBG("first value found is [%.*s]\n",
			RES_ROWS(db_res)[0].values[0].val.str_val.len,
			(char*)RES_ROWS(db_res)[0].values[0].val.str_val.s);
	else
		LM_DBG("first value found has an unexpected type [%d]\n",
			RES_ROWS(db_res)[0].values[0].type);

	/* Free the result */
	db_funcs.free_result(db_handle, db_res);
	db_funcs.close(db_handle);

	return 0;
}

...
		

Chapter 10. Management Interface

The Management Interface is an abstract layerintroduced to allow interaction between KAMAILIO (OPENSER) and external applications likeshell terminal or web applications. In the past, there were two ways to interactwith such applications: via FIFO fileand via unix sockets.

MI came and introduced an abstractization betweenthe transport and application levels. All MI commands are available for allavailable transports. At this moment the following transports are available:

  • fifo - (mi_fifo mofule) - the communication is done via FIFO file using asimple text-based, line oriented protocol.

  • xmlrpc - (mi_xmlrpc) - the communication is done via XMLRPC

  • datagram - (mi_datagram) - the communication is done via unix socket filesor UDP sockets.

To implement a new command for MI you don't needto interact with the transports. MI will pass toyour function a compiled version of the command, in the form of a tree. The functionswalks through the tree, finds the input parameters, executes the actions accordingly,and returns a new MI tree with the response tothe command.

10.1. MI Command Function

The function you have to implement for a new MIcommand has a simple prototype.

...
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 inmi/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) */
    };
    ...
    				
  • param - parameter given when registering the command toMI.

Returns a tree containing the response to be send back for that command or NULL incase of error.

10.2. Register MI Command

It is recommended to register new MI commands viaKAMAILIO (OPENSER) module interface - described later, in the chapted aboutmodule development.

The alternative is to use register_mi_cmd(...),defined in file mi/mi.h.

...
typedef struct mi_root* (mi_cmd_f)(struct mi_root*, void *param);
typedef int (mi_child_init_f)(void);

int register_mi_cmd(mi_cmd_f f, char *name, void *param,
		mi_child_init_f in, unsigned int flags);
...
		

Parameters:

  • f - function to be called when the command is received from the transport layer

  • name - the name of the command

  • param - parameter to be given when function is executed

  • in - function to be executed at KAMAILIO (OPENSER) initialization time

  • flags - set of flags describing properties of the command

    ...
    #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
    ...
    				

Returns 0 if the MI command was successfully registered, <0 in case of error.

10.3. Example of MI Command Function

We look at a MI command exported by dispatchermodule. The command allow to set the state (active/inactive) for a destinationaddress in the dispatching list (read documentation fordispatcher module).

The command expects 3 parameters:

  • state - the state to set to the destination address

  • group - the id of the group (destination set) the destination belongs to

  • address - the address of the destination to change the state for it

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 accordinglyand successful code and message is sent back, in not, an appropriate error code andmessage is sent back.

Code is shown below (in the sources look in filemodules/dispatcher/dispatcher.c).

...
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 = 1;
	if(sp.s[0]=='0' || sp.s[0]=='I' || sp.s[0]=='i')
		state = 0;
	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(state==1)
		ret = ds_set_state(group, &sp, DS_INACTIVE_DST, 0);
	else
		ret = ds_set_state(group, &sp, DS_INACTIVE_DST, 1);

	if(ret!=0)
	{
		return init_mi_tree(404, "destination not found", 21);
	}

	return init_mi_tree( 200, MI_OK_S, MI_OK_LEN);
}
...
		

10.4. MI FIFO Command

The structure of the command that has to be sent to transport layer dependson the implementation. Check the documentation of the modules implementing theMI transports.

For FIFO, the structure is line oriented, command being plain text.

...
:_command_name_:_reply_fifo_file_
_parameters_
_empty_line_
...
		

MI FIFO command structure:

  • _command_name_ - the name of the command

  • _reply_fifo_file_ - the FIFO file where to write the reply message

  • _parameters_ - values for parameters, one per line

  • _empty_line_ - an empty line to mark the end of the command

For the command described in the previous section, it can look like:

...
:ds_set_state:openser_fifo_reply
i
2
sip:10.10.10.10:5080
\n
...
		

Chapter 11. Pseudo-variables

Why this name? Yes, they are kind of variables, but a bit different:

  • some are read-only - most of them are read-only, because theyare references inside the original SIP message and that does not changeduring config file execution (see Data Lumpchapter.)

  • some are array - even if looks as simple variable name, assigning a value meansto add one more in an array - the case for $avp(name).

So, they were named pseudo-variable. Initially they were introduced inxlog module having a simple mechanism behind. There isa marker character ($ in this case) to identify the start of pseudo-variable name from therest of the text. To a pseudo-variable name was associated a function that returned a stringvalue. 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 tobe possible to use them directly in the configuration file. Also, some of them can have dynamicname and index.

The framework for pseudo-variables is now easy to extend. They can be introduced as corepseudo-variables or exported by modules (this is preferred option). We will show such casefurther in the document.

11.1. Naming Format

The naming format for a pseudo-variable is described in the next example:

...
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 '}' )+ ')'
...
		

Meaning of the tokens:

  • marker - it is the char $

  • classname - a string specifying the class of pseudo-variables. A class can have onpseudo-variable, when the innername is missing. Lot of references to parts ofSIP 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-variableclass, it is not valid for all classes.

  • index - index in the array, when the pseudo-variable can have multiple values atthe same time. Such pseudo-variables are header references and AVPs. Also the index canhave 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.

Some examples with pseudo-variable names:

...
$ru - reference to request URI
$(ru) - same as above - this format can be used when the class name cannot be delimited. It must
        be used if the PV has index or transformations.
$avp(s:test) - the AVP with the string name 'test'
$avp(s:test[2]) - the third AVP with the string name 'test'
...
		

11.2. Data structures

The prototypes and data structures for pseudo-variables are defined inpvar.h.

11.2.1. Type pv_value_t

Is the structure returned by the get functionassociated to a pseudo-variable. It includes the flags that describe the value. Itcan have integer and string value, in most of the case, the string value is all thetime set as PV used to be involved in string operations.

...
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;

...
			

The type can be a combination of the following flags:

...
#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
...
			

11.2.2. Type pv_name_t

The structure to store the specifications for innername. Can be integer or string (e.g.,for AVPs). It can be a pointer to another pseudo-variable specifier or something else,up to implementation.

...
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;

...
			

Type can be:

...
#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

...
			

Type for isname can be:

...
0                                       // the name is integer

#define AVP_NAME_STR     (1<<0)   // 1 - the name is string
...
			

11.2.3. Type pv_index_t

The structure holding index specifications.

...
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;

...
			

Type can be:

...
#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
...
			

11.2.4. Type pv_param_t

The structure groups the name and the index to be easy to give them as parameterto the functions that requires them.

...
typedef struct _pv_param
{
	pv_name_t    pvn; /* PV name */
	pv_index_t   pvi; /* PV index */
} pv_param_t, *pv_param_p;

...
		

11.2.5. Type pv_spec_t

The structure that describes the pseudo-variable - the PV spec. It includesa type of pseudo-variable, the functions to get andset the pseudo-variable value, the parameter withname and index specifiers and the list to transformations associated for that specificinstance of the pseudo-variable.

...
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*);

typedef struct _pv_spec {
	pv_type_t    type;   /* type of PV */
	pv_getf_t    getf;   /* get PV value function */
	pv_setf_t    setf;   /* set PV value function */
	pv_param_t   pvp;    /* parameter to be given to get/set functions */
	void         *trans; /* transformations */
} pv_spec_t, *pv_spec_p;

...
			

The types are defined in pvar.h and are usedto 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.

Such structure resides behind each occurence of a pseudo-variable in configuration file.

11.2.6. Type pv_export_t

The structure that one has to fill in order to add a new pseudo-variable. There is an arrayof such objects in the core, in file pvar.c, and itis possible to export such structure with the module interface.

...
typedef int (*pv_parse_name_f)(pv_spec_p sp, str *in);
typedef int (*pv_parse_index_f)(pv_spec_p sp, str *in);
typedef int (*pv_init_param_f)(pv_spec_p sp, int param);

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;

...
			

Practically, to add the pseudo-variable you have to give the classname and implement the functions to:

  • get the value of the pseudo-variable (required)

  • set the value of the pseudo-variable (optional)

  • parse the inner name (optional)

  • parse the index (optional)

  • initialize the pseudo-variable spec (optional)

The optional attributes can be left NULL. iparamis used together with function init_param.

11.3. Adding a pseudo-variables

We will show how to add a simple pseudo-variable in the core, later will show how to adda pseudo-variable with a inner name via module interface. $ru(request URI) is taken as example. This pseudo-variable is read/write, so we have to implementthe get and setfunctions. These are in file pvar.c.

...
static int pv_get_ruri(struct sip_msg *msg, pv_param_t *param,
		pv_value_t *res)
{
	if(msg==NULL || res==NULL)
		return -1;

	if(msg->first_line.type == SIP_REPLY)	/* REPLY doesnt have a ruri */
		return pv_get_null(msg, param, res);

	if(msg->parsed_uri_ok==0 /* R-URI not parsed*/ && parse_sip_msg_uri(msg)<0)
	{
		LM_ERR("failed to parse the R-URI\n");
		return pv_get_null(msg, param, res);
	}
	
	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;
	char backup;

	if(msg==NULL || param==NULL || val==NULL)
	{
		LM_ERR("bad parameters\n");
		return -1;
	}

	if(!(val->flags&PV_VAL_STR))
	{
		LM_ERR("str value required to set R-URI\n");
		goto error;
	}
	
	memset(&act, 0, sizeof(act));
	act.elem[0].type = STRING_ST;
	act.elem[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;
	if (do_action(&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;
}
...
		

The parameters to the functions are:

  • msg - the SIP message currenty processed

  • param - the param field from PV spec structure

  • op - the assign operation for set function

  • value - pointer to a pv_value_t structure. It is out parameter forget function and in parameter forset function.

The functions return 0 in case of success and<0 in case of error.

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 valueto 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 (seepvar.c) -- remember that this is required only forpseudo-variables included in core, not for the ones exported by modules.

...
static pv_export_t _pv_names_table[] = {
...
	{{"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}
};
...
		

... and do not forget to document in the Pseudo-Variable Cookbok.

Chapter 12. Transformations

The transformations are strictly related to pseudo-variables. They are properties thatcan be associated to instances of pseudo-variables. A transformation produces changes to thevalue returned by a pseudo-variable. There can be a chain of transformations assigned to sameinstance of a pseudo-variable.

An example of a transformation is to get a substring from the value returned by apseudo-variable. Another one is to get the length of the values returned by a pseudo-variable.

The value of a pseudo-variable with a chain of transformations is evaluated as:

  • get the value of the pseudo-variable

  • apply the operation specified by the first transformation to the value returned bythe pseudo-variable

  • apply the operation specified by the current transformation to the value returned bythe 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 inputsuch a value and as output another value, stored over the input value. The transformation areimplemented only in the KAMAILIO (OPENSER) core, data structures and functions are in filestransformations.{c,h}.

12.1. Naming Format

The transformations are given in between the pharantesis around the parenthesis of a pseudo-variable.The transformation name and parameters are enclosed in between curly brackets{ }. The grammar for a transformation specifier:

...
'{' classname '.' innername ( ',' parameter )* '}'
...
		

The tokens are:

  • classname - string identifying the class of transformation. So far there are:

    • 's' - string transformations

    • 'uri' - URI transformations

    • 'param' - parameter transformations

  • innername - string identifying the operation within the class of transformations

  • parameter - the parameter to the transformation. There can be transformation with noparameters, one or more parameters.

Example of existing transformations:

...
{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
...
		

12.2. Data Structures

Internally, to the classname of a transformation it is associated type, an integer number, and tothe innername a subtype, also an integer number. Constants and definitions are in filetransformations.h

...
typedef struct tr_param_ {
	int type;        /* type of the parameter value */
	union {
		int n;       /* the integer value of the parameter */
		str s;       /* the string value of the parameter */
		void *data;  /* pseudo-variable spec of the parameter */
	} v;
	struct tr_param_ *next;  /* link to next parameter */
} tr_param_t, *tr_param_p;

typedef int (*tr_func_t) (struct sip_msg *msg, tr_param_t *param, int subtype, pv_value_t *value);

typedef struct trans_ {
	str name;              /* full name of the transformation */
	int type;              /* the id of the transformation class */
	int subtype;           /* the id of the transformation inner name */
	tr_func_t trf;         /* the function to be executed when applying the transformation */
	tr_param_t *params;    /* the parameters for this transformation */
	struct trans_ *next;   /* link to next transformation to be applied to same pseudo-variable */
} trans_t, *trans_p;
...
		

The parameters of the function to be executed for a transformation:

  • msg - the SIP message currently processed

  • param - the list with the parameters of the transformation

  • subtype - the subtype of the transformation

  • 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 givenvia subtype. The structure trans_t is what stands behind eachtransformation occurrence.

12.3. Adding a Transformation

The core transformation framework assigns for each transformation class a function to parse the innernameand the parameters, plus a function to execute when the transformation need to be applied. To extendan existing transformation class, you have to extend the two functions of that transformation. You haveto assign a subtype for your new transformation.

To add a new class, you have to extend the framework to recognize the class name, and implement the twofunctions for it.

Exemplifying with transformation s.len - it returns the lengthof the string value of a pseudo-variable. The type TR_STRINGand subtype TR_S_LEN are added in filetransformations.h.

The function parse_transformation(...) in filetransformations.c is extended to recognize the classs.

...
	if(tclass.len==1 && (*tclass.s=='s' || *tclass.s=='S'))
	{
		t->type = TR_STRING;
		t->trf = tr_eval_string;
		s.s = p; s.len = in->s + in->len - p;
		p0 = tr_parse_string(&s, t);
		if(p0==NULL)
			goto error;
		p = p0;
	}
...
		

It calls function tr_parse_string(...) to parse the rest ofthe string transformation. In this function, if the inner nameis 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 functiontr_eval_str(...).

...
	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 representationof its initial value.

Chapter 13. Statistics

These are integer numbers that collect information about KAMAILIO (OPENSER) internals. They are providing real-timefeedback about health and load of an KAMAILIO (OPENSER) instance. In fact, they are represented by an integer variable ora function that returns an integer.

The statistics engine is implemented in the filesstatistics.{c,h}. If you want to extend it, you have to read andunderstand the code in those file. The purpose of this chapter is to teach how to add new statisticvariables.

You have to include the header file statistics.h and declare thestatistic variable. We exemplify with the statistic stored_messagesfrom module msilo. In the filemodules/msilo/msilo.c.

...		
#include "../../statistics.h"

stat_var* ms_stored_msgs;
...
	

Next is to register the statistic to the engine, which is done there via module interface (to be explainedlater, in a dedicated chapter).

...
stat_export_t msilo_stats[] = {
	{"stored_messages" ,  0,  &ms_stored_msgs  },
...
/** module exports */
struct module_exports exports= {
	"msilo",    /* module id */
...
	msilo_stats,
...	
};
...
	

Alternative is to use the function register_stat(...) defined instatistics.{c,h}.

...
int register_stat( char *module, char *name, stat_var **pvar, int flags);
...
	

The parameters are:

  • module - name of the module exporting the statistic

  • name - name of the statistic

  • var - where the statistic value will be stored

  • flags - flags describing the statistic

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);
...
	

13.1. Statistic Macros

There are three macros that help to deal with statistic values easily.

  • 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

  • get_stat_val (stat) - return the value of the statistic

Chapter 14. Data Lumps

A topic that won't be involved very much in the development of new features, but importantto understand as it is one of the most discussed issues related to KAMAILIO (OPENSER).

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 appliedimmediately. They are actually added in a list of operations to be done. Practically, changingthe 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 diffsare applied after the configuration file is executed, before sending the SIP message furtherto the network.

There are two types of diff operations:

  • add content - this diff command is specified by the position in the original SIPmessage and the value to be added there. The value can be a static string, or aspecial marker to be interpreted later, when required information is available. Forthe later, it is the case of Record-Route headers where it is needed to set theIP address of the socket that is used to send the message further. That addressis detected just before sending through the socket.

  • remove content - this diff command is specified by the position in the original SIPmessage and the length to be deleted.

There are two classes of data lumps:

  • message lumps - these lumps are associated to the SIP message currently processed. Thechanges 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 requestand you want to add content to the reply to be sent for that request. Here cannot belumps that delete content as the SIP reply is to be constructed from the SIPrequest.

Maybe future releases will touch deeper this subject, depending on the interest. If you want toinvestigate by yourself, start with files data_lump.{c,h} anddata_lump_rpl.{c,h}.

Chapter 15. Timer

KAMAILIO (OPENSER) provides an internal timer that ticks every 100 milliseconds. The step can bechanged at compile time. It offers developer API to:

  • register functions to be run every 1 second or interval of multiple seconds

  • register functions to run every 100ms or interval of multiple 100ms

  • start a new timer process

The timer API is implemented in the files timer.{c,h}. Ifyou want to extend the timer API you have to start with those files. We focus on how to addnew functions to be run by timer. The timer is available after shared memory initialization.

15.1. Data Types

The functions that can be given as callbacks for timer have the following prototypes:

...
typedef unsigned long long utime_t;

typedef void (timer_function)(unsigned int ticks, void* param);
typedef void (utimer_function)(utime_t uticks, void* param);
...
		

Parameters are:

  • ticks - number of second ticks elapsed at the moment of running the function

  • uticks - number of 100mili-second ticks elapsed at the moment of running the function

  • param - parameter given when registering the callback function

15.2. Timer API Functions

Register a function to be executed by second-based timer:

...
int register_timer(timer_function f, void* param, unsigned int interval);
...
		

Parameters:

  • f - callback function

  • param - parameter to callback function

  • interval - interval to execute the callback function

Register a function to be executed by millisecond-based timer:

...
int register_utimer(utimer_function f, void* param, unsigned int interval);
...
		

Parameters:

  • f - callback function

  • param - parameter to callback function

  • interval - interval to execute the callback function

Register a function to start a new timer process:

...
int register_timer_process(timer_function f,void* param, unsigned int interval,
		unsigned int flags);
...
		

Parameters:

  • f - callback function

  • param - parameter to callback function

  • interval - interval to execute the callback function

  • flags - flags describing the timer process

There are two functions that return the number of ticks elapsed since KAMAILIO (OPENSER) started,one for second-based timer and the other for millisecond-based timer.

...
unsigned int get_ticks(void);

utime_t get_uticks(void);
...
		

15.3. Example of usage

Next we will show how the module msilo register a timerfunction to clean the stored messages. Seemodules/msilo/msilo.c.

...
#include "../../timer.h"
...
void m_clean_silo(unsigned int ticks, void *);
...
		

Registration to the second-based timer is done in functionmod_init().

...
register_timer(m_clean_silo, 0, ms_check_time);
...
		

Function implementation is:

...

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;
	
	LM_DBG("cleaning stored messages - %d\n", ticks);
	
	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 = DB_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);
	
	/* cleaning expired messages */
	if(ticks%(ms_check_time*ms_clean_period)<ms_check_time)
	{
		LM_DBG("cleaning expired messages\n");
		db_keys[0] = &sc_exp_time;
		db_vals[0].type = DB_INT;
		db_vals[0].nul = 0;
		db_vals[0].val.int_val = (int)time(NULL);
		if (msilo_dbf.delete(db_con, db_keys, db_ops, db_vals, 1) < 0) 
			LM_DBG("ERROR cleaning expired messages\n");
	}
}

...
		

The function deletes from database the messages that were succesfully delivered and themessages that were stored for too long time in database and the recipient was not onlineor not able to receive them.

Chapter 16. Module Development

The easiest way to write extensions for KAMAILIO (OPENSER) is to extend existing modules or create new ones. Most of the featuresavailable in the configuration file are exported via module functions.

KAMAILIO (OPENSER) modules in a pretty simple concept, are objects that export a set of parameters to control theinternals 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 toload a module.

...
loadmodule "/path/to/module.so"
...
	

Each module has to export a structure struct module_exports with the nameexports.

16.1. module_exports type

The main structure that has to be exported by a module. It is defined in filesr_module.h.

Example 16.1. module_exports definition

...
struct module_exports{
	char* name;                     /* null terminated module name */
	unsigned int dlflags;           /* flags for dlopen */
	
	cmd_export_t* cmds;             /* null terminated array of the exported
	                                   commands */
	param_export_t* params;         /* null terminated array of the exported
	                                   module parameters */

	stat_export_t* stats;           /* null terminated array of the exported
	                                   module statistics */

	mi_export_t* mi_cmds;           /* null terminated array of the exported
	                                   MI functions */

	pv_export_t* items;             /* null terminated array of the exported
	                                   module items (pseudo-variables) */

	proc_export_t* procs;           /* null terminated array of the additional
	                                   processes reqired by the module */

	init_function init_f;           /* Initialization function */
	response_function response_f;   /* function used for responses,
	                                   returns yes or no; can be null */
	destroy_function destroy_f;     /* function called when the module should
	                                   be "destroyed", e.g: on openser exit */
	child_init_function init_child_f;/* function called by all processes
	                                    after the fork */
};
...
			

The comments in the definition are explanatory, each internal structure and data type is detailed in the nextsections.

16.2. cmd_export_t type

The structure corresponds to a function exported by modules

...
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;
...
		

Flags can be a bit mask of:

  • REQUEST_ROUTE - the function can be used in request route blocks

  • FAILURE_ROUTE - the function can be used in failure_route blocks

  • ONREPLY_ROUTE - the function can be used in onreply_route blocks

  • BRANCH_ROUTE - the function can be used in branch_route blocks

  • ERROR_ROUTE - the function can be used in error_route block

16.3. param_export_t type

The structure specifies a parameter exported by a module.

...
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;
...
		

The type can be:

  • STR_PARAM - parameter takes a string value

  • INT_PARAM - parameter takes an integer value

  • USE_FUNC_PARAM - this must be used in combination with one from the above. Means that internally there isa function called every time the parameter is set, instead of setting the value to a variable.

When using USE_FUNC_PARAM flag, the param_pointermust 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

  • val - pointer to the value set in the configuration file

The function has to return 0 in case of success.

16.4. proc_export_t type

It is the type that specify the attributes for new processes to be started by KAMAILIO (OPENSER). The module can askthe core to start a new process that runs the function set in the attribute. The developer can specify functionsto be run before forking or just after forking.function.

...
typedef void (*mod_proc)(int no);
typedef int (*mod_proc_wrapper)();
...
struct proc_export_ {
	char *name;                              /* name of the process */
	mod_proc_wrapper pre_fork_function;      /* function to be called before forking new process */
	mod_proc_wrapper post_fork_function;     /* function to be called after forking new process */
	mod_proc function;                       /* function to be called in the new process */
	unsigned int no;                         /* number of processes to start */
};
typedef struct proc_export_ proc_export_t;
...
		

Typical module implementations for such functionality are the MI transports module. They require a special processto listen to the MI transport layer.

16.5. stat_export_t type

The structure allow to export statistics variables from your module.

...
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;
...
		

For more see the chapter Statistics.

16.6. mi_export_t type

The structure to export MI commands. For more see the chapter Management Interface.

...
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.7. pv_export_t

The structure to export pseudo-variables from module. See the chapterPseudo 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;

...
		

16.8. Functions Types

These are the types of functions used in the structure module_exports.

...
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-compileprocessing

  • response_function - is the type for the functions to be register to automatically be called when a SIPreply is received by KAMAILIO (OPENSER)

  • destroy_function - is the type for the function to be executed at shut down time, to clean up the resourcesused 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 afterforking

16.9. Command Functions

These are the functions implemented by the module that can be invoked from the configuration file. It has a strictprototype. First parameter is the pointer to structure with the current processed SIP message. Next arechar*. Most existing command functions have up to two char* parameters, recent extensions allow up to six such parameters.

It means that in the configuration file you can give only char* values asparameter. KAMAILIO (OPENSER) provides the mechanisms to convert the values to something more meaningful for the module,during KAMAILIO (OPENSER) start up, via fixup functions.

When calling from the configuration file, the structure sip_msg is not givenas parameter, it is added by the config interpreter when calling the C function. The name of the function availablein the configuration file may be different that the name of the C function. There can be different C functions behindthe same config file function, when the number of the parameters is different.

16.9.1. Return Values

Returning values from the command functions have a special meaning in the configuration file. The command functionsreturn an integer value, and the config file interpreter use it as follows:

  • if <0 - evaluation of the return code is FALSE

  • if 0 - the interpreter stop executing the configuration file

  • if >0 - evaluation of the return code is TRUE

16.9.2. Fixup Functions

As the parameters of the functions exported by modules to configuration file are strings, converting the parametersto internal structures every time a module function is executed is not optimal. Most of the functions do notneed the parameters as a string, but as integer, pseudo-variable names or pseudo-variables values, or even morecomplex structures.

Here are the fixup functions. These functions convert from plain null-terminated strings to what the developerneeds. Such a function gets a pointer to the initial value and the index of the parameter for that function. Insideit 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.

...

int fixup_uint(void** param)
{
	unsigned int ui;
	str s;

	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 configfunction that get one char* parameter that need to be interpretedas unsigned integer.

The files mod_fix.{c,h} implement a set of common fixup functions youcan. 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 anduse exported functions dynamically at run time from linked applications.

16.10. Developing a new module

Here we show the steps to develop new modules, exemplifying mainly with modulecfgutils. It is a good learning example as it exports many structures via the module interface: parameters, configuration file commands (functions), pseudo-variables, MI commands.

In the chapter Statistics exemplifies how to export statistics with themodule msilo.

Prior starting writing code, check with developers whether that functionality is already implemented. Also try toidentify whether the extension fits better in an existing module or needs to be created a new one.

16.10.1. Naming the module

The first decision to be taken. It must be suggestive for the functionality. There are some rules to befollowed when implementing certain modules.

  • 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 withpresence_

  • when writing a PUA extension, the name should start with pua_

The rules are enforced for coherence in grouping related functionalities.

Create the directory for your new module.

...
mkdir maodules/my_new_module
...
			

16.10.2. Makefile

You need to create a Makefile to build the module. Fortunately that is very simple and pretty standard forall modules. you can use the following template:

...
include ../../Makefile.defs
auto_gen=
NAME=my_new_module.so
LIBS=

include ../../Makefile.modules
...
			

If the module needs to link to special libraries, you can use LIBS andDEFS to set the libraries and the path to required header files to be included.

...
LIBS = -L/path/to/lib/directory -lmylib
DEFS += -I/path/to/include/directory
...
			

16.10.3. Main File

The main file of the modules is where you place the structure module_exports.The common naming formats are:

  • my_new_module.c

  • my_new_module_mod.c

In this file you must include the macro MODULE_VERSION to allow KAMAILIO (OPENSER)to detect whether core and the module are same version and compiled with same flags. You just simply add next lineafter all header files includes.

...
MODULE_VERSION
...
			

16.10.4. Add Module Parameter

In the configuration file, can be set integer or string values for a module parameter. The type is specifiedin the param_export_t structure.

To learn the meaning of the parameters in the module cfgutils read themodule documentation. We exemplify the parameters exported by the module cfgutilsand include one integer parameter, one string parameter and another string parameter that is set via a function.Beware that the shared memory is not available when parameters are set, so you have to do operations with pkgmemory only.

For the parameters stored directly in a variable, you have to declare C variables of typeint or char*.

...
static int initial = 10;

static char* hash_file = NULL;

static param_export_t params[]={ 
	{"initial_probability", INT_PARAM, &initial},
	{"hash_file",           STR_PARAM, &hash_file        },
	{"shvset",              STR_PARAM|USE_FUNC_PARAM, (void*)param_set_shvar },
	{0,0,0}
};

...
			

In the config, one can set many times the value for a parameter. In case of parameters stored in a variable, thelast 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 configfile variable $shv(name). The function is implemented in the filemodules/cfgutils/shvar.c. Check the readme of the module to see the formatof the parameter value - it includes the name of the shared variable as well as the value. So the functionparses the value given from the configuration file, splits in name and value and store in a local structure.

...

int param_set_shvar( modparam_t type, void* val)
{
	str s;
	char *p;
	int_str isv;
	int flags;
	int ival;
	script_var_t *sv;

	if(shvar_initialized!=0)
		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;
	}
	sv = add_local_shvar(&s);
	if(sv==NULL)
		goto error;
	if(set_var_value(sv, &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 produces a set of operations behind.

...
modparam("cfgutils", "shvset", "debug=i:1")
...
			

By setting the parameter as above results in a variable $shv(debug) initializedto 1.

16.10.5. Module Init Function

The function is executed after setting the module parameters, config file is parsed completely, shared memory andlocking system are initialized.

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.

Beware that if you open an database connection in the module init function you must close it before returning fromthe function. Otherwise, it will be inherited by the children processes and will be shared for DB operations,resulting in desyncronization.

...

static int mod_init(void)
{
	if (!hash_file)
		hash_file = cfg_file;

	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);

	if (initial > 100) {
		LM_ERR("invalid probability <%d>\n", initial);
		return -1;
	}
	LM_DBG("initial probability %d percent\n", initial);

	probability=(int *) shm_malloc(sizeof(int));

	if (!probability) {
		LM_ERR("no shmem available\n");
		return -1;
	}
	*probability = initial;

	if(init_shvars()<0)
	{
		LM_ERR("init shvars failed\n");
		shm_free(probability);
		return -1;
	}
	LM_INFO("module initialized, pid [%d]\n", getpid());

	return 0;
}

...
			

First in the function is the handling of the config hashing. If the appropriate parameter is not set, it isinitialized to the config file used by KAMAILIO (OPENSER). The is computed the hash value that will be used forcomparison, later at runtime. Next is checking the probability parameter and create the variable in share memoryto store it. At the end it initializes the environment for shared variables.

The module init function must return 0 in case of success.

16.10.6. Module Child Init Function

This is the function called just after KAMAILIO (OPENSER) forks its worker processes. If KAMAILIO (OPENSER) is set in non-forkmodule, the function is called for the main process after calling the module init function.

In this function must be added the operations that has to be taken for each worker or special processes onlyonce during the runtime, at the start up time. Example of such operations are to open the connection todatabase, 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 workerprocess and negative for special processes like timer processes or TCP attendant. The defines with thesespecial ranks are in file sr_module.h.

As an example, we show the child_init function of the modulespeeddial. The operations there are for opening the connection to database.

...
static int child_init(int rank)
{
	db_handle = db_funcs.init(&db_url);
	if (!db_handle)
	{
		LM_ERR("failed to connect database\n");
		return -1;
	}
	return 0;

}
...
			

The child init function must return 0 in case of success.

16.10.7. Module Destroy Function

It is the function to be called when KAMAILIO (OPENSER) is stopped. The main purpose is to clean up the resources createdand used at initialization and/or runtime. For the module cfgutils means tofree the variable allocated in shared memory for keeping the probability and destroying the shared variablesenvironment.

...
static void mod_destroy(void)
{
	if (probability)
		shm_free(probability);
	shvar_destroy_locks();
	destroy_shvars();
}
...
			

16.10.8. Add Command Function

The module cfgutils exports a functions stop and wait for a period oftime the execution of the configuration file. It is an interface the the standard C functionsleep(...). Takes as parameter the number of the seconds to wait.

As the parameter is given as string, but it is actually an integer value, a fixup function is used. It isin the list of the fixup functions exported by mod_fix.h. The fixupfunction fixup_uint_null(...) is shown few sections above.

...
static cmd_export_t cmds[]={
...
	{"sleep",    (cmd_function)m_sleep,    1,      fixup_uint_null, 0, 
		REQUEST_ROUTE|FAILURE_ROUTE|ONREPLY_ROUTE|BRANCH_ROUTE},
...
	{0, 0, 0, 0, 0, 0}
};
...

static int m_sleep(struct sip_msg *msg, char *time, char *str2)
{
	LM_DBG("sleep %lu seconds\n", (unsigned long)time);
	sleep((unsigned int)(unsigned long)time);
	return 1;
}

...
			

In the C function, as shown in the above example, the fitst parameter is cased to integer, because thefixup function replaced the original string value with the integer representation.

This function return all the time 1 (TRUE in the configuration file). Thefunction in C (m_sleep(...)) has a different name than the one that can beused in the KAMAILIO (OPENSER) configuration file (sleep(...)). Next shows how thisfunction can be called in the configuration file to introduce a pause of 3 seconds.

...
sleep("3");
...
			

16.10.9. Add Pseudo-Variable

The chapter dedicated to Pseudo Variables presents the structurepv_export_t. When a module exports pseudo-variables, a null terminatedarray of pv_export_t is included in the structuremodule_exports.

The module cfgutils exports three classes of pseudo-variables: $RANDOM(random value), $shv(name) (shared variables) and $time(name) (broken-down time attributes). We will exemplifywith $time(name), showing how a class of pseudo-variables can have inner name. For that it exports the nameparsing function pv_parse_time_name(...).

The name can be:

  • sec - to return number of seconds

  • min - to return number of minutes

  • hour - to return the hour

  • mday - to return the day of month

  • mon - to return the month

  • year - to return the year

  • wday - to return the day of week

  • yday - to return the day of year

  • isdst - return daylight saving mode

To increase the execution speed and not compare strings all the time, the name is kept internallyas integer. At runtime, depending on the value, the appropriate attribute is returned.

...

static pv_export_t mod_items[] = {
...
	{ {"time", (sizeof("time")-1)}, 1002, pv_get_time,
		0, pv_parse_time_name, 0, 0, 0},
...
	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
};
...
int pv_parse_time_name(pv_spec_p sp, str *in)
{
	if(sp==NULL || in==NULL || in->len<=0)
		return -1;

	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;

int pv_get_time(struct sip_msg *msg, pv_param_t *param,
		pv_value_t *res)
{
	time_t t;

	if(msg==NULL || param==NULL)
		return -1;

	if(_cfgutils_msg_id != msg->id)
	{
		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.

16.10.10. Add MI Command

MI commands exported by a module are in a null-terminated array of structuresmi_export_t, include in module_exports.

One of the MI commands exported by modules cfgutils is theshv_set. It can be used to set the value of a shared variable via MI. Thecommand takes three parameters: name of the shared variable, the type of the value and the value. The Cwrapper of the command is the function mi_shvar_set(...).

...

static mi_export_t mi_cmds[] = {
...
	{ "shv_set" ,      mi_shvar_set,  0,                 0,  0 },
...
	{ 0, 0, 0, 0, 0}
};

struct mi_root* mi_shvar_set(struct mi_root* cmd_tree, void* param)
{
	str sp;
	str name;
	int ival;
	int_str isv;
	int flags;
	struct mi_node* node;
	sh_var_t *shv = NULL;

	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 command returns the code 200 in case of success.

16.10.11. Add Extra Process

It happens to need worker processed that do a different job than handling the SIP traffic. It is the case forthe MI transport modules or the xmpp gateway. These processes listen on an input stream different than the SIPports.

The modules cfgutils does not need an extra process. The next example isfrom 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 configurationoption, the module will act as a XMPP component or server.

...
static proc_export_t procs[] = {
	{"XMPP receiver",  0,  0, xmpp_process, 1 },
	{0,0,0,0,0}
};
...
static void xmpp_process(int rank)
{
	close(pipe_fds[1]);

	LM_DBG("started child connection process\n");
	if (!strcmp(backend, "component"))
		xmpp_component_child_process(pipe_fds[0]);
	else if (!strcmp(backend, "server"))
		xmpp_server_child_process(pipe_fds[0]);
}
...
			

The function gets as parameter the rank of the process.

16.10.12. CFGUTILS module_exports

The structure module_exports of the modulecfgutils includes some of the structures detailed in the examplesseen in the previous sections.

...
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 */
};
...
			

Chapter 17. Licensing

KAMAILIO (OPENSER) is licensed under GPL. New development to be introduced in the public repository should have GPL license.The copyright for major developments is developer's choice, either the developer, the company is working for orsomeone else.

If the code includes parts having other license make sure it is compatible with the GPL and the way it is used nowdoes not violate the original license and the copyright.

Each C source file includes a header as follows:

...
/*
 * $Id$
 *
 * Copyright (C) YEARS OWNER
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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
 */
...
	

YEARS and OWNER to be replaced appropriately.

Chapter 18. References

Chapter 19. Contact Details

The authors can be contacted via coordinates provided atwww.asipto.com.

The tutorial is going to be included in the KAMAILIO (OPENSER) repository, ifyou want to post messages with improvements or mistakes you can write emails to <devel [at] lists [dot] kamailio [dot] org>or use the documentation tracker fromsourceforge.net.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值