Kamailio 101 – Part 5- First Call

So now we’ve got Kamailio handling REGISTER traffic, and we know what IPs endpoints are on, so let’s join this together and let’s route a call between two endpoints via our Proxy!

We’ll work on the config we were working on in the previous tutorial:

/* Main SIP request routing logic
 * - processing of any incoming SIP request starts with this route
 * - note: this is the same as route { ... } */
request_route {
        if(method=="INVITE"){
                sl_reply("480", "Temporarily Unavailable");
                exit;
        }
        if(method=="REGISTER"){
                save("location");
                exit;
        }
        sl_reply("501", "Not Implemented");
}

Let’s change how we handle the INVITE messages in our if(method==”INVITE”) block so instead of responding with a 480 Unavailable response, let’s lookup the location we saved if it was a REGISTER and forward the INVITE to the IP we’ve got.

        if(method=="INVITE"){
                lookup("location");
                t_relay();
                exit();
        }

Let’s break down each of the new functions we’ve introduced:

lookup(“location”);

In the last post we introduced save(“location”); which saves an Address on Record for the URI. lookup(“location”); looks up the IP address of the URI we’re after in the Address on Record table we wrote to with our save(“location”); call and automatically sets it as the destination IP of where we’re going to send the message.

When a user registered via a SIP REGISTER request, we saved their details, now we’re looking them up.

You could even replace the lookup(“location”) call with say a SQL lookup on an address book, and save the output to the IP the INVITE will be forwarded to, but lookup(“location”); does this it all in one function.

t_relay();

t_relay is transactional relay function. By transactional it means Kamailio remembers this session next time it’s referenced, we’ll touch upon transaction aware / stateful SIP proxies later, but for now what you need to know is it forwards the INVITE we just received to the address we got from our lookup(“location”).

exit();

Exit bails out so we won’t keep processing after that, it doesn’t just bail out of our current conditional but stops processing this request further. Without it we’d still go on and send the 501 Not Implemented reply.

The Setup

I’ve setup two phones – One using the username 106 and the other using 108, I’ve pointed both to the IP of our Kamailio instance and they’ve registered. Keep in mind if you’ve restarted Kamailio it’ll lose it’s location table, as it’s stored in memory, so you’ll have to force the phones to register again.

You can check you’ve got devices registered by using kamcmd ul.dump at the command line to confirm you’ve got entries for both. I have so I’ll try dialling 108 from 106:

INVITE -> Trying -> Ringing -> OK

Here we can see the INVITE coming in, the lookup(“location”) doing it’s magic and forwarding the messages to the IP of 108.

The 100 Trying, 180 Ringing and 200 OK messages all go through on their own as they’re part of an existing dialog which we’ll touch on why later in the series,

So we’re done here right? Well, not quite…

What if a call is made to an endpoint that isn’t registered? Or if they send a CANCEL before the call is answered?

Let’s take a look at what happens if we try this:

SIP Cancel / Not Registered

The INVITE is received by Kamailio, a 100 TRYING response is sent back, but Kamailio isn’t really doing anything, after about 10 seconds I try and end the call from my softphone (CANCEL) and I get a 501 response back as we haven’t defined how to handle CANCEL messages, so there’s no way to end the call, and we’re stuck…

When things don’t go to plan…

As I said in the Introduction to Kamailio post, the power in Kamailio is the ability to define how every part functions, but to quote Spiderman – With great power comes great responsibility, and Peter Parker didn’t have the 269 pages of RFC3261 to grapple with…

We know the above example works fine when there’s a device registered on the other end and if they answer, but if they don’t? As we’ve seen, we’ve got to cater for every scenario.

Handling no registered user

Instead of just lookup(“location”) to lookup location we can put it in an if statement with some code to be run if it fails.

We’ll use the the if(!lookup(“location”)){ } call to create a new conditional for if lookup location fails, and inside it we can reply with a 404 and exit.

The if(!){} block can be used to try and run a command, and if it fails, it’ll run what’s between the {}.

In our scenario we try and lookup the location, if that fails, for example because we don’t have an Address on Record for that destination, it executes what’s between the {} in this case we use sl_reply to send a 404 error back and then exit:

    if(method=="INVITE"){
            if(!lookup("location")){
                            sl_reply("404", "User not Registered");
                            exit;
            }

            lookup("location");
            t_relay();
            exit();
    }

So now if an INVITE is received to a destination we can’t find via a lookup(“location”); we respond with a 404 and exit.

Sending an INVITE to a destination that isn't registered leading to a 404 being sent to the requester.

Handling CANCEL (Calls terminated before answered)

So now we’ve specified how we’ll handle calling a device that isn’t registered, next we have to specify how we deal with CANCEL messages, so if the calling party hangs up (cancels) the call before it’s answered by the called party, it stops ringing the called party.

CANCEL is a bit of a headache. – Here’s why:

CANCEL is treated differently to a lot of in-dialog messages. Let’s take a look at the IETF’s example call flows & best practices example:

Note that the CANCEL message is acknowledged with a 200 OK on a hop by hop basis, rather than end to end.


RFC 3665 – SIP Basic Call Flow Examples – Unsuccessful No Answer

This means our SIP server has to get the CANCEL message from the caller who’s given up, respond with it with a 200 OK to the caller, before forwarding that CANCEL onto the next hop (the user we were trying to call).

By this point you’re probably dreading your work ahead of you as you try and understand how to handle every scenario, every eventuality and all the weird gotchas like CANCEL.

If only there was someone else that had done this before…

“Homer the Vigilante” Season 5 / Episode 11

Luckily the Kamailio team have got pretty great examples in the kamailio.cfg file that ships by default (that one we ruthlessly gutted to keep it simple in the second tutorial) and now is a good time to re-introduce some of these routing blocks, that are reused in most Kamailio instances.

The boilerplate that gets you up and running faster and means you don’t have to worry about quite as many possible scenarios or read about how to handle CANCELS ad nauseam.

In the next tutorial we’ll talk about these routing blocks, and add them to our code to manage scenarios like CANCEL, timeouts to the remote destination, and how you can use these blocks to speed things up and keep everything RFC compliant.

Next Post – Kamailio 101 – Tutorial 6| Kamailio 101 – Tutorial 5 – First Call| Kamailio 101 – Tutorial 4- Taking Registrations

Other posts in the Kamailio 101 Series:
Kamailio 101 – Tutorial 1 – Introduction

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap