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.

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:

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.

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

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.

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.

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.

And:

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.

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:

And finally, the way we should call it:

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.

Dialer: tracking calls with Asterisk/AMI

Long story short: I was asked to write a real time dialer callable from a web page that takes the number from the parameters received, make the call, and play a pre-recorded message when the called picks up. This post is not about the dialer itself, it’s about tracking the call originated by it, detect the “answer” event, detect the “hangup” event and save a CDR.

The approach I chose involves AMI to generate and track the calls. It offers a really nice interface based on a text protocol and the most important thing, the API can be mapped in almost any programming language like C# (Asterisk.NET).

Some of the protocol internals

  1. The connection is made via TCP/IP sockets using user/password authentication.
  2. Once logged in, you receive every event on the system, even if that event doesn’t belong to you. This could be a real mess because it introduces a lot of noise into the dialer and the software must support some kind of filtering mechanism.
  3. Every event comes with certain data, including a field named Uniqueid which relates the events to a transaction.
  4. The order the events were received not necessarily match the order they were generated.

The native way to generate a call is through the¬†OriginateAction¬†but the problem is that OriginateAction doesn’t reply with the uniqueID of the transaction, it just tells you¬†whether¬†the call was successfully queued or not. Then, you start receiving the events with the uniqueIDs but you have no way to know if that call belongs to you, to another dialer instance, to a human making a call, etc.

Searching the web I found what I thought was the solution: use asynchronous origination.¬†¬†The usual OriginateAction is a blocking call that returns only if success or failure. With async origination the answer to the request is delivered through the OriginateResponse event and with the parameters you receive, the so wanted uniqueID. I built the dialer on top of this “fix” just to later discover that if the call fails, the uniqueID is empty. Once again, you have no way to know which call failed and for what reason.

I was so pissed off because something as basic as this wasn’t correctly available, or at least, there is no documentation to expose an standard solution. I started considering parallel approaches to¬†abandon¬†AMI but reading the input parameters for OriginateAction¬†a dirty workaround came to my mind.

OriginateAction takes as input a field named CallerID, this field is always available and always filled with information. I used this field to uniquely identify the call whether it succeeded or not. In my particular case, the calls goes to a GSM network so overwriting the callerID has no effect and the call follows its normal destination.

Bellow is part of the dialer class. If you can read C#, it would explain itself.

[csharp]

public class Dialer
{
ManagerConnection _managerConnection;
Timer _timer;
public delegate void CallFinalizedCallback(Dialer sender);
string _uniqueID = string.Empty;
bool _wasOriginated = false;
object _locker = new object();

List<HangupEvent> _hangupList = new List<HangupEvent>();

CallFinalizedCallback _callFinalized;
public CallFinalizedCallback CallFinalized
{
get {
return this._callFinalized;
}
set {
_callFinalized = value;
}
}

CallInfo _callInfo;
public CallInfo CallInfo
{
get
{
return this._callInfo;
}
}

public Dialer()
{
_managerConnection = new ManagerConnection(Configuration.ManagerHost,
Configuration.ManagerPort,
Configuration.ManagerUserName,
Configuration.ManagerPassword);

//_timer = new Timer(Handle_timeoutCallback, Configuration.CallNotificationTimeout);

AddEventHandlers();
}

void Handle_managerConnectionOriginateResponse (object sender, OriginateResponseEvent e)

{
if (!e.CallerIdNum.Equals(_callInfo.CallId.ToString()))
return;

Logger.Print(string.Format(“Call to {0}-{1} was originated”, _callInfo.CallId, e.Exten));

bool succeeded = e.Response.ToLower().Equals(“success”);

if (succeeded)
UpdateDialoutQueue(CallStatus.Established);
else
UpdateDialoutQueue(CallStatus.FinishedWithError);

lock(_locker)
{
_wasOriginated = true;
_uniqueID = _callInfo.CallId.ToString();
}

foreach(HangupEvent hEvent in _hangupList)
{
if (hEvent.CallerIdNum.Equals(e.CallerIdNum)) // the call failed for some reason
{
Handle_managerConnectionHangup(this, hEvent);
break;
}
}
_hangupList.Clear();

}

public Dialer(CallInfo callInfo, CallFinalizedCallback callback) : this()
{
_callInfo = callInfo;

if (callback == null)
throw new ArgumentException(“CallFinalizedCallback cannot be null”);

_callFinalized = callback;
}

void AddEventHandlers()
{
_managerConnection.Hangup += Handle_managerConnectionHangup;
_managerConnection.OriginateResponse += Handle_managerConnectionOriginateResponse;
}

public void DisconnectFromAMI()
{
_managerConnection.Hangup -= Handle_managerConnectionHangup;
_managerConnection.OriginateResponse -= Handle_managerConnectionOriginateResponse;
}

void Handle_timeoutCallback()
{
try
{
Logger.Print(string.Format(“Call {0}-{1} forced to finalize after timeout”, _callInfo.CallId, _callInfo.Extension));

UpdateDialoutQueue(CallStatus.Timedout);
//_timer.CancelAlarm();

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_timeoutCallback: {0}”, ex.Message));
DisconnectFromAMI();
}
}

void Handle_managerConnectionHangup(object sender, HangupEvent e)
{
try
{
lock(_locker)
if (!_wasOriginated || !_uniqueID.Equals(e.CallerIdNum))
{
_hangupList.Add(e); // register this event in case it was fired before the OriginateAction event
return;
}

UpdateDialoutQueue(CallStatus.Finished, e.Cause, e.CauseTxt);
//_timer.CancelAlarm();

Logger.Print(string.Format(“Call {0}-{1} was hung up”, _callInfo.CallId, e.Channel));

if (_callFinalized != null)
_callFinalized(this);

DisconnectFromAMI();
}
catch(Exception ex)
{
Logger.Error(string.Format(“Handle_managerConnectionHangup: {0}”, ex.Message));
DisconnectFromAMI();
}
}

public void Call()
{
Call(_callInfo);
}

public void Call(CallInfo callInfo)
{
try
{
OriginateAction originate = new OriginateAction();

originate.Channel = callInfo.Channel;
originate.Priority = callInfo.Priority;
originate.Context = callInfo.Context;
originate.Exten = callInfo.Extension;
originate.CallerId = callInfo.CallId.ToString(); // <——–
originate.Timeout = 500000;
originate.Async = true;

_managerConnection.Login();

MarkCallAsProcessed();

//_timer.SetAlarm();

UpdateDialoutQueue(CallStatus.Trying);
ManagerResponse r = _managerConnection.SendAction(originate, originate.Timeout);
}
catch(Exception ex)
{
Logger.Error(string.Format(“Call: {0}”, ex.Message));

//_timer.CancelAlarm();

if (ex is Asterisk.NET.Manager.TimeoutException)
{
UpdateDialoutQueue(CallStatus.Timedout);
DisconnectFromAMI();
}
else
UpdateDialoutQueue(CallStatus.FinishedWithError);
}
}
}

[/csharp]

 

Socket descriptor leak

TCP circuit

Today I noticed that the Openser plugin that I wrote some time ago was causing a major situation on a time-critic application running on an I5OS from IBM iSeries (formerly known as AS400). This application sporadically segfaults after what we thought was a random condition but after some log mining, we found out that the process was being halted because there was too many opened sockets for the process ID that it possesses.

The normal time span for each transaction never exceeds 2 seconds in our typical scenario. This assumption led us to think that there was and bug in the listener thread, some sort of infinite loop breaking the program but, after reading the socket client source file I figured out that was actually a bug in my code.

Since I’m not a TCP expert, I will try to explain the basics of the close procedure of TCP connections to better illustrate the problem.

  1. After the connection was established there is no distinction between server and client. Any of the two parties can close the connection whenever they want.
  2. Consider the two parties as A and B. When B tries to close the connection it sends the FIN packet (TCP packet with the flag FIN activated) to A and enters to FIN-WAIT-1 state.
  3. A replies with an ACK and when B receives this answers, it enters to FIN-WAIT-2. Now B expects the FIN from A.
  4. A sends FIN to finally confirm the disconnection and after the final ACK from B, the socket is closed.

The problem?

Under certain conditions, I wasn’t closing the sockets explicitly leaving it in an inconsistent state: FIN-WAIT-2 on the i5 side, and CLOSE_WAIT on the Linux side. There is no standard timeout to reuse sockets in¬†FIN-WAIT-2, i5OS uses 10 minutes and this value is far too high to recycle the unused sockets. When the counting reached 2000, the program inevitably crashes.

The fix

[c]

BOOL send_message(struct _cnxaaa_net_config *net_config, const str *content, str *reply_buffer, unsigned int timeout)
{
int sockfd, n;
struct sockaddr_in serv_addr;
struct hostent *server;
struct timeval tv;

tv.tv_sec = 0;
tv.tv_usec = timeout;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)
{
_CNXAAA_LOG_ERR(“Error opening socket”);
return FALSE;
}

if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)) != 0)
{
_CNXAAA_LOG_ERR(“Error assigning socket option”);
return FALSE;
}

server = gethostbyname(net_config->ip);
if (server == NULL)
{
_CNXAAA_LOG_ERR(“No such host”);
return FALSE;
}

bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);

serv_addr.sin_port = htons(net_config->port);

if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
{
_CNXAAA_FLOG_ERR(“Error connecting: %s”, strerror( errno ));
close(sockfd); // I wasn’t closing here
return FALSE;
}

n = write(sockfd, content->s, content->len);

if (n < 0)
{
_CNXAAA_FLOG_ERR(“CNXAAA ERROR | Error writing to socket: %s”, strerror( errno ));
close(sockfd); // I wasn’t closing here
return FALSE;
}

n = recv(sockfd, (void *) reply_buffer->s, reply_buffer->len, 0);

if (n < 0)
{
_CNXAAA_FLOG_ERR(“CNXAAA ERROR | Error reading from socket: %s”, strerror( errno ));
close(sockfd); // I wasn’t closing here
return FALSE;
}

reply_buffer->s[n + 1] = ‘’;
reply_buffer->len = n;

close(sockfd);

return TRUE;
}

[/c]

Script to upload users to Moodle

In my spare time, I am a¬†maintainer of¬†moodle¬†powered platforms hosted by a company that offers e-learning services and this post is about uploading new students to that platform ūüėÄ

This task generally requires a CSV file filled with information about the student. The mandatory fields are username, first name, last name and email but this is just an example and some other fields may be required. The tutor or the course owner generally provides an excel file with the necessary data but since an username is needed to login and the students may not have an email account or is not a company’s policy to use personal email addresses for login, we have take alternative measures to fit the requirements.

Here are enumerated some rules imposed for standardization:

1. Username must be the first letter of the forename followed by the first surname, no spaces in between.
2. If no email account is provided, the system will provide one for the user, even if the account doesn’t exists yet.

This is how the file looks like:
[bash]
1 Pedro Pedroso pepe@gmail.com Cobranzas
2 Juan Perez jp123@gmail.com Cobranzas
3 Juan Gonzalez gonzo1900@hotmail.com Cobranzas
4 Un boludo No tiene Por ahi

[/bash]

And this is the output from the script:
[bash]
username, password, firstname, lastname, email
ppedroso, elpassword*, Pedro, Pedroso, pepe@gmail.com
jperez, elpassword*, Juan, Perez, jp123@gmail.com
jgonzalez, elpassword*, Juan, Gonzalez, gonzo1900@hotmail.com
uboludo, elpassword*, Un, boludo, unboludo@example.com
[/bash]

And finally, the script written in Perl. It explains itself very well I think, there is no need for line to line comment ūüėÄ

[perl]
use strict;
use warnings;

open(FILE, “< $ARGV[0]"); print "username, password, firstname, lastname, emailn"; for ()
{
if ($_=~m/([a-z|A-Z]+)s([a-z|A-Z]+)/g)
{
my $name = $1;
my $lastname = $2;
$name=~m/^w/g;
my $username = lc(“$&$lastname”);
my $mail;

if (!($_=~m/[w|_|-]+@w+.(com|net)/g))
{
$mail = lc($name.$lastname).’@example.com’;
}
else
{
$mail = $&;
}

print “$username, elpassword*, $name, $lastname, $mailn”;
}
else
{
die ($_);
}
}
[/perl]

Date/Time operations

Since I am not very fanatic of software homogeneity, when I write perl scripts and I need some function that performs certain task I use commands from console whenever it’s available even knowing that could be an equivalent Perl module available out there.

When your main goal is to get things done in the least amount of time, you cannot afford the time wasted in searching a library/module when the tools are just as far as calling a external program. This doesn’t mean that your code has to look horrible, it means that there’s no need to search for a (long) piece of code to attach it to yours in order to make your program¬†homogeneous. After all, if you know how to write clean code, you will do it even if you mix 2323423423 languages inside your perl script.

Date/Time arithmetic using Perl.

I am aware of the existence of DateTime.pm but I have no experience using it. This was not the case with date command which finally led me to skip the learning curve and pass directly to the coding job.

The problem.

From a CDR entry containing end-datetime and duration, calculate the start-time, and transform the output using the YYMMDD/HHMMSS format.

[perl]

use strict;
use warnings;

sub main()
{
my $startTime = “02/01/2011 12:02:00”;
my $secs = 3650;

print “$startTime – $secs secs (YYMMDD HHMMSS) = @{ [ CalculateDate(“$startTime”, $secs) ] }”;
print “$secs secs in HHMMSS format is = @{ [ FormatTime($secs) ] } n”;
}

sub CalculateDate($$)
{
my ($date, $secs) = @_;

$date = FormatDateTime(date --date "$date $secs seconds ago");

return $date;
}

sub FormatDateTime($)
{
return date --date "$_[0]" +"%y%m%d %H%M%S";
}

sub FormatTime($)
{
my ($seconds, $hours, $mins, $secs) = shift;

$hours = padLeft(int($seconds / 3600));

$mins = padLeft($hours == 0 ? int($seconds / 60) : int(($seconds – ($hours * 3600)) / 60));

$secs = padLeft($hours == 0 && $mins == 0 ? $seconds : $seconds % 60);

return “$hours$mins$secs”;
}

sub padLeft($)
{
my $value = shift;
return (length “$value” == 1) ? “0$value” : $value;
}

main();
[/perl]

And the output is

[bash]
$ perl convert.pl
02/01/2011 12:02:00 – 3650 secs (YYMMDD HHMMSS) = 110201 110110
3650 secs in HHMMSS format is = 010050
[/bash]

Now, what I did not found searching the web was an easy method to convert seconds¬†to a hour:minute:second format. This forced me to write my own algorithm which, hopefully, is bug free ūüėÄ
[perl]
sub FormatTime($)
{
my ($seconds, $hours, $mins, $secs) = shift;

$hours = padLeft(int($seconds / 3600));
$mins = padLeft($hours == 0 ? int($seconds / 60) : int(($seconds – ($hours * 3600)) / 60));
$secs = padLeft($hours == 0 && $mins == 0 ? $seconds : $seconds % 60);
return “$hours$mins$secs”;
}
[/perl]

What would a portability freak say?

It would probably say that my solution ignores the portability features of Perl. Yes, it is true but I don’t really care since my target is, and will always be, Unix based machines.

 

Merging PDF files with Linux

Almost everything I write here is related to problems I face daily and this time I came with something new and not very common :D.

A client of mine asked me to find a solution which allow him to combine several PDFs files into a single one without the “complicated” steps of Windows software. I think there is no free solution to do that and the one I know, Adobe Reader, comes with this feature only with paid licence.

The physical scenario involve a Linux file server reachable by a network of Windows machines acting as SMB/CIFS clients and the procedure to merge the files looks like the following:

– The file server shares, with read/write permissions, a directory (folder) called zip2pdf.
– If a user wants to combine a series of files he just need to zip the files and copy the compressed file into the shared directory.
– After a certain time, proportional to the size of the documents to be merged, a PDF file will “appear” in the shared directory containing the PDFs combined.

How I did this?

– I used a filesystem watcher which notifies me every change on a target directory, in my case, file creation.
– I unzipped the file, sorted the output and merged the PDFs with ghostscript.
– I put it all in a bash script and ran it in background.

[bash]
#!/bin/bash
inotifywait –monitor –event CREATE –format “%w%f” $1 | while read file
do
MATCH=echo $file | grep -P ".zip$"
if [ “$MATCH”!=”” ] && [ -e $file ];
then
sleep 3 # wait an arbitrary value to “ensure” the file was closed.
# There are another ways to check it using lsof for instance,
# but it would complicate the script a lot

STAMP=date +"%Y-%m-%d_%N"
DIR=”uncompressed_$STAMP/”

mkdir -p $DIR

unzip $file -d $DIR

FILES=find $DIR -type f -iname *.PDF | sort
#echo “LIST: $FILES”
gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=$STAMP.pdf -dBATCH $FILES

mv $STAMP.pdf $1
rm -rf $DIR
else
echo NOOOOOOOOOOO
fi
done
[/bash]

[bash]
$ ./z2p.sh /home/carlos/Desktop/zip2pdf/
[/bash]

It works and it was pretty simple ūüėÄ