Kamailio: shared memory hashtable

Using shared memory in Kamailio is fundamental and sometimes inevitable. Luckily, Kamailio’s core abstract us from A LOT of the complexity behind this kind of memory management and help us build a ready to use in-memory hash table in no time!

First of all, you need to include the str_hash header file but it is probably implicitly included by some other mandatory header files.


#include <kamailio/str_hash.h>

For this example, I’ll store the hashtable’s (HT from now on) address inside a global variable visible from the entire module. Being that the case, I need to create it as follows:


struct str_hash_table *my_ht;

Later, I have to allocate some memory for the variable and there’s no better place for that than the module’s mod_init() function. Having allocated memory, I need to perform another allocation. This time is for the internal str_hash_table structure that contains the metadata for maintaining our information accessible. Usually, the function in charge of this task is str_hash_alloc() but looking its internals, we will find that it uses pkg_malloc() which certainly won’t work for us. Due this situation, I created an equivalent function for shared memory, mostly because Kamailio’s core doesn’t provide this functionality, or at least, I wasn’t able to find it.

static int shm_str_hash_alloc(struct str_hash_table *ht, int size)
{
ht->table = shm_malloc(sizeof(struct str_hash_head) * size);

if (!ht->table)
return -1;

ht->size = size;
return 0;
}
[/c]

Then, we make the actual allocation:
[c]
static int mod_init(void)
{
my_ht = shm_malloc(sizeof(struct str_hash_table));
if (!my_ht)
{
LM_ERR("No enough memory");
return -1;
}

if (shm_str_hash_alloc(my_ht, 69) != 0)
{
LM_ERR("Error allocating shared memory hash tablen");
return -1;
}

}
return 0;
}

Then, I need to define the structure that will be contained into the shared memory.

typedef struct call
{

unsigned int start_timestamp;
unsigned int end_timestamp;

} call_t;

Storing and retrieving

In this example, I will store the call ID as the key of the HT’s entry and in the useful part, the call’s start and end timestamp.

First, I created a function that encapsulates all the logic for allocating a new call into shared memory. Please notice that having a HT storing your data, it doesn’t implicitly allocate your information in shared memory address space, this is, you have to allocate your structure using shm_malloc() before adding it to the HT.

static call_t *alloc_new_call()
{
call_t *call = shm_malloc(sizeof(call_t));
call->start_timestamp = 0;
call->end_timestamp = 0;
return call;
}

Every entry in the HT is represented by str_hash_entry which is a linked-list ready structure that contains the key and the data part as an union. Take in consideration that an union is NOT an structure and its fields retain only one value at a time. Whatever modification you make to any field it will immediately affect to the others.

static int add_new_call(str *callid, call_t *call)
{
struct str_hash_entry *e = str_hash_get(my_ht, callid->s, callid->len);

/* check if the call id already exists */
if (e != NULL)
{
LM_ALERT("Call already exists: key %.*sn", e->key.len, e->key.s);
return -1;
}

/* we allocate the new entry into shared memory. */
e = shm_malloc(sizeof(struct str_hash_entry));

if (e == NULL)
{
LM_ERR("No shared memory leftn");
return -1;
}

/* duplicate the key, always in shared memory */
if (shm_str_dup(&e->key, client_id) != 0)
{
LM_ERR("No shared memory leftn");
return -1;
}

/* we reference the call using one of the members of the union */
/* a lock_get() may be needed */
e->u.p = call;
/* a lock_release() may be needed */

}

Finally we need a function callable from the script. In this case start_monitoring does this for us and it must be registered as an exported command.

static cmd_export_t cmds[]= {
{"start_monitoring", (cmd_function) start_monitoring, 0, 0, 0, ANY_ROUTE},
{"stop_monitoring", (cmd_function) stop_monitoring, 0, 0, 0, ANY_ROUTE},
{0,0,0,0,0,0}
};

And:

static int start_monitoring(struct sip_msg* msg, char* p1, char* p2)
{

call_t *call = NULL;

if (parse_headers(msg, HDR_CALLID_F, 0) != 0)
{
LM_ERR("Error parsing call-id");
return -1;
}

call = allow_new_call();
if (call == NULL)
return -1;

call->start_timestamp = (unsigned int) time(0);

if (add_new_call(&msg->callid->body, call) != 0)
return -1;

LM_DBG("Call added!");
return 1;

}

To finish this little tutorial, I present the stop_monitoring which tries to retrieve the entry using the callid of the call. In case it was found, it writes the current timestamp to the field end_timestamp.

static int stop_monitoring(struct sip_msg* msg, char* p1, char* p2)
{

call_t *call = NULL;
struct str_hash_entry *e = NULL;

if (parse_headers(msg, HDR_CALLID_F, 0) != 0)
{
LM_ERR("Error parsing call-id");
return -1;
}

e = str_hash_get(my_ht, msg->callid->body.s, msg->callid->body.len);
if (e == NULL)
{
LM_ERR("Call not found");
return -1;
}

((call_t *) e->u.p)->end_timestamp = time(0);

return 1;

}

PS: I am aware that I never deallocated the memory I used. I may write the dealloc function for the next entry 😀

Reading pvars with Kamailio

It’s really hard to find documentation in some opensource projects, specially when you want to achieve something that’s not very common. I’m speaking about Kamailio project which is actually very well documented but its core development manual is not profound in topics that we may need to develop a fully functional module entirely in C. This post pretends to be the first of many short tutorials to access to certain parts of the core dev API on which I wasn’t able to find any documentation or when it was present, it was not sufficient.

Reading system pvars.

This time is about reading pseudo variables like $rU, $si or $fU. I won’t explain what pvars are because I’m assuming the reader has some basic knowledge but if you want to have  a better understanding, the development guide has a section dedicated to it but not with the approach I’m using here.

Ok, the code:


/**
* Get str value using its name
*
* msg: parsed SIP message
* pv_name: the name of the pseudo variable
* pv_Value: pointer to str in which the value will be stored
*
*/

static int get_str_pv(struct sip_msg* msg, str *pv_name, str *pv_value)

{
pv_spec_t spec;
pv_value_t value;
pv_parse_spec(&pv_name, &spec);

if (pv_get_spec_value(msg, &spec, &value) != 0)
{
LM_ERR("Can't get spec for [%.*s]n", pv_name->len, pv_name->s);
return -1;
}

if (pkg_str_dup(pv_value, &value.rs) != 0)
{
LM_ERR("No more pkg memory availablen");
return -1;
}

return 0;
}

And finally, the way we should call it:


str source_ip_pv = str_init("$si");
str value;

if (get_str_pv(msg, &source_ip_pv, &value) != 0)
return -1;

If you really want to learn the right way of doing things with Kamailio, the tool you should master is grep. Grepping code was much more useful than reading mailing lists or consulting the devel guide. Consider it an advice.