Category Archives: Kamailio

ENUM in Practice with Kamailio

In our last post we covered the theory behind ENUM and its use, and in this post we’ll cover setting up Kamailio to query an ENUM server.

Before we start, if you’re not familiar with ENUM, check out my primer on the topic here, and then for more in-depth info check out this post on configuring an ENUM server in Bind and the ways records can be configured,

So once we’ve got an ENUM server configured and confirmed we can query it and get the results we want using Dig, we can configure Kamailio.

But before we get to the Kamailio side, a word on how Kamailio handles DNS,
Kamailio doesn’t have the ability to set a DNS server, instead it uses the system DNS server details,
This means your system will need to use the DNS server we want to query for ENUM for all DNS traffic, not just for Kamailio. This means you may need to setup Recursion to still be able to query DNS records for the outside world.

To add support to Kamailio, we’ll need to load the enum module (enum.so),

In terms of parameters, all we’ll set is the doman_suffix, which is, as it sounds, the domain suffix used in the DNS queries. If you’re using a different domain for your ENUM it’d need to be reflected here.

 modparam("enum", "domain_suffix", "e164.arpa.")

Next up inside our minimalist dialplan we’ll just add enum_query(); to query the SIP URI,

if(is_method("INVITE")){
         enum_query();
         xlog("Ran ENUM query");
         xlog("To URI $tU");
         forward();
}

Obviously in production you’d want to add more sanity checks and error handling, but with this, sending a SIP INVITE to Kamailio with an E.164 number in the SIP URI user part, will lead to an ENUM query resolving this, and routing the traffic to it,

An example PCAP above, showing a call to +61355500912, the resulting ENUM query and routing to server1.enum.nickvsnetworking.com,

By using Kamailio’s transaction module ™ as a stateful proxy, we can run queries again if one were to fail,

Copy of example config is available here,

A very unstable Diameter Routing Agent (DRA) with Kamailio

I’d been trying for some time to get Kamailio acting as a Diameter Routing Agent with mixed success, and eventually got it working, after a few changes to the codebase of the ims_diameter_server module.

It is rather unstable, in that if it fails to dispatch to a Diameter peer, the whole thing comes crumbling down, but incoming Diameter traffic is proxied off to another Diameter peer, and Kamailio even adds an extra AVP.

Having used Kamailio for so long I was really hoping I could work with Kamailio as a DRA as easily as I do for SIP traffic, but it seems the Diameter module still needs a lot more love before it’ll be stable enough and simple enough for everyone to use.

I created a branch containing the fixes I made to make it work, and with an example config for use, but use with caution. It’s a long way from being production-ready, but hopefully in time will evolve.

https://github.com/nickvsnetworking/kamailio/tree/Diameter_Fix

Using Docker to develop SIP solutions with Kamailio

VoIP networks are often large beasts, with lots of moving parts; carrier links, SBCs, application servers, etc.

While writing the other Kamailio tutorials on this blog I often find I’m spinning up several VMs to act as different parts of the network, for example in the posts on the Dispatcher module I needed to have 3 VMs to show it in action.

Instead of going to all the effort of creating VMs (or running Ansible playbooks) we can use Docker and docker-compose to create a test environment with multiple Asterisk instances to dispatch traffic to from Kamailio.

I covered the basics of using Kamailio with Docker in this post, which runs a single Kamailio instance inside Docker with a provided config file, but in this post we’ll use docker-compose to run multiple Asterisk instances and setup Kamailio to dispatch traffic to them.

I am a big Kubernetes fan, and yes, all this can be done in Kubernetes, and would be a better fit for a production environment, but for a development environment it’s probably overkill.

Like in the first post that covered Kamailio and Docker we’ll start with a Dockerfile to create the config we want.
The Dockerfile is largely unchanged from my original post on Docker and Kamailio, for the Kamailio config in this example I’m using Dispatcher with a flat text file of the targets to dispatch to (dispatcher.list), so I’ll include a command to copy the two config files into the Container when the image is created in the Dockerfile:

#Copy the config file onto the Filesystem of the Docker instance
COPY dispatcher.list /etc/kamailio/
COPY kamailio.cfg /etc/kamailio/

The Kamailio config we’re using is very similar to the Dispatcher example but with a few minor changes to the timers and setting it to use the Dispatcher data from a text file instead of a database. If you have a look at the contents of dispatcher.list you’ll see three entries; dispatcher_w_docker_asterisk_1, dispatcher_w_docker_asterisk_2 & dispatcher_w_docker_asterisk_3. These will be the hostnames of the 3 Asterisk instances we’ll create.

Next up we’ll take a look at the docker-compose file, which defines how our environment will be composed, and defines which containers will be run

The docker-compose file contains definitions about the Containers we want to run, for this example we’ll run several Asterisk instances and a single Kamailio instance.

I’m using a Docker image for Asterisk from Andrius Kairiukstis to deploy Asterisk, running a default config, so we’ll start our docker-compose file by defining a service called “asterisk” using Andrius’ image:

services:
   asterisk:
     image: andrius/asterisk
     deploy:
       mode: replicated
       replicas: 6

The replicas: 6 parameter is ignored by standard docker-compose up command, but will be used if you’re using Docker swarm, otherwise we’ll manually set the number of replicas when we run the command.

So with that defined let’s define our Kamailio service;

services:
   asterisk:
     image: andrius/asterisk
     deploy:
       mode: replicated
       replicas: 6
   kamailio_dispatcher:
     depends_on:
       - asterisk
     build:
      context: .
      dockerfile: Dockerfile
     ports:
       - "5060:5060/udp"
     restart: always

That will build Kamailio from our Dockerfile and expose port 5060,

We can run the docker-compose file with 3 replicas by running:

nick@oldfaithful:kamailio-101-tutorial/Docker_examples/Dispatcher_w_Docker$ docker-compose up --force-recreate --build --scale asterisk=3

Next if we run docker-ps we can see the running instances, the single Kamailio and the 3 Asterisk instances:

nick@oldfaithful:kamailio-101-tutorial/Docker_examples/Dispatcher_w_Docker$ docker ps

We can also query Kamailio to see how it’s Dispatcher table is looking and confirm it can reach each of our 3 instances:

nick@oldfaithful:kamailio-101-tutorial/Docker_examples/Dispatcher_w_Docker$ docker exec -it dispatcher_w_docker_kamailio_dispatcher_1 kamcmd dispatcher.list

Kamailio Bytes – OnReply Route

So far with most of our discussions about Kamailio we’ve talked about routing the initial SIP request (INVITE, REGISTER, SUBSCRIBE, etc), but SIP is not a one-message protocol, there’s a whole series of SIP messages that go into a SIP Dialog.

Sure the call may start with an INVITE, but there’s the 180 RINGING, the 200 OK and the ACK that go into getting the call actually established, and routing these in-dialog messages is just as important as routing the first INVITE.

When we’ve talked about SIP routing it’s all happened in the request_route {} block:

request_route {
        xlog("Received $rm to $ru - Forwarding");
        append_hf("X-Proxied: You betcha\r\n");
        #Forward to new IP
        forward("192.168.1.110");
}

In the example above we statelessly forward any initial requests to the IP 192.168.1.110.

All the routing from that point on happens using the standard RFC3261 in-dialog routing using the Route headers.

We can add an onreply_route{} block to handle any replies from 192.168.1.110 back to the originator.

But why would we want to?

Some simple answers would be to do some kind of manipulation to the message – say to strip a Caller ID if CLIP is turned off, or to add a custom SIP header containing important information, etc.

onreply_route{
        xlog("Got a reply $rs");
        append_hf("X-Proxied: For the reply\r\n");
}

Let’s imagine a scenario where the destination our SIP proxy is relaying traffic to (192.168.1.110) starts responding with 404 error.

We could detect this in our onreply_route{} and do something about it.

onreply_route{
        xlog("Got a reply $rs");
        if($rs == 404) {
                #If remote destination returns 404
                xlog("Got a 404 for $rU");
                #Do something about it
        }
}

In the 404 example if we were using Dispatcher it’s got easily accessed logic to handle these scenarios a bit better than us writing it out here, but you get the idea.

There are a few other special routes like onreply_route{}, failure routes and event routes, etc.

Hopefully now you’ll have a better idea of how and when to use onreply_route{} in Kamailio.

Kamailio Bytes – Gotchas with Kamailio as an Asterisk Load Balancer

How do I make Kamailio work with Asterisk?

It’s a seemingly simple question, the answer to which is – however you want, sorry if that’s not a simple answer.

I’ve talked about the strengths and weaknesses of Kamailio and Asterisk in my post Kamailio vs Asterisk, so how about we use them to work together?

The State of Play

So before we go into the nitty gritty, let’s imagine we’ve got an Asterisk box with a call queue with Alice and Bob in it, set to ring those users if they’re not already on a call.

Each time a call comes in, Asterisk looks at who in the queue is not already on a call, and rings their phone.

Now let’s imagine we’re facing a scenario where the single Asterisk box we’ve got is struggling, and we want to add a second to share the load.

We do it, and now we’ve got two Asterisk boxes and a Kamailio load balancer to split the traffic between the two boxes.

Now each time a call comes in, Kamailio sends the SIP INVITE to one of the two Asterisk boxes, and when it does, that Asterisk box looks at who is in the queue and not already on a call, and then rings their phone.

Let’s imagine a scenario where a Alice & Bob are both on calls on Asterisk box A, and another call comes in this call is routed to Asterisk box B. Asterisk box B looks at who is in the queue and who is already on a call, the problem is Alice and Bob are on calls on Asterisk box A, so Asterisk box B doesn’t know they’re both on a call and tries to ring them.

We have a problem.

Scaling stateful apps is a real headache,

So have a good long hard think about how to handle these issues before going down this path!

Kamailio Bytes – UAC – Authenticate Outbound Calls

Sometimes you need Kamailio to serve as a User Agent Client, we covered using UAC to send SIP REGISTER messages and respond with the authentication info, but if you find you’re getting 401 or 407 responses back when sending an INVITE, you’ll need to use the UAC module, specifically the uac_auth() to authenticate the INVITE,

When Kamailio relays an INVITE to a destination, typically any replies / responses that are part of that dialog will go back to the originator using the Via headers.

This would be fine except if the originator doesn’t know the user name and password requested by the carrier, but Kamailio does,

Instead what we need Kamailio to do is if the response to the INVITE is a 401 Unauthorised Response, or a 407 Proxy Authentication required, intercept the request, generate the response to the authentication challenge, and send it to the carrier.

To do this we’ll need to use the UAC module in Kamailio and set some basic params:

loadmodule "uac.so"
modparam("uac", "reg_contact_addr", "10.0.1.252:5060")
modparam("uac", "reg_db_url", DBURL)
modparam("uac","auth_username_avp","$avp(auser)")
modparam("uac","auth_password_avp","$avp(apass)")
modparam("uac","auth_realm_avp","$avp(arealm)")

Next up when we relay the INVITE (using the Transaction module because we need the response to be transaction stateful).

Before we can call the t_relay() command, we need to specify a failure route, to be called if a negative response code comes back, we’ll use one called TRUNKAUTH and tell the transaction module that’s the one we’ll use by adding t_on_failure(“TRUNKAUTH”);

	$du = "sip:sip.nickvsnetworking.com:5060";
	if(is_method("INVITE")) {
		t_on_failure("TRUNKAUTH");
		t_relay();
		exit;
	   }

What we’ve done is specified to rewrite the destination URI to sip.nickvsnetworking.com, if the request type is an INVITE, it’ll load a failure route called TRUNKAUTH and proxy the request with the transaction module to sip.nickvsnetworking.com.

What we get is a 401 response back from our imaginary carrier, and included in it is a www-auth header for authentication.

To catch this we’ll create an on failure route named “TRUNKAUTH”

failure_route[TRUNKAUTH] {
    xlog("trunk auth");
    }

We’ll make sure the transaction hasn’t been cancelled, and if it has bail out (no point processing subsequent requests on a cancelled dialog).

failure_route[TRUNKAUTH] {
    xlog("trunk auth");
    if (t_is_canceled()) {
        exit;
    }

And determine if the response code is a 401 Unauthorised Response, or a 407 Proxy Authentication required (Authentication requests from our upstream carrier):

failure_route[TRUNKAUTH] {
    xlog("trunk auth");
    if (t_is_canceled()) {
        exit;
    }
	xlog("Checking status code");
    if(t_check_status("401|407")) {
	xlog("status code is valid auth challenge");
    }
}

Next we’ll define the username and password we want to call upon for this challenge, and generate an authentication response based on these values using the uac_auth() command,

failure_route[TRUNKAUTH] {
    xlog("trunk auth");
    if (t_is_canceled()) {
        exit;
    }
	xlog("Checking status code");
    if(t_check_status("401|407")) {
	xlog("status code is valid auth challenge");
        $avp(auser) = "test";
        $avp(apass) = "test";
        uac_auth();

    }
}

Then finally we’ll relay that back to the carrier with our www-auth header populated with the challenge response;

failure_route[TRUNKAUTH] {
    xlog("trunk auth");
    if (t_is_canceled()) {
        exit;
    }
	xlog("Checking status code");
    if(t_check_status("401|407")) {
	xlog("status code is valid auth challenge");
        $avp(auser) = "test";
        $avp(apass) = "test";
        uac_auth();
	xlog("after uac_auth");
        t_relay();
        exit;
    }
}

And done!

We can get this data from the UAC database so we don’t need to load these values directly into our config file too using the SQLops module.

As always I’ve put the running code example on my GitHub.

Kamailio Bytes – Docker and Containers

I wrote about using Ansible to automate Kamailio config management, Ansible is great at managing VMs or bare metal deployments, but for Containers using Docker to build and manage the deployments is where it’s at.

I’m going to assume you’ve got Docker in place, if not there’s heaps of info online about getting started with Docker.

The Dockerfile

The Kamailio team publish a Docker image for use, there’s no master branch at the moment, so you’ve got to specify the version; in this case kamailio:5.3.3-stretch.

Once we’ve got that we can start on the Dockerfile,

For this example I’m going to include

#Kamailio Test Stuff
FROM kamailio/kamailio:5.3.3-stretch

#Copy the config file onto the Filesystem of the Docker instance
COPY kamailio.cfg /etc/kamailio/

#Print out the current IP Address info
RUN ip add

#Expose port 5060 (SIP) for TCP and UDP
EXPOSE 5060
EXPOSE 5060/udp

Once the dockerfile is created we can build an image,

docker image build -t kamtest:0.1 .

And then run it,

docker run kamtest:0.1

Boom, now Kamailio is running, with the config file I pushed to it from my Dockerfile directory,

Now I can setup a Softphone on my local machine and point it to the IP of the Docker instance and away we go,

Where the real power here comes in is that I can run that docker run command another 10 times, and have another 10 Kamailio instannces running.

Tie this in with Kubernetes or a similar platform and you’ve got a way to scale and manage upgrades unlike anything you’d get on Bare Metal or VMs.

I’ve uploaded a copy of my Dockerfile for reference, you can find it on my GitHub.

Kamailio Proxy-CSCF Pull

I had a few headaches getting the example P-CSCF example configs from the Kamailio team to run, recent improvements with the IPsec support and code evolution meant that the example config just didn’t run.

So, after finally working out the changes I needed to make to get Kamailio to function as a P-CSCF, I took the plunge and made my first pull request on the Kamailio project.

And here it is!

https://github.com/kamailio/kamailio/pull/2203

It’s now in the master branch, so if you want to setup a P-CSCF using Kamailio, give it a shot, as the example config finally works!

Kamailio Bytes – http_client

I’ve touched on the http_client module in Kamailio in the past, and I’ve talked about using Kamailio as an HTTP server.

Today I thought I’d cover a simple use case – running an HTTP get from Kamailio and doing something with the output.

The http_client does what it sounds – Acts as an HTTP client to send HTTP GET and POST requests.

The use cases for this become clear quite quickly, you could use http_client to request credit from an accounting server via it’s API, get the latest rate to a destination from a supplier, pull weather data, etc, etc.

Let’s take a very simple example, we’ll load http_client by adding a loadmodule line:

...
loadmodule "http_client.so"
...

Next I’ve put together a very simple request_route block that requests a HTTP file from a web server and sends the response to a SIP client:

####### Routing Logic ########


/* Main SIP request routing logic
 * - processing of any incoming SIP request starts with this route
 * - note: this is the same as route { ... } */
request_route {

        xlog("Got request");
        http_client_query("https://nickvsnetworking.com/utils/curl.html", "", "$var(result)");
        xlog("Result is $var(result)");
        sl_reply("200", "Result from HTTP server was  $var(result)");
}

Using the http_client_query() function we’re able to query a HTTP server,

We’ll query the URL https://nickvsnetworking.com/utils/curl.html and store the output to a variable called result.

If you visit the URL you’ll just get a message that says “Hello there“, that’s what Kamailio will get when it runs the http_client function.

Next we print the result using an xlog() function so we can see the result in syslog,

Finally we send a stateless reply with the result code 200 and the body set to the result we got back from the server.

We can make this a bit more advanced, using HTTP Post we can send user variables and get back responses.

The http_client module is based on the ubiquitous cURL tool, that many users will already be familiar with.

You can find a full copy of my running code on GitHub.

Kamailio Bytes – Multiple Kamailio Instances on a Single Box

For whatever reason you might want to run multiple Kamailio instances on the same machine.

In my case I was working on an all-in-one IMS box, which needed the P-CSCF, I-CSCF and S-CSCF all in the one box.

init.d File

As you probably already know, all the startup scripts for each service/daemon live in the /etc/init.d directory.

We’ll start by copying the existing init.d file for kamailio:

cp /etc/init.d/kamailio /etc/init.d/kamailio1

Next up we’ll edit it to reflect the changes we want made.

You only really need to change the DEFAULTS= parameter, but you may also want to update the description, etc.

DEFAULTS=/etc/default/kamailio1

The CFGFILE parameter we can update later in the defaults file or specify here.

Defaults File

Next up we’ll need to create a defaults file where we specify how our instance will be loaded,

Again, we’ll copy an existing defaults file and then just edit it.

cp /etc/default/kamailio /etc/default/kamailio1

The file we just created from the copy will need to match the filename we specified in the init.d file for DEFAULTS=

In my case the filename is kamailio1

In here I’ll need to at minimum change the CFGFILE= parameter to point to the config file for the Kamailio instance we’re adding.

In this case the file is called kamailio1.cfg in /etc/kamailio/

For some Ubuntu systems you’re expected to reload the daemons:

systemctl daemon-reload

Starting / Running

Once you’ve done all this you can now try and start your instance using /etc/init.d/kamailio1 start

For my example startup failed as I haven’t created the config file for kamailio1.cfg

So I quickly created a config file and tried to start my service:

/etc/init.d/kamailio1 restart

And presto, my service is running,

I can verify all is running through ps aux:

ps aux | grep kamailio1

Just keep in mind if you want to run multiple instances of Kamailio, you can’t have them all bound to the same address / port.

This also extends to tools like kamcmd which communicate with Kamailio over a socket, again you’d need to specify unique ports for each instance.

Kamailio Bytes – SIP over TLS (SIPS)

It’s probably pretty evident to most why you’d want to use TLS these days,

SIP Secure – aka sips has been around for a long time and is supported by most SIP endpoints now.

Kamailio supports TLS, and it’s setup is very straightforward.

I’ve got a private key and certificate file for the domain nickvsnetworking.com so I’ll use those to secure my Kamailio instance by using TLS.

I’ll start by copying both the certificate (in my case it’s cert.pem) and the private key (privkey.pem) into the Kamailio directory. (If you’ve already got the private key and certificate on your server for another application – say a web server, you can just reference that location so long as the permissions are in place for Kamailio to access)

Next up I’ll open my Kamailio config (kamailio.cfg), I’ll be working with an existing config and just adding the TLS capabilities, so I’ll add this like to the config:

!define WITH_TLS

That’s pretty much the end of the configuration in kamailio.cfg, if we have a look at what’s in place we can see that the TLS module loads it’s parameters from a separate file;

#!ifdef WITH_TLS
# ----- tls params -----
modparam("tls", "config", "/etc/kamailio/tls.cfg")
#!endif

So let’s next jump over to the tls.cfg file and add our certificate and private key;

[server:default]
method = TLSv1
verify_certificate = yes
require_certificate = yes
certificate = fullchain.pem
private_key = privkey.pem

Boom, as simple as that,

After restarting Kamailio subscribers can now contact us via TLS using sips.

You may wish to disable TCP & UDP transport in favor of only TLS.

A note about CAs…

If you’re planning on rolling out SIP over TLS (sips) to existing IP phones it’s worth looking at what Certificate Authorities (CAs) are recognised by the IP phones.

As TLS relies on a trust model where a CA acts kind of like a guarantor to the validity of the certificate, if the IP phone doesn’t recognise the CA, it may see the certificate as Invalid.

Some IP phones for example won’t recognize Let’s Encrypt certificates as valid, while others may not recognize any of the newer CAs.

Vendor Yealink publishes a list of CAs their IP phones recognize, which could save you a lot of headaches when setting this up and buying certificates.

Kamailio Bytes – Nightly Releases the Lazy Way

Installing from source can be a headache,

If you’re running a Debian system, the Kamailio team provide nightly development builds as Debian packages that can be installed on Debian or Ubuntu systems using the apt package manager.

Installing is a breeze, first we just add the GPG key for the repo:

 wget -O- http://deb.kamailio.org/kamailiodebkey.gpg | sudo apt-key add - 

Then it’s just a matter of adding the release to your /etc/apt/sources.list file.

I’m running Bionic, so I’ll add:

deb     http://deb.kamailio.org/kamailiodev-nightly bionic main
deb-src http://deb.kamailio.org/kamailiodev-nightly bionic main 

Then just update and install the packages you require:

apt-get update
apt-get install kamailio*

For a full list of the Debian packages published check out the Debian package list:

https://deb.kamailio.org/

Where you can find the nightly builds and stable builds for each of the releases.

Enjoy!

Kamailio Bytes – Ansible for Automating Deployments

Despite the fact it’s 2020 there’s still a lot of folks in the world manually configuring boxes,

Ansible is a topic I could talk all day about, but in essence it’s kind of an automation framework, tell Ansible what to do one and it can spin you up two boxes, or two thousand boxes and manage the config on them.

I talked about DMQ, the Distributed Message Queue in a Kamailio Bytes post some time ago, and so as an example I’ll share an example playbook to Install Kamailio the lazy way from the Repos, and load the DMQ config with the IP Address and DMQ Address pulled from variables based on the host itself.

There’s a huge number of posts on installing and the basics of Ansible online, if you’re not familiar with Ansible already I’d suggest starting by learning the basics and then rejoining us.

The Hosts

Depending on if your hosts are running on bare metal, VMWare VMs or cloud based, I’m going to assume you’re working with a Debian system.

I’ve already got 3 servers ready to go, they’ve got sequential IP Addresses so I’ve added the range to my /etc/ansible/hosts file:

I’ve created the group kamailio and put the IP Address range 10.0.1.193 to 10.0.1.195 in there.

You will probably need to add the authentication info, such as passwords, private keys and privilege escalation details, but I’m going to assume you’ve already done that and you can run the ping module on each one:

ansible kamailio -m ping

Assuming that comes back OK and you can get into each one let’s move onto the Playbook.

The Playbook

There’s a few tasks we’ll get out of the way before we configure Kamailio,

The first of which is adding the Debian repo and the keys,

Next we’ll load a Kamailio config from a template that fills in our IP Address and Kamailio version, then we’ll install Kamailio,

Rather than talk you through each of the plays here’s a copy of my playbook:

---
- name: Configure Kamailio


  hosts: kamailio
  become: yes

  vars:
    kamailio_version: "53"
    debian_sources_dir: "/etc/apt/sources.list.d"

  tasks:



    - name: Add keys for Kamailio repo
      apt_key:
        url: http://deb.kamailio.org/kamailiodebkey.gpg
        state: present


    - name: Add repo to sources.list
      apt_repository:
        repo: deb http://deb.kamailio.org/kamailio{{kamailio_version}} {{hostvars[inventory_hostname]['ansible_lsb']['codename']}} main
                #The full list of Debian repos can be found at http://deb.kamailio.org/
                #The version is based off the versions listed there and the release is based on the codename of the Debian / Ubuntu release.
        state: present


    - name: Copy Config Template
                #Copies config from the template, fills in variables and uplaods to the server
      template:
        src: kamailio.cfg.j2
        dest: /etc/kamailio/kamailio.cfg
        owner: root
        group: root
        backup: yes
      register: config_changed

    - name: Install Kamailio
                #Updates cache (apt-get update) and then installs Kamailio
      apt:
        name: kamailio
        update_cache: yes
        state: present
      register: kamailio_installed_firstrun

    - name: Restart Kamailio if config changed
      service:
       name: kamailio
       state: restarted
      when: config_changed.changed

    - name: Start Kamailio if installed for the first time
      service:
       name: kamailio
       state: started
      when: kamailio_installed_firstrun.changed

Should be pretty straight forward to anyone who’s used Ansible before, but the real magic happens in the template module. Let’s take a look;

Kamailio config in Jinja2 template

Pusing out static config is one thing, but things like IP Addresses, FQDNs and SSL certs may differ from machine to machine, so instead of just pushing one config, I’ve created a config and added some variables in Jinja2 format to the config, that will be filled with the value on the target when pushed out.

In the template module of the playbook you can see I’ve specified the file kamailio.cfg.j2 this is just a regular Kamailio config file but I’ve added some variables, let’s look at how that’s done.

On the machine 10.0.1.194 we want it to listen on 10.0.1.194, we could put list 0.0.0.0 but this can lead to security concerns, so instead let’s specify the IP in the Jinja config,

listen=udp:{{ ansible_default_ipv4.address }}:5060
listen=tcp:{{ ansible_default_ipv4.address }}:5060
listen=udp:{{ ansible_default_ipv4.address }}:5090

By putting ansible_default_ipv4.address in two sets of curly brackets, this tells Ansible to fill in thes values from the template with the Ansible IPv4 Address of the target machine.

Let’s take a look on the 10.0.1.194’s actual kamailio.cfg file:

Let’s take another example,

To keep DMQ humming it makes sense to have different DMQ domains for different versions of Kamailio, so in the Kamailio config file template I’ve called the variable kamailio_version in the DMQ address,

This means on a Kamailio 5.2 version this URL look like this on the boxes’ config:

# ---- dmq params ----
modparam("dmq", "server_address", "sip:10.0.1.194:5090")
modparam("dmq", "notification_address", "sip:dmq-53.nickvsnetworking.com:5090")

Running It

Running it is just a simple matter of calling ansible-playbook and pointing it at the playbook we created, here’s how it looks setting up the 3 hosts from their vanilla state:

The great thing about Kamailio is it’s omnipotent – This means it will detect if it needs to do each of the tasks specified in the playbook.

So if we run this again it won’t try and add the repo, GPG keys, install Kamailio and load the template, it’ll look and see each of those steps have already been done and skip each of them.

But what if someone makes some local changes on one of the boxes, let’s look at what happens:

Likewise now if we decide to change our config we only need to update the template file and Ansible will push it out to all our machines, I’ve added a comment into the Kamailio template, so let’s run it again and see the config pushed out to all the Kamailio instances and them restarting.

Hopefully this gives you a bit more of an idea of how to manage a large number of Kamailio instances at scale, as always I’ve put my running code on GitHub, Ansible Playbook (configure_kamailio.yml) and Kamailio Jinja config template (kamailio.cfg.j2)

VoLTE Logo on Samsung Galaxy Handset

Things I wish I knew about setting up private VoLTE Networks

I’ve been working for some time on open source mobile network cores, and one feature that has been a real struggle for a lot of people (Myself included) is getting VoLTE / IMS working.

Here’s some of the issues I’ve faced, and the lessons I learned along the way,

Sadly on most UEs / handsets, there’s no “Make VoLTE work now” switch, you’ve got a satisfy a bunch of dependencies in the OS before the baseband will start sending SIP anywhere.

Get the right Hardware

Your eNB must support additional bearers (dedicated bearers I’ve managed to get away without in my testing) so the device can setup an APN for the IMS traffic.

Sadly at the moment this rules our Software Defined eNodeBs, like srsENB.

In the end I opted for a commercial eNB which has support for dedicated bearers.

ISIM – When you thought you understood USIMs – Guess again

According to the 3GPP IMS docs, an ISIM (IMS SIM) is not a requirement for IMS to work.

However in my testing I found Android didn’t have the option to enable VoLTE unless an ISIM was present the first time.

In a weird quirk I found once I’d inserted an ISIM and connected to the VoLTE network, I could put a USIM in the UE and also connect to the VoLTE network.

Obviously the parameters you can set on the USIM, such as Domain, IMPU, IMPI & AD, are kind of “guessed” but the AKAv1-MD5 algorithm does run.

Getting the APN Config Right

There’s a lot of things you’ll need to have correct on your UE before it’ll even start to think about sending SIP messaging.

I was using commercial UE (Samsung handsets) without engineering firmware so I had very limited info on what’s going on “under the hood”. There’s no “Make VoLTE do” tickbox, there’s VoLTE enable, but that won’t do anything by default.

In the end I found adding a new APN called ims with type ims and enabling VoLTE in the settings finally saw the UE setup an IMS dedicated bearer, and request the P-CSCF address in the Protocol Configuration Options.

Also keep in mind on Android at least, what you specify as your APN might be ignored if your UE thinks it knows best – Thanks to the Android Master APN Config – which guesses the best APN for you to use, which is a useful feature to almost any Android user, except the very small number who see fit to setup their own network.

Get the P-GW your P-CSCF Address

If your P-GW doesn’t know the IP of your P-CSCF, it’s not going to be able to respond to it in the Protocol Configuration Options (PCO) request sent by the UE with that nice new bearer for IMS we just setup.

There’s no way around Mutual Authentication

Coming from a voice background, and pretty much having RFC 3261 tattooed on my brain, when I finally got the SIP REGISTER request sent to the Proxy CSCF I knocked something up in Kamailio to send back a 200 OK, thinking that’d be the end of it.

For any other SIP endpoint this would have been fine, but IMS Clients, nope.

Reading the specs drove home the same lesson anyone attempting to setup their own LTE network quickly learns – Mutual authentication means both the network and the UE need to verify each other, while I (as the network) can say the UE is OK, the UE needs to check I’m on the level.

For anyone not familiar with the intricacies of 3GPP USIM Network Authentication, I’ve written about Mutual Network Authentication in this post.

In the end I added Multimedia Authentication support to PyHSS, and responded with a Crypto challenge using the AKAv1-MD5 auth,

For anyone curious about what goes on under the hood with this, I wrote about how the AKAv1-MD5 Authentication algorithm works in this post,

I saw my 401 response go back to the UE and then no response. Nada.

This led to my next lesson…

There’s no way around IPsec

According to the 3GPP docs, support for IPsec is optional, but I found this not to be the case on the handsets I’ve tested.

After sending back my 401 response the UE looks for the IPsec info in the 401 response, then tries to setup an IPsec SA and sends ESP packets back to the P-CSCF address.

Even with my valid AKAv1-MD5 auth, I found my UE wasn’t responding until I added IPsec support on the P-CSCF, hence why I couldn’t see the second REGISTER with the Authentication Info.

After setting up IPsec support, I finally saw the UE’s REGISTER with the AKAv1-MD5 authentication, and was able to send a 200 OK.

For some more info on ESP, IPsec SAs and how it works between the UE and the P-CSCF there’s a post on that too.

Get Good at Mind Reading (Or an Engineering Firmware)

To learn all these lessons took a long time,

One thing I worked out a bit late but would have been invaluable was cracking into the Engineering Debug options on the UEs I was testing with.

Samsung UEs feature a Sysdump utility that has an IMS Debugging tool, sadly it’s only their for carriers doing IMS interop testing.

After a bit of work I detailed in this post – Reverse Engineering Samsung Sysdump Utils to Unlock IMS Debug & TCPdump on Samsung Phones – I managed to create a One-Time-Password generator for this to generate valid Samsung OTP keys to unlock the IMS Debugging feature on these handsets.

I outlined turning on these features in this post.

This means without engineering firmware you’re able to pull a bunch of debugging info off the UE.

If you’ve recently gone through this, are going through this or thinking about it, I’d love to hear your experiences.

I’ll be continuing to share my adventures here and elsewhere to help others get their own VoLTE networks happening.

If you’re leaning about VoLTE & IMS networks, or building your own, I’d suggest checking out my other posts on the topic.

SIP SIMPLE – Instant Messaging with SIP

People think SIP they think VoIP & phone calls, but SIP it’s the Phone Call Initiation Protocol it’s the Session Initiation Protocol – Sure VoIP guys like me love SIP, but it’s not just about VoIP.

Have you sent an SMS on a modern mobile phone recently? Chances are you sent a SMS over SIP using SIP MESSAGE method.

So let’s look a bit at SIP SIMPLE, the catchily titled acronym translates to Session Initiation Protocol for Instant Messaging and Presence Leveraging Extensions (Admittedly less catchy in it’s full form).

There’s two way SIP SIMPLE can be used to implement Instant Messaging, Paging Mode with each message sent as a single transaction, and Session Mode where a session is setup between users and IMs exchanged with the same Call ID / transaction.

I’m going to cover the Paging Mode implementation because it’s simpler easier to understand.

Before we get too far this is another example of confusing terminology, let’s just clear this up; According to the RFC any SIP request is a SIP Message, like a SIP OPTIONS message, a SIP INVITE message. But the method of a SIP INVITE message is INVITE, the method of a SIP OPTIONS message is OPTIONS. There’s a SIP MESSAGE method, meaning you can send a SIP MESSAGE message using the MESSAGE method. Clear as mud? I’ll always refer to the SIP Method in Capitals, like MESSAGE, INVITE, UPDATE, etc.

The SIP MESSAGE Method

The basis of using SIP for instant messaging relies on the MESSAGE method, laid out in RFC 3428.

The SIP MESSAGE method looks / acts very similar to a SIP INVITE, in that it’s got all the standard SIP headers, but also a Message Body, in which our message body lives (funny about that), typically we’ll send messages using the Content-Type: text/plain to denote we’re sending a plaintext message.

Example MESSAGE Message Flow

Like a SIP OPTIONS Method, the MESSAGE method is simply answered with a 200 OK (No Ack).

Let’s have a look at how the MESSAGE message looks:

MESSAGE sip:[email protected] SIP/2.0
Via: SIP/2.0/TCP user1pc.domain.com;branch=z9hG4bK776sgdkse
Max-Forwards: 70
From: sip:[email protected];tag=49583
To: sip:[email protected]
Call-ID: [email protected]
CSeq: 1 MESSAGE
Content-Type: text/plain
Content-Length: 18

Hello world.

After receiving the SIP MESSAGE message, the recipient simply sends back a 200 OK with the same Call-ID.

Simple as that.

You can read more about the SIP MESSAGE method in RFC 3428.

I used the SIP MESSAGE method in a Kamailio Bytes example recently where I sent a MESSAGE to an IP phone when a HTTP GET was run against Kamailio, and again to send an alert when an emergency services destination was called.

Kamailio Bytes – Python + SIP with KEMI

In my last post I talked about using KEMI in Kamailio and how you can integrate in a different programming language to handle your SIP request handling in a language you already know – Like Python!

So in this post I’ll cover the basics of how we can manage requests and responses from Kamailio in Python, if you haven’t already read it, go back to last weeks post and get that running, it’s where we’ll start off.

The Framework

Before we get too excited there’s some boilerplate we’ve got to add to our Python script, we need to create a class called kamailio and populate the class by defining some functions, we’ll define an __init__ to handle loading of the class, define a child_init for handling child processes, define ksr_request_route to handle the initial requests. We’ll also need to define a mod_init – outside of the Kamailio class to initialize the class.

import sys
import Router.Logger as Logger
import KSR as KSR

import requests

# global function to instantiate a kamailio class object
# -- executed when kamailio app_python module is initialized
def mod_init():
    KSR.info("===== from Python mod init\n");
    return kamailio();


# -- {start defining kamailio class}
class kamailio:
    def __init__(self):
        KSR.info('===== kamailio.__init__\n');


    # executed when kamailio child processes are initialized
    def child_init(self, rank):
        KSR.info('===== kamailio.child_init(%d)\n' % rank);
        return 0;


    # SIP request routing
    # -- equivalent of request_route{}
    def ksr_request_route(self, msg):
        KSR.info("===== request - from kamailio python script\n");
        KSR.dbg("method " + KSR.pv.get("$rm") + " r-uri " + KSR.pv.get("$ru"))

Most of these should be pretty self explanatory for anyone who’s done a bit more in-depth Python programming, but it’s no big deal if you don’t understand all this, the only part you need to understand is the ksr_request_route function.

ksr_request_route: translates to our request_route{} in the Kamailio native scripting language, all requests that come in will start off in this part.

Python Kamailio Routing

So let’s start to build upon this, so we’ll blindly accept all SIP registrations;

...
    # SIP request routing
    # -- equivalent of request_route{}
    def ksr_request_route(self, msg):
        KSR.info("===== request - from kamailio python script\n");
        KSR.dbg("method " + KSR.pv.get("$rm") + " r-uri " + KSR.pv.get("$ru"))


        if KSR.is_method("REGISTER"):
                KSR.sl.send_reply(200, "Sure")

Here you’ll see we’ve added an if statement, as if we were doing any other If statement in Python, in this case we’re asking if the KSR.is_method(“REGISTER”), and if it is, we’ll send back a 200 OK response.

Let’s pause and talk about KSR

All the Kamailio bits we’ll use in Python will have the KSR. prefix, so let’s take a quick break here to talk about KSR. The KSR. functions are the KEMI functions we’ve exposed to Python.

Without them, we’re just writing Python, and we’d have to do all the functions provided by Kamailio nativeley in Python, which would be crazy.

So we leverage the Kamailio modules you know and love from Python using Python’s logic / programming syntax, as well as opening up the ability to pull in other libraries from Python.

There’s a full (ish) list of the KEMI functions here, but let’s talk about the basics.

Let’s look at how we might send a stateless reply,

There’s a module function to send a stateless reply;

 KSR.sl.send_reply(200, "OK")

The vast majority of functions are abstracted as module functions, like the example above, but not all of them.

So every function doesn’t need to be wrapped up as a module, there’s also a way to call any function that you’d call from the native scripting language, wrapped up, kind of like an Exec command:

KSR.x.modf("sl_send_reply", "200", "OK");

So thanks to this we can call any Kamailio function from Python, even if it’s not explicitly in the KEMI abstraction.

Python Kamailio Routing (Continued)

So earlier we managed REGISTER requests and sent back a 200 OK response.

What about forwarding a SIP Request to another proxy? Let’s follow on with an elif statement to test if the method is an INVITE and statelessly forward it.

        elif KSR.is_method("INVITE"):
                #Lookup our public IP address
                try:
                    ip = requests.get('https://api.ipify.org').text
                except:
                    ip = "Failed to resolve"

                #Add that as a header
                KSR.hdr.append("X-KEMI: I came from KEMI at " + str(ip) + "\r\n");

                #Set host IP to 10.1.1.1
                KSR.sethost("10.1.1.1");

                #Forward the request on
                KSR.forward()

Now an incoming SIP invite will be proxied / forwarded to 10.1.1.1, all from Python.

But so far we’ve only done things in KEMI / Python that we could do in our native Kamailio scripting language, so let’s use some Python in our Python!

I utterly love the Python Requests library, so let’s use that to look up our public IP address and add it as a header to our forwarded SIP INVITE;

        elif KSR.is_method("INVITE"):
                #Lookup our public IP address
                try:
                    ip = requests.get('https://api.ipify.org').text
                except:
                    ip = "Failed to resolve"

                #Add that as a header
                KSR.hdr.append("X-KEMI: I came from KEMI at " + str(ip) + "\r\n");

                #Set host IP to 10.1.1.1
                KSR.sethost("10.1.1.1");

                #Forward the request on
                KSR.forward()

(For anyone pedantic out there, Kamailio does have an HTTP client module that could do this too, but Requests is awesome)

So let’s have a look at our forwarded request:

Bottom header is the X-KEMI custom header we included with our public IP

So let’s wrap this up a bit and handle any other request that’s not an INVITE or a REGISTER, with a 500 error code.

    # SIP request routing
    # -- equivalent of request_route{}
    def ksr_request_route(self, msg):

        KSR.dbg("method " + KSR.pv.get("$rm") + " r-uri " + KSR.pv.get("$ru"))


        if KSR.is_method("REGISTER"):
            KSR.sl.send_reply(200, "OK")

        elif KSR.is_method("INVITE"):
                #Lookup our public IP address
                try:
                    ip = requests.get('https://api.ipify.org').text
                except:
                    ip = "Failed to resolve"

                #Add that as a header
                KSR.hdr.append("X-KEMI: I came from KEMI at " + str(ip) + "\r\n");

                #Set host IP to 10.1.1.1
                KSR.sethost("10.1.1.1");

                #Forward the request on
                KSR.forward()
        else:
               KSR.sl.send_reply(500, "Got no idea...")

I’ve put my full code on GitHub which you can find here.

Kamailio Bytes – UAC for Remote User Registration to external SIP Server (Originating SIP REGISTER)

I’ve talked about using the UAC module, but as promised, here’s how we can use the UAC module to send SIP REGISTER requests to another SIP server so we can register to another SIP proxy.

Let’s say we’re using Kamailio to talk to a SIP Trunk that requires us to register with them so they know where to send the calls. We’d need to use Kamailio UAC module to manage SIP Registration with our remote SIP Trunk.

But Kamailio’s a proxy, why are we sending requests from it? A proxy just handles messages, right?
Proxies don’t originate messages, it’s true, and Kamailio can be a proxy, but with the UAC module we can use Kamailio as a Client instead of a server. Keep in mind Kamailio is what we tell it to be.

Getting Started

Before we can go spewing registrations out all over the internet we need to start by getting a few things in place;

First of which is configuring UAC module, which is something I covered off in my last post,

We’ll also need to have a database connection in place, again I’ve covered off connecting to a MySQL database in Kamailio here.

Once we’ve got that done we’ll need to tell the UAC module our IP Address for the from address for our Contact field, and the database URL of what we’ve setup.

modparam("uac", "reg_contact_addr", "192.168.1.99:5060")
modparam("uac", "reg_db_url", "mysql://kamailio:kamailiorw@localhost/kamailio")

I haven’t used a variable like DBURL for the database information, but you could.

Finally a restart will see these changes pushed into Kamailio.

/etc/init.d/kamailio restart

This is the end of the Kamailio config side of things, which you can find on my GitHub here.

Defining the Registration parameters

Once we’ve got a database connection in place and UAC module loaded, then we can configure an entry in the uacreg table in the database, in my example I’m going to be registering to an Asterisk box on 192.168.1.205, so I’ll insert that into my database:

mysql> INSERT INTO `uacreg` VALUES (NULL,'myusername','myusername','192.168.1.205','myusername','192.168.1.205','asterisk','myusername','mypassword','','sip:192.168.1.205:5060',60,0,0);

Note: If you’re using a later version of Kamailio (5.4+) then the DB schema changes and you may want something like this:

insert into uacreg values ('', 'myusername', 'myusername', 'mydomain', 'myusername', 'mydomain', 'asteriskrealm', 'myusername', 'mypassword', '', 'sip:remoteproxy.com:5060', 60, 0, 0, 0)

Having a look at the fields in our table makes it a bit clearer as to what we’ve got in place, setting flags to 0 will see Kamailio attempt registration. Make sure the auth_proxy is a SIP URI (Starts with sip:) and leave the auth_ha1 password empty as we haven’t calculated it.

mysql> SELECT * FROM 'uacreg' \G
            id: 2
        l_uuid: myusername
    l_username: myusername
      l_domain: 192.168.1.205
    r_username: myusername
      r_domain: 192.168.1.205
         realm: asterisk
 auth_username: myusername
 auth_password: mypassword
      auth_ha1:
    auth_proxy: sip:192.168.1.205:5060
       expires: 60
         flags: 0
     reg_delay: 0

Putting it into Play

After we’ve got our database connection in place, UAC module configured and database entries added, it’s time to put it into play, we’ll use Kamcmd to check it’s status:

kamcmd> uac.reg_reload
kamcmd> uac.reg_dump

Unfortunately from Kamcmd we’re not able to see registration status, but Sngrep will show us what’s going on:

From Sngrep we can see the REGISTRATION going out, the authentication challenge and the 200 OK at the end.

Make sure you’ve got your Realm correct, otherwise you may see an error like this:

RROR: {2 10 REGISTER [email protected]} uac [uac_reg.c:946]: uac_reg_tm_callback(): realms do not match. requested realm: [localhost]

If you’re not familiar with the SIP Registration process now’s a good time to brush up on it by having a read of my post here. – “What is a SIP Registrar?”

Kamailio Bytes – SIP UAC Module to act as a UAC / SIP Client

Kamailio is a great SIP proxy, but sometimes you might want to see requests originate from Kamailio.

While this isn’t typical proxy behaviour, RFC definitions of a proxy and technical requirements are often two different things. The UAC module allows us to use Kamailio to act as a User Agent Client instead of just a UAS.

There’s one feature I won’t cover in this post, and that’s initiating and outbound SIP Registration using the UAC module, that will get a post of it’s own in the not to distant future.

You may already be sort of using Kamailio is a UAC, if you’re using Dispatcher and sending SIP Pings, then Kamailio is sending SIP OPTIONS messages to the dispatcher destinations. If you’re using the NAT module and sending Keepalives, then you’re also using Kamailio as a UAC. The only difference is the Dispatcher and NAT Helper modules do this for us, and we’re going to originate our own traffic.

There’s a bit of a catch here, when Kamailio receives a request it follows a set of logic and does something with that request. We’re going to remain constrained by this for our example, just to keep things simple.

So let’s work on an example, if a user on our network dials a call to an emergency services number, we’ll send a text message to my IP phone to let me know who’s dialed the emergency services number.

So to start with we’ll need to load the Kamailio UAC module, using LoadModule as we would with any other module:

loadmodule "uac.so"

If you’re working on the default config file that ships with Kamailio you’ll probably have to change how record routing is handled to support UAC,

modparam("rr", "append_fromtag", 1)

Now we should have UAC support added in Kamailio, I’m going to do a bare bones example of the routing logic below, but obviously if you wanted to put this into practice in real life you’d want to actually route the SIP INVITE to an emergency services destination.

First we’ll need to find if the request is an INVITE with the Request URI to an emergency services number, I’ve programmed this in with the Australian emergency services numbers:

if(is_method("INVITE") && ($rU == "000" or $tU == "112" or $tU == "116")){      
  #Matches any INVITEs with the Request URI to Address as 000, 112 or 116
  xlog("Emergency call from $fU to $rU (Emergency number) CSeq is $cs ");
}

Now calls to 000, 112 or 116 will see the alert apear in Xlog:

07:22:41 voice-dev3 /usr/sbin/kamailio[10765]: ERROR: : Emergency call from Test to 112 (Emergency number)

So next up we need to handle the sending a SIP MESSAGE request to my IP phone on the IP 10.0.1.5 – You’re probably thinking we could use the Registrar module to lookup my registered IP address, and you’re right, but to keep things simple I’m just hardcoding it in.

So to keep our routing neat we’ll send calls to the route route(“EmergencyNotify”); and so the demo works I’ll send back a 200 OK and exit – In real life you’d want to handle this request and forward it onto emergency services.

if(is_method("INVITE") && ($rU == "000" or $tU == "112" or $tU == "116")){      
#Matches any INVITEs with the Request URI to Address as 000, 112 or 116
  xlog("Emergency call from $fU to $rU (Emergency number) CSeq is $cs ");
  route("EmergencyNotify");
  #You obviously would want this to route to an emergency services destination...
  sl_reply("200", "ok");
  exit;
}

if(is_method("INVITE")){                                                                                
  #Matches everything else
  xlog("Just a regular call from $fU to $rU");
}

Obviously we need to now create a route called route[“EmergencyNotify”]{ } where we’ll put our UAC logic.

For the UAC module we need to craft the SIP Request we’re going to send; we’re going to be sending a SIP MESSAGE request,

route["EmergencyNotify"]{
  xlog("Emergency Notify Route");
  $uac_req(method)="MESSAGE";
  $uac_req(ruri)="sip:10.0.1.5:5060";
  $uac_req(furi)="sip:Emergency Alert";
  $uac_req(turi)="sip:thisphone";
  $uac_req(callid)=$(mb{s.md5});
  $uac_req(hdrs)="Subject: Emergency Alert\r\n";
  $uac_req(hdrs)=$uac_req(hdrs) + "Content-Type: text/plain\r\n";
  $uac_req(body)="Emergency call from " + $fU + " on IP Address " + $si + " to " + $rU + " (Emergency Number)";
  $uac_req(evroute)=1;
  uac_req_send();
}

So now we’ve sort of put it all together, when a call comes into an emergency destination, like 000, the route EmergencyNotify is called which sends a SIP MESSAGE request to my IP Phone to alert me.

When a caller dials 000 I can see Kamailio sends a SIP MESSAGE to my IP Phone:

Let’s have a look at how this looks on my IP Phone:

I’ve fleshed out the code a little more to handle SIP REGISTER requests etc, and put the full running code on GitHub which you can find here.

Kamailio Bytes – KEMI Intro

When learning to use Kamailio you might find yourself thinking about if you really want to learn to write a Kamailio configuration file, which is another weird scripting language to learn to achieve a task.

Enter KEMI – Kamailio Embedded Interface. KEMI allows you to abstract the routing logic to another programing language. In layman’s terms this means you can write your routing blocks, like request_route{}, reply_route{}, etc, in languages you already know – like Lua, JavaScript, Ruby – and my favorite – Python!

Why would you use KEMI?

Write in a language you already know;

You don’t need to learn how to do write complex routing logic in Kamailio’s native scripting language, you can instead do it in a language you’re already familiar with, writing your Routing Blocks in another programming language.

Change Routing on the Fly;

By writing the routing logic in KEMI allows you to change your routing blocks without having to restart Kamailio, something you can’t do with the “native” scripting language – This means you can change your routing live.

Note: This isn’t yet in place for all languages – Some still require a restart.

Leverage your prefered language’s libraries;

While Kamailio’s got a huge list of modules to interface with a vast number of different things, the ~200 Kamailio modules don’t compare with the thousands of premade libraries that exist for languages like Python, Ruby, JavaScript, etc.

Prerequisites

We’ll obviously need Kamailio installed, but we’ll also need the programming language we want to leverage setup (fairly obvious).

Configuring Kamailio to talk to KEMI

KEMI only takes care of the routing of SIP messages inside our routing blocks – So we’ve still got the Kamailio cfg file (kamailio.cfg) that we use to bind and setup the service as required, load the modules we want and configure them.

Essentially we need to load the app for the language we use, in this example we’ll use app_python3.so and use that as our Config Engine.

loadmodule "app_python3.so"
modparam("app_python3", "load", "/etc/kamailio/kemi.py")
cfgengine "python"

After that we just need to remove all our routing blocks and create a basic Python3 script to handle it,

We’ll create a new python file called kemi.py

## Kamailio - equivalent of routing blocks in Python
import sys
import Router.Logger as Logger
import KSR as KSR

# global function to instantiate a kamailio class object
# -- executed when kamailio app_python module is initialized
def mod_init():
    KSR.info("===== from Python mod init\n");
    return kamailio();


# -- {start defining kamailio class}
class kamailio:
    def __init__(self):
        KSR.info('===== kamailio.__init__\n');


    # executed when kamailio child processes are initialized
    def child_init(self, rank):
        KSR.info('===== kamailio.child_init(%d)\n' % rank);
        return 0;


    # SIP request routing
    # -- equivalent of request_route{}
    def ksr_request_route(self, msg):
        KSR.info("===== request - from kamailio python script\n");
        KSR.info("===== method [%s] r-uri [%s]\n" % (KSR.pv.get("$rm"),KSR.pv.get("$ru")));

So that’s it! We’re running,

The next step is of course, putting some logic into our Python script, but that’s a topic for another day, which I’ve covered in this post.

Running code for kamailio.cfg (Kamailio config) and kemi.py (Python3 script).