Tag Archives: SIP

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.

IMS / VoLTE IPsec on the Gm Interface

For most Voice / Telco engineers IPsec is a VPN technology, maybe something used when backhauling over an untrusted link, etc, but voice over IP traffic is typically secured with TLS and SRTP.

IMS / Voice over LTE handles things a bit differently, it encapsulates the SIP & RTP traffic between the UE and the P-CSCF in IPsec Encapsulating Security Payload (ESP) payloads.

In this post we’ll take a look at how it works and what it looks like.

It’s worth noting that Kamailio recently added support for IPsec encapsulation on a P-CSCF, in the IMS IPSec-Register module. I’ll cover usage of this at a later date.

The Message Exchange

The exchange starts off looking like any other SIP Registration session, in this case using TCP for transport. The UE sends a REGISTER to the Proxy-CSCF which eventually forwards the request through to a Serving-CSCF.

This is where we diverge from the standard SIP REGISTER message exchange. The Serving-CSCF generates a 401 Unauthorized response, containing an authentication challenge in the WWW-Authenticate header, and also a Ciphering Key & Integrity Key (ck= and ik=) also in the WWW-Authenticate header.

The Serving-CSCF sends the Proxy-CSCF the 401 response it created. The Proxy-CSCF assigns a SPI for the IPsec ESP to use, a server port and client port and indicates the used encryption algorithm (ealg) and algorithm to use (In this case HMAC-SHA-1-96.) and adds a new header to the 401 Unauthorized called SecurityServer header to share this information with the UE.

The Proxy-CSCF also strips the Ciphering Key (ck=) and Integrity Key (ik=) headers from the SIP authentication challenge (WWW-Auth) and uses them as the ciphering and integrity keys for the IPsec connection.

Finally after setting up the IPsec server side of things, it forwards the 401 Unauthorized response onto the UE.

Upon receipt of the 401 response, the UE looks at the authentication challenge.

Keep in mind that the 3GPP specs dictate that IMS / VoLTE authentication requires mutual network authentication meaning the UE authenticates the network as well as the network authenticating the UE. I’ve written a bit about mutual network authentication in this post for anyone not familiar with it.

If the network is considered authenticated by the UE it generates a response to the Authentication Challenge, but it doesn’t deliver it over TCP. Using the information generated in the authentication challenge the UE encapsulates everything from the network layer (IPv4) up and sends it to the P-CSCF in an IPsec ESP.

Communication between the UE and the P-CSCF is now encapsulated in IPsec.

Wireshark trace of IPsec IMS Traffic between UE and P-CSCF

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

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

Using Wireshark to peer inside IPsec ESP VoLTE data from the P-CSCF

IPsec ESP can be used in 3 different ways on the Gm interface between the Ue and the P-CSCF:

  • Integrity Protection – To prevent tampering
  • Ciphering – To prevent inception / eavesdropping
  • Integrity Protection & Ciphering

On Wireshark, you’ll see the ESP, but you won’t see the payload contents, just the fact it’s an Encapsulated Security Payload, it’s SPI and Sequence number.

By default, Kamailio’s P-CSCF only acts in Integrity Protection mode, meaning the ESP payloads aren’t actually encrypted, with a few clicks we can get Wireshark to decode this data;

Just open up Wireshark Preferences, expand Protocols and jump to ESP

Now we can set the decoding preferences for our ESP payloads,

In our case we’ll tick the “Attempt to detect/decode NULL encrypted ESP payloads” box and close the box by clicking OK button.

Now Wireshark will scan through all the frames again, anything that’s an ESP payload it will attempt to parse.

Now if we go back to the ESP payload with SQN 1 I showed a screenshot of earlier, we can see the contents are a TCP SYN.

Now we can see what’s going on inside this ESP data between the P-CSCF and the UE!

As a matter of interest if you can see the IK and CK values in the 401 response before they’re stripped you can decode encrypted ESP payloads from Wireshark, from the same Protocol -> ESP section you can load the Ciphering and Integrity keys used in that session to decrypt them.

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

SIP Supported & Require

On top of plain vanilla RFC3261, there’s a series of “Extension” methods added to SIP to expand it’s functionality, common extension methods are INFO, MESSAGE, NOTIFY, PRACK and UPDATE. Although now commonplace, of these is not defined in RFC3261 so is considered an “extension” to SIP.

It’s worth just pausing here to reiterate we’re not talking extensions like in a PBX context, like extra phones, we’re talking extensions like you’d add to a house, like extra functionality.

A SIP client can request functionality from a server (UAC to a UAS), if the server does not have support for that functionality, it can reject the session on those grounds and send back a response indicating it doesn’t know how to handle that extension, like a 420 Bad ExtensionBad SIP Protocol Extension used, not understood by the server. Response.

So clients can determine what functionality a server doesn’t support if it rejects the request, but there was no way to see what functionality the server does support, and what functionality the client requires.

Enter the Supported header, initially drafted by Rosenberg & Schulzrinne in 2000, it made it into the SIP we know today (SIP v2 / RFC3261).

If a UAC or UAS requires support for an extension – For example a Media Gateway has to understand PRACK, it can use the Require header to specify the request should be rejected if support for the listed extensions is not provided.

These headers are most commonly seen in SIP OPTIONS requests.

Kamailio Bytes – Dispatcher States

You may already be familiar with Kamailio’s Disptacher module, if you’re not, you can learn all about it in my Kamailio Bytes – Dispatcher Module post.

One question that’s not as obvious as it perhaps should be is the different states shown with kamcmd dispatcher.list command;

So what do the flags for state mean?

The first letter in the flag means is the current state, Active (A), Inactive (I) or Disabled (D).

The second letter in the flag means monitor status, Probing (P) meaning actively checked with SIP Options pings, or Not Set (X) denoting the device isn’t actively checked with SIP Options pings.

AP Actively Probing – SIP OPTIONS are getting a response, routing to this destination is possible, and it’s “Up” for all intents and purposes.

IPInactively Probing – Destination is not meeting the threshold of SIP OPTIONS request responses it needs to be considered active. The destination is either down or not responding to all SIP OPTIONS pings. Often this is due to needing X number of positive responses before considering the destination as “Up”.

DX Disabled & Not Probing – This device is disabled, no SIP OPTIONS are sent.

AX Active & Not Probing– No SIP OPTIONS are sent to check state, but is is effectively “Up” even though the remote end may not be reachable.

Kamailio Bytes – Rewriting SIP Headers (Caller ID Example)

Back to basics today,

In the third part of the Kamailio 101 series I briefly touched upon pseudovariables, but let’s look into what exactly they are and how we can manipulate them to change headers.

The term “pseudo-variable” is used for special tokens that can be given as parameters to different script functions and they will be replaced with a value before the execution of the function.

https://www.kamailio.org/wiki/cookbooks/devel/pseudovariables

You’ve probably seen in any number of the previous Kamailio Bytes posts me use pseudovariables, often in xlog or in if statements, they’re generally short strings prefixed with a $ sign like $fU, $tU, $ua, etc.

When Kamailio gets a SIP message it explodes it into a pile of variables, getting the To URI and putting it into a psudovariable called $tU, etc.

We can update the value of say $tU and then forward the SIP message on, but the To URI will now use our updated value.

When it comes to rewriting caller ID, changing domains, manipulating specific headers etc, pseudovariables is where it mostly happens.

Kamailio allows us to read these variables and for most of them rewrite them – But there’s a catch. We can mess with the headers which could result in our traffic being considered invalid by the next SIP proxy / device in the chain, or we could mess with the routing headers like Route, Via, etc, and find that our responses never get where they need to go.

So be careful! Headers exist for a reason, some are informational for end users, others are functional so other SIP proxies and UACs can know what’s going on.

Rewriting SIP From Username Header (Caller ID)

When Kamailio’s SIP parser receives a SIP request/response it decodes the vast majority of the SIP headers into a variety of pseudovariables, we can then reference these variables we can then reference from our routing logic.

Let’s pause here and go back to the Stateless SIP Proxy Example, as we’ll build directly on that.

Follow the instructions in that post to get your stateless SIP proxy up and running, and we’ll make this simple change:

####### 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("Received $rm to $ru - Forwarding");
        $fU = "Nick Blog Example";   #Set From Username to this value
        #Forward to new IP
        forward("192.168.1.110");

}

Now when our traffic is proxied the From Username will show “Nick Blog Example” instead of what it previously showed.

Pretty simple, but very powerful.

As you’ve made it this far might be worth familiarising yourself with the different types of SIP proxy – Stateless, Transaction Stateful and Dialog Stateful.

Load Testing with SIPp

I recently wrote a post on software-based transcoding limits on common virtualisation hardware.

To do this I needed to make a lot of calls, consistently, so as to generate some pretty graphs and stats.

To do this, I used SIPp (a performance testing tool for SIP) to simulate many concurrent calls leading to many concurrent transcoding sessions.

Installation

I built SIPp on Ubuntu 18.04:

apt-get install make autoconf pkg-config automake libncurses5-dev libpcap*
git clone https://github.com/SIPp/sipp.git
cd sipp/
./build.sh --with-rtpstream 
cp sipp /usr/local/bin/

Next I setup RTPengine and setup Kamailio to use it.

I modified the Kamailio config allow Transcoding, as I talked about in the post on setting up Transcoding in RTPengine with Kamailio.

Now I had a working Kamailio instance with RTPengine that was transcoding.

So the next step becomes testing the transcoding is working, for this I had two SIPp instances, one to make the calls and once to answer them.

Instance 1

Makes calls to the IP of the Kamailio / RTPengine instance, for this I modified the uac_pcap scenario to playback an RTP stream of a PCMA (G.711 a-law) call to the called party (stored in a pcap file), and made it call the Kamailio instance multiple times based on how many concurrent transcoding sessions I wanted:

sipp -m 120 -r 200 -sf uac_pcap.xml rtpenginetranscode.nickvsnetworking.com

Instance 2

Instance 2 acted as a simple SIP UAS, the call came in, it answered and echoed back the RTP stream it received.

sipp -rtp_echo -sf uas.xml

Kamailio Bytes – DMQ

We’ve talked about using a few different modules, like a SIP Registrar and Htable, that rely on data stored in Kamailio’s memory, the same is true for all the Stateful proxy discussion last week.

But what if you want to share this data between multiple Kamailio instances? This allows distributing workload and every server having the same information and therefore any server is able to process any request.

Enter DMQ the Distributed Messaging Queue,

This allows memory data to be shared between multiple Kamailio instances (aka “Nodes”), so for example if you are storing data in Htable on one Kamailio box, all the other boxes/nodes in the DMQ pool will have the same HTable data.

Kamailio uses SIP to transfer DMQ messages between DMQ nodes, and DNS to discover DMQ nodes.

For this example we’ll share user location data (usrloc) between Kamailio instances, so we’ll create a very simple setup to store location data:

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

        #Enable record_routing so we see the BYE / Re-INVITE etc

        if(is_method("REGISTER")){

                save("location");
        }else{
                sl_reply("500", "Nope");
        }


}

Now if we register a SIP endpoint we should be able to view it using Kamcmd’s ul.dump call, as we talked about in the Kamailio SIP Registrar tutorial.

Next we’ll setup DMQ to allow this data to be shared to other nodes, so they also have the same userloc data available,

First we’ll begin by binding to an extra port for the DMQ messages to go to, to make it a bit clearer what is normal SIP and what’s DMQ,

So for this we’ll add a new line in the config to listen on port 5090:

/* uncomment and configure the following line if you want Kamailio to
 * bind on a specific interface/port/proto (default bind on all available) */
listen=udp:0.0.0.0:5060
listen=tcp:0.0.0.0:5060
listen=udp:0.0.0.0:5090

Next we’ll load the DMQ modules and set them up:

loadmodule "dmq.so"
loadmodule "dmq_usrloc.so"

# ---- dmq params ----
modparam("dmq", "server_address", "sip:0.0.0.0:5090")
modparam("dmq", "notification_address", "sip:dmq.nickvsnetworking.com:5090")
modparam("dmq", "multi_notify", 1)
modparam("dmq_usrloc", "enable", 1)

The server_address means we’re listening on any IP on port 5090. In production you may have an IP set here of a private NIC or something non public facing.

The notification address resolves to 2x A records, one is the IP of this Kamailio instance / node, the other is the IP of the other Kamailio instance / node, I’ve just done this in /etc/hosts

root@dmq1:/etc/kamailio# nslookup dmq.nickvsnetworking.com
 Server:         127.0.0.53
 Address:        127.0.0.53#53
 Non-authoritative answer:
 Name:   dmq.nickvsnetworking.com
 Address: 192.168.3.121
 Name:   dmq.nickvsnetworking.com
 Address: 192.168.3.116

Finally we’ll add some routing logic to handle the DMQ messages coming in on port 5090:

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


        if (is_method("KDMQ") && $Rp == 5090)
        {
                dmq_handle_message();
        }

        #Enable record_routing so we see the BYE / Re-INVITE etc

        if(is_method("REGISTER")){

                save("location");
        }else{
                sl_reply("500", "Nope");
        }


}

We’ll put the same config on the other Kamailio instance and restart Kamailio on both.

We can now check the DMQ node status to confirm they’re talking to each other.

root@dmq1:/etc/kamailio# kamcmd dmq.list_nodes
 {
         host: 192.168.3.116
         port: 5090
         resolved_ip: 192.168.3.116
         status: active
         last_notification: 0
         local: 0
 }
 {
         host: 192.168.3.121
         port: 5090
         resolved_ip: 192.168.3.121
         status: active
         last_notification: 0
         local: 0
 }
 {
         host: 0.0.0.0
         port: 5090
         resolved_ip: 0.0.0.0
         status: active
         last_notification: 0
         local: 1
 }

Bingo they are,

Now if I register to one of these two instances, I can run a kamcmd ul.dump on either and they’ll both have the same data. Magic!

As always I’ve posted a copy of my code on GitHub here.

Kamailio Bytes – Transaction Module

We talked a little about the Transaction module and using it for Transaction Stateful SIP Proxy, but it’s worth knowing a bit more about the Transaction Module and the powerful functions it offers.

So today I’ll cover some cool functionality TM offers!

Different Reply Routes

By calling the t_on_reply(); we can specify the reply route to be used for replies in this transaction.

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");

        #Relay (aka Forward) the request
        t_relay_to_udp("192.168.3.118", "5060");

}

onreply_route[OurReplyRoute] {
        #On replies from route[RELAY]

        #Check our AVP we set in the initial request
        xlog("for $rs response the value of AVP \"state_test_var\" is $avp(state_test_var) ");

        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");

}

Any responses from the route[RELAY] routing block will go to onreply_route[OurReplyRoute], the beauty of this is it allows you to have multiple reply routes each with their own logic. For example for a call leg to a carrier you may want to preserve CLI, but for a call leg to a customer you may wish to restrict it if that’s the option the user has selected, and you can make these changes / modifications in the reply messages.

Failure Routes

Failure routes allow the transaction module to know to try again if a call fails, for example if no response is received from the destination, send it to a different destination, like a backup.

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");
        t_on_failure("OurFailureRoute");
        #Relay (aka Forward) the request
        t_relay_to_udp("192.168.1.118", "5060");

}

failure_route[OurFailureRoute]{
        xlog("At failure route");
        t_reply("500", "Remote end never got back to us");
        exit;
}

We can build upon this, and try a different destination if the first one fails:

request_route {

        #Enable record_routing so we see the BYE / Re-INVITE etc
        record_route();

        #Handle Registrations in a dumb way so they don't messy our tests
        if(is_method("REGISTER")){
                sl_reply("200", "ok");
                exit;
        }



                 #Append a header so we can see this was proxied in the SIP capture
                append_hf("X-Proxied: You betcha\r\n");

        if(is_method("INVITE")){
                        #Createa  new AVP called "state_test_var" and set the value to "I remember"
                        $avp(state_test_var) = "I remember";
        }
                #Let syslog know we've set the value and check it
                xlog("for $rm the value of AVP \"state_test_var\" is $avp(state_test_var) ");

                #Send to route[RELAY] routing block
                rewritehostport("nonexistentdomain.com");
                route(RELAY);

}

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");
        t_on_failure("OurFailureRoute");
        #Relay (aka Forward) the request
        t_relay();
}

failure_route[OurFailureRoute]{
        xlog("At failure route");
        #t_reply("500", "Remote end never got back to us");

                rewritehostport("192.168.3.118");
                append_branch();
                t_relay();

}

onreply_route[OurReplyRoute] {
        #On replies from route[RELAY]

        #Check our AVP we set in the initial request
        xlog("for $rs response the value of AVP \"state_test_var\" is $avp(state_test_var) ");

        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");

}

One thing to keep in mind is that there’s lots of definitions of failure, for example if you are sending a call to a carrier and get a 404 response back, you probably want to relay that through to the end user, because that destination isn’t there.

But if you get back a 5xx series response you may consider that to be a failure and select the next carrier for example.

Different conditions / requirements have different definitions of “failures” and so there’s a lot to think about when implementing this, along with timeouts for no replies, TCP session management, etc.

Parallel Forking the Call to Multiple Destinations

Parallel Forking is a fancy way of saying ring multiple destinations at the same time.

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

        #Enable record_routing so we see the BYE / Re-INVITE etc
        record_route();

        #Handle Registrations in a dumb way so they don't messy our tests
        if(is_method("REGISTER")){
                sl_reply("200", "ok");
                exit;
        }



                 #Append a header so we can see this was proxied in the SIP capture
                append_hf("X-Proxied: You betcha\r\n");

        if(is_method("INVITE")){
                        #Createa  new AVP called "state_test_var" and set the value to "I remember"
                        $avp(state_test_var) = "I remember";
        }
                #Let syslog know we've set the value and check it
                xlog("for $rm the value of AVP \"state_test_var\" is $avp(state_test_var) ");

                #Send to route[RELAY] routing block
                route(RELAY);

}

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");

        #Append branches for each destination we want to forward to
        append_branch("sip:[email protected]");
        append_branch("sip:[email protected]");
        append_branch("sip:[email protected]");

        t_on_failure("OurFailureRoute");
        #Relay (aka Forward) the request
        t_relay();
}

failure_route[OurFailureRoute]{
        xlog("At failure route");
        t_reply("500", "All those destinations failed us");

}

onreply_route[OurReplyRoute] {
        #On replies from route[RELAY]

        #Check our AVP we set in the initial request
        xlog("for $rs response the value of AVP \"state_test_var\" is $avp(state_test_var) ");

        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");

}

Bit of a mess but here we see the initial INVITE being branched to the 3 destinations at the same time (Parallel forking)

Serial Forking / Sequential Forking the calls to Multiple Destinations one after the Other

This could be used to try a series of weighted destinations and only try the next if the preceding one fails:

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

        #Enable record_routing so we see the BYE / Re-INVITE etc
        record_route();


        #Send to route[RELAY] routing block
        route(RELAY);

}

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");

        append_branch("sip:[email protected]", "0.3");
        append_branch("sip:[email protected]", "0.2");
        append_branch("sip:[email protected]", "0.1");

        t_load_contacts();

        t_next_contacts();

        t_on_failure("OurFailureRoute");
        #Relay (aka Forward) the request
        t_relay();
        break;
}

failure_route[OurFailureRoute]{

        xlog("At failure route - Trying next destination");
        t_on_failure("OurFailureRoute");
        t_relay();


}

onreply_route[OurReplyRoute] {
        #On replies from route[RELAY]
        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");

}

Again this will try each destination, but one after the other based on the weight we added to each destination in the append_branch()

Here we see each destination being tried sequentially

Transaction Stateful Proxy with Kamailio

We covered the 3 different types of SIP Proxy, Stateless, Transaction Stateful and Dialog Stateful.

It’s probably worth going back to have a read over the description of the types of proxy and have a read over the whole Stateless proxy we implemented in Kamailio, before we get started on Stateful proxies, because we’ll be carrying on from what we started there.

The Setup

The 3 different proxies all do the same thing, they all relay SIP messages, so we need a way to determine what state has been saved.

To do this we’ll create a variable (actually an AVP) in the initial request (in our example it’ll be an INVITE), and we’ll reference it when handling a response to make sure we’ve got transactional state.

We’ll also try and reference it in the BYE message, which will fail, as we’re only creating a Transaction Stateful proxy, and the BYE isn’t part of the transaction, but in order to see the BYE we’ll need to enable Record Routing.

Stateless Proof

Before we add any state, let’s create a working stateless proxy, and add see how it doesn’t remember:

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

        #Enable record_routing so we see the BYE / Re-INVITE etc
        record_route();

        #Handle Registrations in a dumb way so they don't messy our tests
        if(is_method("REGISTER")){
                sl_reply("200", "ok");
                exit;
        }



                 #Append a header so we can see this was proxied in the SIP capture
                append_hf("X-Proxied: You betcha\r\n");

        if(is_method("INVITE")){
                        #Createa  new AVP called "state_test_var" and set the value to "I remember"
                        $avp(state_test_var) = "I remember";
        }
                #Let syslog know we've set the value and check it
                xlog("for $rm the value of AVP \"state_test_var\" is $avp(state_test_var) ");

                #Forard to new IP
                forward("192.168.3.118");



}

onreply_route{
        #Check our AVP we set in the initial request
        xlog("for $rs response the value of AVP \"state_test_var\" is $avp(state_test_var) ");

        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");
}

Now when we run this and call from any phone other than 192.168.3.118, the SIP INVITE will hit the Proxy, and be forwarded to 192.168.3.118.

Syslog will show the INVITE and us setting the var, but for the replies, the value of AVP $avp(state_test_var) won’t be set, as it’s stateless.

Let’s take a look:

kamailio[2577]: {1 1 INVITE [email protected]} ERROR: : for INVITE the value of AVP "state_test_var" is I remember
kamailio[2575]: {2 1 INVITE [email protected]} ERROR: <script>: for 100 response the value of AVP "state_test_var" is <null>
kamailio[2576]: {2 1 INVITE [email protected]} ERROR: <script>: for 180 response the value of AVP "state_test_var" is <null>
kamailio[2579]: {2 1 INVITE [email protected]} ERROR: <script>: for 200 response the value of AVP "state_test_var" is <null>
kamailio[2580]: {1 1 ACK [email protected]} ERROR: <script>: for ACK the value of AVP "state_test_var" is <null>
kamailio[2581]: {1 2 BYE [email protected]} ERROR: <script>: for BYE the value of AVP "state_test_var" is <null>

We can see after the initial INVITE none of the subsequent replies knew the value of our $avp(state_test_var), so we know the proxy is at this stage – Stateless.

I’ve put a copy of this code on GitHub for you to replicate yourself.

Adding State

Doing the heavy lifting of our state management is the Transaction Module (aka TM). The Transaction Module deserves a post of it’s own (and it’ll get one).

We’ll load the TM module (loadmodule “tm.so”) and use the t_relay() function instead of the forward() function.

But we’ll need to do a bit of setup around this, we’ll need to create a new route block to call t_relay() from (It’s finicky as to where it can be called from), and we’ll need to create a new reply_route{} to manage the response for this particular dialog.

Let’s take a look at the code:

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

        #Enable record_routing so we see the BYE / Re-INVITE etc
        record_route();

        #Handle Registrations in a dumb way so they don't messy our tests
        if(is_method("REGISTER")){
                sl_reply("200", "ok");
                exit;
        }



                 #Append a header so we can see this was proxied in the SIP capture
                append_hf("X-Proxied: You betcha\r\n");

        if(is_method("INVITE")){
                        #Createa  new AVP called "state_test_var" and set the value to "I remember"
                        $avp(state_test_var) = "I remember";
        }
                #Let syslog know we've set the value and check it
                xlog("for $rm the value of AVP \"state_test_var\" is $avp(state_test_var) ");

                #Send to route[RELAY] routing block
                route(RELAY);

}

route[RELAY]{

        #Use reply route "OurReplyRoute" for responses for this transaction
        t_on_reply("OurReplyRoute");

        #Relay (aka Forward) the request
        t_relay_to_udp("192.168.3.118", "5060");

}

onreply_route[OurReplyRoute] {
        #On replies from route[RELAY]

        #Check our AVP we set in the initial request
        xlog("for $rs response the value of AVP \"state_test_var\" is $avp(state_test_var) ");

        #Append a header so we can see this was proxied in the SIP capture
        append_hf("X-Proxied: For the reply\r\n");

}

So unlike before where we just called forward(); to forward the traffic we’re now calling in a routing block called RELAY.

Inside route[RELAY] we set the routing block that will be used to manage replies for this particular transaction, and then call t_relay_to_udp() to relay the request.

We renamed our onreply_route to onreply_route[OurReplyRoute], as specified in the route[RELAY].

So now let’s make a call (INVITE) and see how it looks in the log:

kamailio[5008]: {1 1 INVITE [email protected]} ERROR: : for INVITE the value of AVP "state_test_var" is I remember
kamailio[5005]: {2 1 INVITE [email protected]} ERROR: <script>: for 100 response the value of AVP "state_test_var" is I remember
kamailio[5009]: {2 1 INVITE [email protected]} ERROR: <script>: for 180 response the value of AVP "state_test_var" is I remember
kamailio[5011]: {2 1 INVITE [email protected]} ERROR: <script>: for 200 response the value of AVP "state_test_var" is I remember
kamailio[5010]: {1 1 ACK [email protected]} ERROR: <script>: for ACK the value of AVP "state_test_var" is <null>
kamailio[5004]: {1 2 BYE [email protected]} ERROR: <script>: for BYE the value of AVP "state_test_var" is <null>
kamailio[5007]: {2 2 BYE [email protected]} ERROR: <script>: for 200 response the value of AVP "state_test_var" is <null>

Here we can see for the INVITE, the 100 TRYING, 180 RINGING and 200 OK responses, state was maintained as the variable we set in the INVITE we were able to reference again.

(The subsequent BYE didn’t get state maintained because it’s not part of the transaction.)

And that’s it.

Here’s a copy of my running code on GitHub.

Hopefully now you’ve got an idea of the basics of how to implement a transaction stateful application in Kamailio.

SIP SDP – ptime

ptime is the packetization timer in VoIP, it’s set in the SDP message and defines the length of each RTP packet that’s sent;

This gives the length of time in milliseconds represented by the media in a packet. This is probably only meaningful for audio data, but may be used with other media types if it makes sense. It should not be necessary to know ptime to decode RTP or vat audio, and it is intended as a recommendation for the encoding/packetisation of audio. It is a media-level attribute, and it is not dependent on charset.

RFC 4556 – SDP: Session Description Protocol, Section 6
SDP body showing ptime value of 20ms

What it’s all about

A lower ptime value leads to more packet per second, while longer ptime leads to fewer packets per second.

In a Toll Quality (TDM) network 8000 samples per second are taken, this is reflected in PCM (Pulse Code Modulation) encoding of the data, see in PCMA / G.711 a-law for example.

But if each of these 8,000 samples per second were sent on an individual packet, we’d be seeing a huge number of tiny RTP packets where the header is a lot larger than the payload.

Instead endpoints generally wait until they’ve got a certain number of theses samples and then send them at once, every X milliseconds as defined by the ptime value.

  • A ptime of 1000ms would mean 1 packet per second.
  • A ptime of 20ms would mean 50 packets per second.
  • A ptime of 50ms would mean 20 packets per second.

ptime headaches

Some VoIP endpoints have issues with varied ptime (*cough Cisco SPA series cough*), and if you’re interconnecting with other carrier networks you have no real control as to what ptime endpoints use (except if you have a B2Bua that can resample / restuff the packets, or you use maxptime which really just limits more than fixes) so it’s worth understanding well.

International carrier trunks often have higher ptime values as they're often dealing with lower quality links, so they want to cut down the packets per second and often have jitter buffers in place to compensate for poor quality links.

RFC4566 (the second version of SDP) introduced the maxptime value.

This optional header in the SDP body allows an endpoint to specify the maximum ptime value it supports.

Older endpoints often don’t have much memory or processing power, so have very small buffers to store the received audio in before playing it to the user, and store the audio to be transmitted before sending it down the wire.

Mismatched ptime or a ptime that’s out of bounds for one endpoint can lead to some strange issues. Often an endpoint will ring, answer the call and even get a 200 OK, but immediately followed by a BYE from the incompatible end instead of an ACK.

In the initial INVITE ptime is not mandatory, meaning you may not know the caller has limits to the ptime values they can support, and the endpoint hangs up the calls straight after the 200 OK.

Identifying these issues may take some time, but here’s some good places to look:

  • SDP ptime value on INVITE and 200 OK
  • Time between RTP packets
  • Timestamp difference between RTP packets

Although it seems pretty self evident, if your endpoint only supports up to 20ms ptime, set the maxptime header to 20ms. You’d be surprised how often this isn’t the case.

You can read more about SDP on my Overview of SDP post and the RFC – RFC4566. You can lean about manipulating SDP headers in Kamailio in my post on SDPops.

Kamailio vs Asterisk

One of the most searched keywords that leads to this site is Kamailio vs Asterisk, so I thought I’d expand upon this a bit more as I’m a big fan of both, and it’s somewhat confusing.

(Almost everything in this post I talk about on Asterisk is roughly true for FreeSWITCH as well, although FS is generally more stable and scalable than Asterisk. )

Asterisk

Asterisk is a collection of PBX / softswitch components that you can configure and put together to create a large number of different products with the use of config files and modules.

Asterisk can read and write the RTP media stream, allowing it to offer services like Voicemail, B2B-UA, Conferencing, Playing back audio, call recording, etc.

It’s easy to learn and clear to understand how it handles “calls”.

Kamailio

Kamailio is a SIP proxy, from which you can modify SIP headers and then forward them on or process them and generate a response.

Kamailio is unable to do manipulate the RTP media stream. It can’t listen to, modify or add to the call audio, it only cares about SIP and not the media stream. This means it can’t playback an audio file, record a call or serve voicemail.

Kamailio has a bit of a steep learning curve, which I’ve tried to cover in my Kamailio 101 series, but even so, Kamailio doesn’t understand the concept of a “call”, it deals in Sessions, as in SIP, and everything you want to do, you have to write into Kamailio’s logic. Awesome power but a lot to take in.

Note – RTPengine is growing in capabilities and integrates beautifully into Kamailio, so for some applications you may be able to use RTPengine for media handling.

ScaleSpeedStabilityMedia FunctionsEase
AsteriskXX
KamailioXXX

Working Together

Asterisk has always had issues at scale. This is for a variety of reasons, but the most simplistic explanation is that Asterisk is fairly hefty software, and that each subscriber you add to the system consumes resources at a rate where once your system reaches a few hundred users you start to see issues with stability.

Kamailio works amazingly at scale, it’s architecture was designed with running at scale in mind, and it’s super lightweight footprint means the load on the box between handling 1,000 sessions and handling 100,000 sessions isn’t that much.

Because Asterisk has the feature set, and Kamailio has the scalability, so the the two can be used together really effectively. Let’s look at some examples of Asterisk and Kamailio working together:

Asterisk Clustering

You have a cluster of Asterisk based Voicemail servers, serving your softswitch environment. You can use a Kamailio instance to sit in front of them and route INVITEs evenly throughout the cluster of Asterisk instances.

You’d be using Asterisk’s VM functions (because Asterisk can do media functions) and Kamailio’s SIP routing functions.

Here’s an example of Kamailio Dispatcher acting in this function.

Application Server for SIP Softswitch

You have a Kamailio based Softswitch that routes SIP traffic from customers to carriers, customers want a hosted Conference Bridge. You offer this by routing any SIP INVITES to the address of the conference bridge to an Asterisk server that serves as the conference bridge.

You’d be using Kamialio to route the SIP traffic and using Asterisk’s ability to be aware of the media stream and join several sources to offer the conference bridge.

Which should I use?

It all depends on what you need to do.

If you need to do anything with the audio stream you probably need to use something like Asterisk, FreeSwitch, YaTE, etc, as Kamailio can’t do anything with the audio stream.*

If it’s just signalling, both would generally be able to work, Asterisk would be easier to setup but Kamailio would be more scaleable / stable.

Asterisk is amazingly quick and versatile when it comes to solving problems, I can whip something together with Asterisk that’ll fix an immediate need in a faction of the time I can do the same thing in Kamailio.

On the other hand I can fix a problem with Kamailio that’ll scale to hundreds of thousands of users without an issue, and be lightning fast and rock solid.

Summary

Kamailio only deals with SIP signalling. It’s very fast, very solid, but if you need to do anything with the media stream like mixing, muxing or transcoding (RTP / audio) itself, Kamailio can’t help you.*

Asterisk is able to deal with the media stream, and offer a variety of services through it’s rich module ecosystem, but the trade-off is less stability and more resource intensive.

If you do require Asterisk functionality it’s worth looking into FreeSWITCH, although slightly harder to learn it’s generally regarded as superior in a lot of ways to Asterisk.

I don’t write much about Asterisk these days – the rest of the internet has that pretty well covered, but I regularly post about Kamailio and other facets of SIP.

Kamailio Bytes – SDP Manipulation with SDPops

I’m not a fan of Transcoding. It costs resources, often leads to reduced quality and adds latency.

Through some fancy SDP manipulating footwork we can often rejig the SDP order or limit the codecs we don’t support to cut down, or even remove entirely, the need for transcoding in the network.

If you’re not yet familiar with SDP have a read over my post on SDP Overview.

Modparam

There are no module parameters for SDP ops, we’ve just got to load the module with loadmodule “sdpops.so”

Use in Routing Logic

We’ll pickup where we left off on the Basic Stateless SIP Proxy use case (You can grab the basic code from that post), but this time we’ll remove PCMU (Aka G.711 μ-law) from the SDP body:

loadmodule "sdpops.so"


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

if(is_method("REGISTER")){
        sl_reply("200", "Ok");
}

        xlog("Received $rm to $ru - Forwarding");

        append_hf("X-Proxied: You betcha\r\n");

        #Remove PCMU (G.711 u-law) by it's SDP Payload ID
        sdp_remove_codecs_by_id("0");

        #Remove PCMU by name
        sdp_remove_codecs_by_name("PCMU");


        #Forard to new IP
        forward("192.168.3.110");

}

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

We can remove the codec either by it’s name (PCMU) or by it’s payload ID.

Before traversing the Proxy
After traversing the proxy

For removing it by name we just specify the name:

#Remove PCMU by name
sdp_remove_codecs_by_name("PCMU");

And by payload ID:

#Remove PCMU (G.711 u-law) by it's SDP Payload ID
sdp_remove_codecs_by_id("0");

We may want to remove all but one codec, again super simple:

#Only keep PCMA codec
sdp_keep_codecs_by_name("PCMA");

If you can’t help but transcode RTPengine now has this functionality, have a read of Transcoding with RTPengine and Kamailio and it may be worth looking over Virtualized Transcoding Dimensioning for an idea of how powerful your box is going to have to be.

SIP Extensions – Path

In vanilla RFC3261 SIP, a UA can only send a REGISTER request to a SIP Registrar.

It can’t go via any intermediary proxies.

That’s obviously a bit of a problem, as we build out our network we might have a series of load balancers that send traffic to a pool of Registrars, but according to RFC3261 this can’t be done, the SIP REGISTER request would need to go direct to one of these Registrars.

To get around this the SIP Path extensions, officially called “Session Initiation Protocol (SIP) Extension Header Field for Registering Non-Adjacent Contacts” (catchy title) was defined under RFC 3327.

An additional header is introduced called “Path:” for each proxy between the UA and the Registrar,

As the SIP REGISTER request passes through each proxy, each proxy appends the Path header with the value of it’s own SIP URI.

Let’s take a look at an example call flow from [email protected] who sends his REGISTER to atlanta.com, which is proxied by atlanta.com to registrar1.atlanta.com:

Bob to atlanta.com:

[email protected] > atlanta.com
      REGISTER sip:atlanta.com SIP/2.0
      Via: SIP/2.0/UDP 192.0.2.4:5060;branch=z9hG4bKnashds7
      To: Bob <sip:[email protected]>
      From: Bob <sip:[email protected]>;tag=456248
      Call-ID: 843817637684230@998sdasdh09
      CSeq: 1826 REGISTER
      Contact: <Bob <sip:[email protected]>>
      Supported: path

The REGISTER request is received by atlanta.com, which forwards it to registrar1.atlanta.com after adding it’s own URI as a Path header.

atlanta.com > registrar1.atlanta.com
      REGISTER sip:atlanta.com SIP/2.0
      Via: SIP/2.0/UDP atlanta.com;branch=z9hG4bK34ghi7ab04
      Via: SIP/2.0/UDP 192.0.2.4:5060;branch=z9hG4bKnashds7
      To: Bob <sip:[email protected]>
      From: Bob <sip:[email protected]>;tag=456248
      Call-ID: 843817637684230@998sdasdh09
      CSeq: 1826 REGISTER
      Contact: <Bob <sip:[email protected]>>
      Supported: path
      Path: <sip:atlanta.com;lr>

The response is sent back in the same way, relying on the via headers and Path headers.

Virtualized Transcoding Dimensioning

A seemingly simple question is how many concurrent calls can a system handle.

Sadly the answer to that question is seldom simple and easy to say, even more so when we talk about transcoding.

Transcoding is the process of taking a media stream encoded in one codec (format) and transferring it to a different codec (hence trans-coding).

This can be a very resource intensive process, so there’s a large number of hardware based solutions (PCI cards / network devices) that use FGPAs and clever processor arrangements to handle the transcoding. These products are made by a multitude of different vendors but are generally called hardware transcoders.

Today we’ll talk a bit about software based transcoding, and how many concurrent calls you can transcode on common VM configurations.

These stats will translate fairly well to their dedicated hardware counterparts, but a VM provides us with a consistent hardware environment so makes it a bit easier.

For these tests I created the baseline VM to run in VMWare Workstation with the below settings:

We’ll be transcoding using RTPengine, which recently added transcoding capabilities, so I set that up as per my post on setting up RTPengine for Transcoding.

Next I setup some SIPp scenarios to simulate call loads, from G.711 a-law to G.711 u-law (the simplest of transcoding (well re-compounding)) and used glances to get the max CPU usage and logged the results.

PCMA to PCMU (Re-companding)

PCMA to PCMU

RTPengine fared significantly better than I expected, I stopped at 150 concurrent transcoding sessions as that’s when call quality was really starting to degrade, but I was still achieving MOS of 4.3+ up to 130 concurrent sessions.

For what I needed to do, running this in a virtualised environment allowed 150 transcoding sessions before the MOS started to drop and call quality was adversely affected. Either way I was pretty amazed at how efficiently RTPengine managed to handle this.

Transcoding from one codec to a different codec was a different matter, and I’ll post the results from that another day.

If you want to learn more about RTPengine have a read of my other posts on RTPengine, that cover Installing and configuring RTPengine, using RTPengine with Kamailio, transcoding with RTPengine and scaling with RTPengine over geographic areas.

Kamailio Bytes – Stateless SIP Proxy

We’ve talked a bit in the Kamailio Bytes series about different modules we can use, but I thought it’d be useful to talk about setting up a SIP Proxy using Kamailio, and what’s involved in routing a message from Host A to Host B.

In the past I’ve talked about the 3 types of SIP proxies out there, stateless, dialog stateful and transaction stateful.

Proxying the initial Request

When we talk about proxying for the most part we’re talking about forwarding, let’s look at the steps involved:

  1. Our Kamailio instance receives a SIP request (for simplicity we’ll assume an INVITE).
  2. Kamailio looks at it’s routing logic to lookup where to forward the request to. You could find out where to send the request to from a lot of different sources, you could consult the Dialplan Module or Dispatcher Module, perform an SQL lookup, consult UsrLoc table to find a AoR, etc.
  3. Add it’s own Via header (more on that later)
  4. Forward the Request (aka Proxy it) to the destination selected

Let’s take a look at a very simple way we can do this with two lines in Kamailio to forward any requests to 192.168.1.110:

####### 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("Received $rm to $ru - Forwarding");

        #Forard to new IP
        forward("192.168.1.110");

}

After we restart Kamailio and send a call (INVITE) to it let’s see how it handles it:

 192.168.1.75.5060 > 192.168.1.221.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
 192.168.1.221.5060 > 192.168.1.110.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
 192.168.1.110.5060 > 192.168.1.221.5060: SIP: SIP/2.0 100 Trying
 192.168.1.221.5060 > 192.168.1.75.5060: SIP: SIP/2.0 100 Trying
 192.168.1.110.5060 > 192.168.1.221.5060: SIP: SIP/2.0 180 Ringing
 192.168.1.221.5060 > 192.168.1.75.5060: SIP: SIP/2.0 180 Ringing

Presto, our request was proxied (forwarded).

Let’s make a small modification, we’ll add a header called “X-Proxied” to the request before we forward it.

####### 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("Received $rm to $ru - Forwarding");

        append_hf("X-Proxied: You betcha\r\n");
        #Forard to new IP
        forward("192.168.1.110");

}

On the wire the packets still come from the requester, to the Proxy (Kamailio) before being forwarded to the forward destination (192.168.1.110):

We’ve now got a basic proxy that takes all requests to the proxy address and forwards it to an IP Address.

If you’re very perceptive you might have picked up the fact that the in-dialog responses, like the 100 Trying, the 180 Ringing and the 200 Ok also all went through the proxy, but if you look at syslog you’ll only see the initial request.

/usr/sbin/kamailio: Received INVITE to sip:[email protected]:5060 - Forwarding

So why didn’t we hit that xlog() route and generate a log entry for the replies?

But before we can talk too much about managing replies, let’s talk about Via…

It’s all about the Via

Before we can answer that question let’s take a look at Via headers.

The SIP Via header is added by a proxy when it forwards a SIP message onto another destination,

When a response is sent the reverse is done, each SIP proxy removes their details from the Via header and forwards to the next Via header along.

SIP Via headers in action
SIP Via headers in action

As we can see in the example above, each proxy adds it’s own address as a Via header, before it uses it’s internal logic to work out where to forward it to, and then forward on the INVITE.

Now because all our routing information is stored in Via headers when we need to route a Response back, each proxy doesn’t need to consult it’s internal logic to work out where to route to, but can instead just strip it’s own address out of the Via header, and then forward it to the next Via header IP Address down in the list.

SIP Via headers in action

Via headers are also used to detect looping, a proxy can check when it receives a SIP message if it’s own IP address is already in a Via header, if it is, there’s a loop there.

Managing Responses in Kamailio

By default Kamailio manages responses by looking at the Via header, if the top Via header is its own IP address, it strips it’s own Via header and forwards it onto the next destination in the Via header.

We can add our own logic into this by adding a new route called onreply_route{}

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

Now we’ll create a log entry with the response code in syslog for each response we receive, and we’ll add a header on the replies too:

Recap

A simple proxy to forward INVITEs is easy to implement in Kamailio, the real tricky question is what’s the logic involved to make the decision,

I’ve put a copy of the running code for this on GitHub.

Kamailio Use Case – SIP Honeypot with SQL Database

In my last few Kamailio Bytes posts, I’ve talked about using the GeoIP2 module to lookup the location of IP Addresses and SQLops and db_mysql to work with relational databases from within Kamailio.

Now we’ll put both together to create something functional you could use in your own deployments. (You’d often find it’s faster to use HTable to store and retrieve data like this, but that’s a conversation for another day)

The Project

We’ll build a SIP honeypot using Kamailio. It’ll listen on a Public IP address for SIP connections from people scanning the internet with malicious intent and log their IPs, so our real SIP softswitches know to ignore them.

We’ll use GeoIP2 to lookup the location of the IP and then store that data into a MySQL database.

Lastly we’ll create a routing block we can use on another Kamailio instance to verify if that the IP address of the received SIP message is not in our blacklist by searching the MySQL database for the source IP.

The Database

In this example I’m going to create a database called “blacklist” with one table called “baddies”, in MySQL I’ll run:

CREATE database blacklist;
CREATE TABLE `baddies` (
	`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`ip_address` INT unsigned UNIQUE,
	`hits` INT,
	`last_seen` DATETIME,
	`ua` TEXT,
	`country` TEXT,
	`city` TEXT
);

I’ll setup a MySQL user to INSERT/UPDATE/SELECT data from the MySQL database.

For storing IP addresses in the database we’ll store them as unsigned integers, and then use the INET_ATON('127.0.0.1') MySQL command to encode them from dotted-decimal format, and the INET_NTOA('2130706433') to put them back into dotted decimal.

Modparams

Now we’ll need to configure Kamailio, I’ll continue on from where we left off in the last post on GeoIP2 as we’ll use that to put Geographic data about the IP before adding the MySQL and SQLOps modules:

# ----- SQL params -----
loadmodule "db_mysql.so"
loadmodule "sqlops.so"

#Create a new MySQL database connection called blacklist_db 
modparam("sqlops","sqlcon","blacklist_db=>mysql://root:yourpassword@localhost/blacklist")  

#Set timeouts for MySQL Connections
modparam("db_mysql", "ping_interval", 60)
modparam("db_mysql", "auto_reconnect", 1)
modparam("db_mysql", "timeout_interval", 2)

After loading db_mysql and sqlops we create a new object / connection called blacklist_db with our MySQL Database parameters.

Now after a restart we’ll be connected to our MySQL database.

Honeypot Routing Logic

Now we’ll create a route to log the traffic:

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

        route(AddToBlacklist);
        sl_reply('200', 'Sure thing boss!');

}
route[AddToBlacklist]{
        xlog("Packet received from IP $si");
        sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (2130706433, 10, NOW(), 'testua2', 'Australia', 'Hobart');");
}

Now for each SIP message received a new record will be inserted into the database:

 root@ip-172-31-8-156:/etc/kamailio# mysql -u root -p blacklist -e "select * from baddies;"
 Enter password:
 +----+------------+------+---------------------+---------+-----------+--------+
 | id | ip_address | hits | last_seen           | ua      | country   | city   |
 +----+------------+------+---------------------+---------+-----------+--------+
 |  1 | 2130706433 |   10 | 2019-08-13 02:52:57 | testua2 | Australia | Hobart |
 |  2 | 2130706433 |   10 | 2019-08-13 02:53:01 | testua2 | Australia | Hobart |
 |  3 | 2130706433 |   10 | 2019-08-13 02:53:05 | testua2 | Australia | Hobart |
 +----+------------+------+---------------------+---------+-----------+--------+

This is great but we’re not actually putting the call variables in here, and we’ve got a lot of duplicates, let’s modify our sql_xquery() to include the call variables:

sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 10, NOW(), '$ua', 'Australia', 'Hobart');");

Now we’re setting the IP Address value to the Source IP psedovariable ($si) and formatting it using the INET_ATON function in MySQL, setting the last_seen to the current timestamp and setting the user agent to the User Agent psedovariable ($ua).

Let’s restart Kamailio, truncate the data that’s currently in the DB, send some SIP traffic to it and then check the contents:

mysql -u root -p blacklist -e "select *, INET_NTOA(ip_address) from baddies;"

Here you can see we’re starting to get somewhere, the IP, UA and last_seen values are all now correct.

We’re getting multiple entries from the same IP though, instead we just want to increment the hits counter and set the last_seen to the current time, for that we’ll just update the SQL query to set the time to be NOW() and if that IP is already in the database to update the last_seen value and incriment the hits counter:

sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', 'Australia', 'Hobart') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;");

Now we’ve only got one line per IP address, with the hit counters showing how many SIP requests we’ve received from that IP.

Finally we’ll use the GeoIP2 Module to get the geographic info of the source of the traffic, like we’ve talked about in the previous post:

route[AddToBlacklist]{
        xlog("Packet received from IP $si");
        geoip2_match("$si", "src"))
        sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '$gip2(src=>cc)', '$gip2(src=>city)') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");

}

The only issue with this is if GeoIP2 doesn’t have a match, no record will be added in the database, so we’ll add a handler for that:

route[AddToBlacklist]{
        xlog("Packet received from IP $si");
        if(geoip2_match("$si", "src")){
                sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '$gip2(src=>cc)', '$gip2(src=>city)') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");
        }else{          ##If no match in GeoIP2 leave Country & City fields blank
                sql_xquery("blacklist_db", "insert into baddies (ip_address, hits, last_seen, ua, country, city) values (INET_ATON('$si'), 1, NOW(), '$ua', '', '') ON DUPLICATE KEY UPDATE last_seen = NOW(), hits = hits + 1;", "r_sql");
        }
}

Now let’s check our database again and see how the data looks:

mysql -u root -p blacklist -e "select *, INET_NTOA(ip_address) from baddies;" 

Perfect! Now we’re inserting data into our blacklist from our honeypot. Now we’ll configure a new routing block we can use on another Kamailio instance to see if an IP is in the blacklist.

I left this running on my AWS box for a few hours, and lots of dodgy UAs dropped in to say hello, one of which was very insistent on making calls to Poland…

Querying the Data

Now we’ve got a blacklist it’s only useful if we block the traffic from our malicous actors who we’ve profiled in the database.

You could feed this into BGP to null route the traffic, or hook this into your firewall’s API, but we’re going to do this in Kamailio, so we’ll create a new routing block we can use on a different Kamailio instance – Like a production one – to see if the IP it just received traffic from is in the blacklist.

We’ve already spoken about querying databases in the SQLops Kamailio bytes, but this routing block will query the blacklist database, and if the sender is in the database, one or more records will be returned, so we know they’re bad and will drop their traffic:

route[CheckBlacklist]{

        xlog("Checking blacklist for ip $si");

        #Define a variable containing the SQL query we'll run
        $var(sql) = "select INET_NTOA(ip_address) as ip_address from baddies;";

        #Log the SQL query we're going to run to syslog for easy debugging
        xlog("Query to run is $var(sql)");

        #Query blacklist_db running the query stored in $var(sql) and store the result of the query to result_sql
        sql_query("blacklist_db", "$var(sql)", "result_sql");

        #If more than 0 records were returned from the database, drop the traffic
        if($dbr(result_sql=>rows)>0){
                xlog("This guy is bad news. Dropping traffic from $si");
                exit;
        }else{
                xlog("No criminal record for $si - Allowing to progress");
        }


}

Recap

We’d previously touched on using the GeoIP2 module to lookup the location of IP Addresses and SQLops and db_mysql to work with relational databases from within Kamailio.

This Honeypot use case just put those elements together.

In reality a far better implementation of this would use HTable to store this data, but hopefully this gives you a better understanding of how to actually work with data.

Final Note

I wrote this post about a week ago, and left the config running on an AWS box. I was getting hits to it within the hour, and in the past week I’ve had 172 IPs come and say hello, and some like the FriendlyScanner instance at 159.65.220.215 has sent over 93,000 requests:

mysql> select *, INET_NTOA(ip_address) from baddies;
 +--------+------------+-------+---------------------+----------------------------+---------+---------------------+-----------------------+
 | id     | ip_address | hits  | last_seen           | ua                         | country | city                | INET_NTOA(ip_address) |
 +--------+------------+-------+---------------------+----------------------------+---------+---------------------+-----------------------+
 |      1 | 3070413354 |    23 | 2019-08-23 14:15:40 | friendly-scanner           | CN      | Guangzhou           | 183.2.202.42          |
 |      2 | 1053957678 |     2 | 2019-08-16 00:09:06 | Ozeki VoIP SIP SDK v11.1.2 | FR      | Andresy             | 62.210.30.46          |
 |      3 | 2455302148 |     7 | 2019-08-23 00:05:03 |                      | US      |               | 146.88.240.4          |
 |      5 | 3107280921 |    26 | 2019-08-23 20:46:59 | friendly-scanner           | NL      |               | 185.53.88.25          |
 |      6 | 1308060846 |    53 | 2019-08-23 20:13:12 | friendly-scanner           | NL      |               | 77.247.108.174        |
 |      7 | 2671893706 |    90 | 2019-08-16 02:42:20 | 808                        | US      | North Bergen        | 159.65.220.202        |
 |     14 | 3110176716 |     6 | 2019-08-16 04:44:25 | sipcli/v1.8                | PS      |               | 185.97.135.204        |
 |     22 | 1308061231 |    18 | 2019-08-22 07:24:38 | friendly-scanner           | NL      |               | 77.247.110.47         |
 |     48 | 1347570964 |     8 | 2019-08-17 02:24:36 | friendly-scanner           | NL      |               | 80.82.77.20           |
 |     59 | 3107280933 |  9883 | 2019-08-19 03:03:32 |                      | NL      |               | 185.53.88.37          |
 |   1608 | 1308060849 |    35 | 2019-08-16 03:59:29 | sipcli/v1.8                | NL      |               | 77.247.108.177        |
 |   3967 | 3428686544 |     1 | 2019-08-16 04:03:13 | Asterisk PBX               | US      | Chicago             | 204.93.154.208        |
 |   3994 | 1754805254 |     1 | 2019-08-16 04:03:52 | Asterisk PBX               | US      |               | 104.152.52.6          |
 |   5290 | 3507576938 |     1 | 2019-08-16 04:37:12 | UnYeznuK                   | US      |               | 209.17.96.106         |
 |   7905 | 3107280927 |  3867 | 2019-08-22 11:42:00 |                      | NL      |               | 185.53.88.31          |
 |   8012 | 1356068398 |     1 | 2019-08-16 05:52:59 | Zeeko                      | PL      | Warsaw              | 80.211.246.46         |
 |   8207 | 3107280926 |  1825 | 2019-08-21 06:01:23 |                      | NL      |               | 185.53.88.30          |
 |   8412 | 3070413353 |    10 | 2019-08-23 07:18:04 | friendly-scanner           | CN      | Guangzhou           | 183.2.202.41          |
 |   8415 | 1308060851 |     1 | 2019-08-16 06:13:32 | friendly-scanner           | NL      |               | 77.247.108.179        |
 |   8746 | 1308060835 |    31 | 2019-08-16 08:45:51 | sipcli/v1.8                | NL      |               | 77.247.108.163        |
 |   8755 | 3107280940 |     3 | 2019-08-17 16:21:55 | friendly-scanner           | NL      |               | 185.53.88.44          |
 |   8761 | 1111941599 |     1 | 2019-08-16 07:26:55 | friendly-scanner           | CA      | Montreal            | 66.70.225.223         |
 |   9009 | 1308060832 |     9 | 2019-08-18 00:22:01 | friendly-scanner           | NL      |               | 77.247.108.160        |
 |   9250 | 2746007762 |   805 | 2019-08-20 10:26:04 | pplsip                     | FR      |               | 163.172.192.210       |
 |   9440 | 1308061242 |    16 | 2019-08-23 21:10:59 | friendly-scanner           | NL      |               | 77.247.110.58         |
 |   9458 | 3635051022 |   328 | 2019-08-17 11:17:00 | pplsip                     | US      | Buffalo             | 216.170.122.14        |
 |   9474 | 2959836555 |     8 | 2019-08-23 09:27:08 | Bria 5                     | PL      | Warsaw              | 176.107.133.139       |
 |   9494 | 3107280945 |     7 | 2019-08-22 18:24:01 | friendly-scanner           | NL      |               | 185.53.88.49          |
 |   9516 | 1308061267 |    64 | 2019-08-23 19:38:43 | friendly-scanner           | NL      |               | 77.247.110.83         |
 |   9546 | 1308061199 |  1884 | 2019-08-17 00:19:02 | sipcli/v1.8                | NL      |               | 77.247.110.15         |
 |   9684 | 1308061400 |     7 | 2019-08-23 20:59:58 | friendly-scanner           | NL      |               | 77.247.110.216        |
 |  10088 | 3565227944 |     2 | 2019-08-22 17:47:04 | friendly-scanner           | FR      |               | 212.129.15.168        |
 |  10335 | 1588721057 |     3 | 2019-08-19 12:08:11 | friendly-scanner           | DE      | Frankfurt am Main   | 94.177.245.161        |
 |  10372 | 3107280931 |     8 | 2019-08-21 00:32:14 | friendly-scanner           | NL      |               | 185.53.88.35          |
 |  10440 | 1588717692 |     6 | 2019-08-18 11:37:20 | friendly-scanner           | FR      | Paris               | 94.177.232.124        |
 |  10451 | 3507577090 |     1 | 2019-08-16 18:18:32 | haLoBtKl                   | US      |               | 209.17.97.2           |
 |  10507 | 1505473210 |     1 | 2019-08-16 18:33:49 | pplsip                     | US      |               | 89.187.178.186        |
 |  10653 | 3562251402 |    90 | 2019-08-22 17:24:00 | pplsip                     | FR      | Nuits-Saint-Georges | 212.83.164.138        |
 |  11077 |  861095860 |     1 | 2019-08-16 21:11:03 | friendly-scanner           | FR      |               | 51.83.71.180          |
 |  11157 | 1722098888 |   197 | 2019-08-22 14:11:49 | sipcli/v1.8                | US      |               | 102.165.36.200        |
 |  11408 |   87965623 |    37 | 2019-08-17 12:18:29 | pplsip                     | US      | New York            | 5.62.63.183           |
 |  11589 | 1308061418 |     5 | 2019-08-19 12:34:55 | Telefonadapter             | NL      |               | 77.247.110.234        |
 |  11781 | 3281662327 |     3 | 2019-08-20 14:51:30 | support                    | FR      | Paris               | 195.154.49.119        |
 |  11812 | 2680904312 |     1 | 2019-08-17 01:09:40 | friendly-scanner           | US      | Clifton             | 159.203.90.120        |
 |  11826 |  624027040 |   258 | 2019-08-23 21:31:43 | Telefonadapter             | NL      |               | 37.49.229.160         |
 |  11827 | 1051561183 |     1 | 2019-08-17 01:47:04 | friendly-scanner           | RU      |               | 62.173.140.223        |
 |  11830 | 3514713984 |     3 | 2019-08-18 02:57:23 | sipcli/v1.8                | US      | St Louis            | 209.126.71.128        |
 |  11863 | 2382633440 |     1 | 2019-08-17 03:22:02 | friendly-scanner           | US      | Provo               | 142.4.25.224          |
 |  11895 | 1123085431 |     1 | 2019-08-17 05:05:58 |                      | US      | San Diego           | 66.240.236.119        |
 |  11917 | 3562247345 |     1 | 2019-08-17 05:58:21 | Bria 5                     | FR      | Paris               | 212.83.148.177        |
 |  11951 | 3507577002 |     1 | 2019-08-17 07:18:45 | vCbNSKtv                   | US      |               | 209.17.96.170         |
 |  11997 | 2256000670 |     2 | 2019-08-18 22:15:28 | friendly-scanner           | FR      |               | 134.119.214.158       |
 |  12012 | 2806494445 |     1 | 2019-08-17 09:08:55 | friendly-scanner           | US      | New York            | 167.71.180.237        |
 |  12060 | 1588719724 | 12958 | 2019-08-17 10:34:45 | Asterisk PBX               | FR      | Paris               | 94.177.240.108        |
 |  25027 | 1308060847 |     3 | 2019-08-17 14:05:12 | PortSIP VoIP SDK 11.2      | NL      |               | 77.247.108.175        |
 |  25052 | 1051560788 |     6 | 2019-08-22 23:41:20 | friendly-scanner           | RU      |               | 62.173.139.84         |
 |  25061 | 2769743287 |  3827 | 2019-08-20 13:28:17 |                      | CA      | Toronto             | 165.22.237.183        |
 |  25089 |  624027025 |   237 | 2019-08-23 21:30:21 | Cisco-SIPGateway/IOS-12.x  | NL      |               | 37.49.229.145         |
 |  25135 | 1308060845 |     1 | 2019-08-17 13:13:26 | friendly-scanner           | NL      |               | 77.247.108.173        |
 |  25314 | 1356068336 |     4 | 2019-08-23 06:41:51 |                            | PL      | Warsaw              | 80.211.245.240        |
 |  25342 | 1308061206 |     5 | 2019-08-21 05:50:11 | friendly-scanner           | NL      |               | 77.247.110.22         |
 |  27328 | 3507576890 |     2 | 2019-08-19 20:41:58 | BQDPtxsE                   | US      |               | 209.17.96.58          |
 |  28414 | 3107280932 |     1 | 2019-08-17 22:24:17 | friendly-scanner           | NL      |               | 185.53.88.36          |
 |  29304 | 2745967553 |     2 | 2019-08-21 12:05:24 | friendly-scanner           | FR      |               | 163.172.35.193        |
 |  29317 | 2671893719 | 93202 | 2019-08-18 01:55:38 | friendly-scanner           | US      | North Bergen        | 159.65.220.215        |
 | 122638 |  624634839 |     1 | 2019-08-18 03:44:42 | Ozeki VoIP SIP SDK v11.1.2 | FR      |               | 37.59.43.215          |
 | 122643 | 1308060935 |     1 | 2019-08-18 03:48:16 |                      | NL      |               | 77.247.109.7          |
 | 122692 | 3107280934 |  8470 | 2019-08-18 15:22:11 |                      | NL      |               | 185.53.88.38          |
 | 123190 | 1308061276 |     2 | 2019-08-22 08:42:06 | friendly-scanner           | NL      |               | 77.247.110.92         |
 | 123505 |  860848595 |     1 | 2019-08-18 05:28:07 | friendly-scanner           | CA      |               | 51.79.129.211         |
 | 124707 | 3565236782 |     3 | 2019-08-19 22:04:23 | Careers                    | FR      | Paris               | 212.129.50.46         |
 | 130299 | 3119862842 |     1 | 2019-08-18 12:51:44 | VOIP                       | DK      | Copenhagen          | 185.245.84.58         |
 | 130398 |  394281064 |     1 | 2019-08-18 13:00:27 | friendly-scanner           | US      | Portland            | 23.128.64.104         |
 | 131093 | 3644653804 |     2 | 2019-08-20 08:21:00 | friendly-scanner           | DE      | Frankfurt am Main   | 217.61.0.236          |
 | 131760 | 1588720143 |     1 | 2019-08-18 15:09:48 | friendly-scanner           | FR      | Paris               | 94.177.242.15         |
 | 131891 | 3633377286 |     1 | 2019-08-18 16:03:16 | friendly-scanner           | US      | Dallas              | 216.144.240.6         |
 | 131905 | 3510351397 |    12 | 2019-08-23 14:24:14 | PortSIP VoIP SDK 11.2      | US      | Lansing             | 209.59.182.37         |
 | 131979 | 1356070905 |     1 | 2019-08-18 17:10:47 | friendly-scanner           | PL      | Warsaw              | 80.211.255.249        |
 | 132142 | 1308060872 |     1 | 2019-08-18 19:27:15 | friendly-scanner           | NL      |               | 77.247.108.200        |
 | 132163 | 3507577122 |     1 | 2019-08-18 19:45:08 | jXIjYZTm                   | US      |               | 209.17.97.34          |
 | 132185 | 1308061207 |     2 | 2019-08-19 20:21:17 | friendly-scanner           | NL      |               | 77.247.110.23         |
 | 132244 | 1431941221 |     2 | 2019-08-22 03:46:37 | friendly-scanner           | PL      | �ódź             | 85.89.176.101         |
 | 133058 | 1308061211 |     1 | 2019-08-19 00:03:59 | PBX                        | NL      |               | 77.247.110.27         |
 | 133193 |  861606239 |     1 | 2019-08-19 00:27:24 | friendly-scanner           | FR      |               | 51.91.17.95           |
 | 133194 | 2261891524 |     1 | 2019-08-19 00:27:26 | friendly-scanner           | GB      | London              | 134.209.185.196       |
 | 133247 | 1311362863 |     1 | 2019-08-19 00:35:16 | friendly-scanner           | NL      |               | 78.41.207.47          |
 | 133562 | 3221485468 |     4 | 2019-08-23 00:50:28 | friendly-scanner           | US      | Buffalo             | 192.3.247.156         |
 | 133573 | 2673412367 |     1 | 2019-08-19 01:28:30 | friendly-scanner           | DE      | Frankfurt am Main   | 159.89.9.15           |
 | 134160 |  861605605 |   690 | 2019-08-19 12:20:33 | gffg                       | FR      |               | 51.91.14.229          |
 | 134218 | 3562255759 |     1 | 2019-08-19 05:15:30 | friendly-scanner           | FR      |               | 212.83.181.143        |
 | 134412 | 1053973250 |    22 | 2019-08-23 19:24:58 | friendly-scanner           | FR      | Paris               | 62.210.91.2           |
 | 134556 | 1308061221 |     1 | 2019-08-19 08:19:53 | friendly-scanner           | NL      |               | 77.247.110.37         |
 | 134665 | 1249543663 |     1 | 2019-08-19 10:06:49 | friendly-scanner           | CA      |               | 74.122.133.239        |
 | 134750 |   93785829 |     1 | 2019-08-19 10:52:57 | friendly-scanner           | GB      |               | 5.151.14.229          |
 | 134831 | 1347570977 |     1 | 2019-08-19 11:36:12 |                      | NL      |               | 80.82.77.33           |
 | 134939 | 3225441884 |     3 | 2019-08-23 12:53:33 | friendly-scanner           | US      | Secaucus            | 192.64.86.92          |
 | 134944 | 3276722437 |     1 | 2019-08-19 14:48:33 | friendly-scanner           | IT      |               | 195.78.209.5          |
 | 134945 | 1122091794 |   147 | 2019-08-19 15:48:28 | MizuPhone                  | US      | Schaumburg          | 66.225.195.18         |
 | 135140 | 3000185825 |     1 | 2019-08-19 19:01:47 | friendly-scanner           | TR      | Samsun              | 178.211.51.225        |
 | 135169 | 2745980561 |     1 | 2019-08-19 20:38:42 | friendly-scanner           | FR      |               | 163.172.86.145        |
 | 135205 | 1066345778 |    77 | 2019-08-23 06:04:18 | FreePBX 1.8                | US      | Dallas              | 63.143.37.50          |
 | 135212 | 1053994118 |     1 | 2019-08-19 22:28:37 | friendly-scanner           | FR      |               | 62.210.172.134        |
 | 135218 | 3565239451 |     2 | 2019-08-19 22:49:18 | VaxSIPUserAgent/3.5        | FR      | Le Plessis-Robinson | 212.129.60.155        |
 | 135225 | 3639984622 |     3 | 2019-08-22 20:40:42 | friendly-scanner           | US      | Dallas              | 216.245.193.238       |
 | 135246 | 3000185822 |     1 | 2019-08-19 23:56:45 | friendly-scanner           | TR      | Samsun              | 178.211.51.222        |
 | 135266 | 3562255783 |     1 | 2019-08-20 00:42:30 | friendly-scanner           | FR      |               | 212.83.181.167        |
 | 135284 | 1308061283 |     2 | 2019-08-22 06:51:28 | friendly-scanner           | NL      |               | 77.247.110.99         |
 | 135289 | 2727356702 |     1 | 2019-08-20 01:31:33 | friendly-scanner           | US      | Provo               | 162.144.41.30         |
 | 135324 | 1053992906 |  6758 | 2019-08-23 00:25:28 | fgfdhgfxjfhyjhkj           | FR      |               | 62.210.167.202        |
 | 135353 | 1053988629 |  4944 | 2019-08-23 00:25:51 | fgfdhgfxjfhyjhkj           | FR      |               | 62.210.151.21         |
 | 135695 |  782669768 |     5 | 2019-08-20 04:15:47 | fgfdhgfxjfhyjhkj           | NL      | Uddel               | 46.166.151.200        |
 | 136185 |  782669779 | 35927 | 2019-08-22 20:33:51 | fgfdhgfxjfhyjhkj           | NL      | Uddel               | 46.166.151.211        |
 | 138056 | 3562244647 |     2 | 2019-08-21 06:56:33 | testsip                    | FR      | Guyancourt          | 212.83.138.39         |
 | 141492 | 3281660074 |     2 | 2019-08-23 03:57:42 | Bria 5                     | FR      | Argenteuil          | 195.154.40.170        |
 | 141905 | 2649154441 |  2025 | 2019-08-20 15:24:27 |                      | US      | North Bergen        | 157.230.227.137       |
 | 142082 | 3107280950 |     1 | 2019-08-20 11:55:44 | friendly-scanner           | NL      |               | 185.53.88.54          |
 | 144875 | 3107280956 |     2 | 2019-08-23 03:08:10 | friendly-scanner           | NL      |               | 185.53.88.60          |
 | 144881 | 3281677282 |    74 | 2019-08-20 23:27:29 |                      | FR      |               | 195.154.107.226       |
 | 148332 | 3423277731 |     1 | 2019-08-20 17:15:49 | friendly-scanner           | US      | Seattle             | 204.11.18.163         |
 | 148463 |  866041843 |     1 | 2019-08-20 17:29:43 | friendly-scanner           | FR      | Paris               | 51.158.191.243        |
 | 148935 | 3000019324 |     1 | 2019-08-20 18:17:37 | friendly-scanner           | GB      | London              | 178.208.169.124       |
 | 150006 | 1051562889 |     1 | 2019-08-20 20:02:47 | friendly-scanner           | RU      | Moscow              | 62.173.147.137        |
 | 150459 | 1308060874 |     4 | 2019-08-21 23:39:45 | friendly-scanner           | NL      |               | 77.247.108.202        |
 | 151158 | 3291753334 |     1 | 2019-08-20 22:05:46 | VhPrwfzK                   | US      | Edison              | 196.52.43.118         |
 | 152525 | 1308061269 |     1 | 2019-08-21 00:31:18 | friendly-scanner           | NL      |               | 77.247.110.85         |
 | 152748 | 1588719753 |     2 | 2019-08-21 20:19:51 | friendly-scanner           | FR      | Paris               | 94.177.240.137        |
 | 152938 | 1308061244 |   862 | 2019-08-22 16:25:35 |                      | NL      |               | 77.247.110.60         |
 | 154063 | 3639886154 |   216 | 2019-08-22 02:36:47 | sipcli/v1.8                | US      | Seattle             | 216.244.65.74         |
 | 154100 | 2806460368 |     1 | 2019-08-21 02:06:05 | friendly-scanner           | DE      | Frankfurt am Main   | 167.71.47.208         |
 | 155564 | 1308061232 |   430 | 2019-08-21 04:50:00 |                      | NL      |               | 77.247.110.48         |
 | 156748 | 3107280962 |     1 | 2019-08-21 04:32:50 | friendly-scanner           | NL      |               | 185.53.88.66          |
 | 158028 | 3507576914 |     1 | 2019-08-21 05:49:21 | yMQsDpTp                   | US      |               | 209.17.96.82          |
 | 159649 | 2659999515 |     3 | 2019-08-21 11:24:18 | PortSIP VoIP SDK 11.2      | PS      |               | 158.140.95.27         |
 | 159710 | 2769728503 |     1 | 2019-08-21 09:12:10 | 808                        | US      | North Bergen        | 165.22.179.247        |
 | 160120 | 2755949120 |     1 | 2019-08-21 09:53:12 | friendly-scanner           | DE      |               | 164.68.114.64         |
 | 160495 | 2809328307 |     1 | 2019-08-21 10:31:39 | friendly-scanner           | FR      | Roubaix             | 167.114.242.179       |
 | 160763 | 2807459643 |     2 | 2019-08-23 19:48:17 | friendly-scanner           | DE      | Nuremberg           | 167.86.111.59         |
 | 161033 | 1356048200 |     1 | 2019-08-21 11:27:02 | friendly-scanner           | IT      | Arezzo              | 80.211.167.72         |
 | 162648 | 1245704902 |     1 | 2019-08-21 14:08:05 | friendly-scanner           | US      | Dallas              | 74.63.242.198         |
 | 163133 | 1308060834 |     3 | 2019-08-23 06:01:53 | friendly-scanner           | NL      |               | 77.247.108.162        |
 | 168263 | 2746011496 |   552 | 2019-08-23 14:00:56 | pplsip                     | FR      |               | 163.172.207.104       |
 | 168773 | 2807450735 |     1 | 2019-08-21 21:25:23 | friendly-scanner           | DE      | Nuremberg           | 167.86.76.111         |
 | 169044 | 1308061297 |    17 | 2019-08-23 21:17:33 | FreePBX 1.8                | NL      |               | 77.247.110.113        |
 | 169067 | 3507577202 |     1 | 2019-08-21 21:44:59 | YtsNMLcO                   | US      |               | 209.17.97.114         |
 | 171542 | 1588717792 |     1 | 2019-08-22 00:23:29 | friendly-scanner           | FR      | Paris               | 94.177.232.224        |
 | 171799 | 1053987565 | 12958 | 2019-08-22 00:42:46 | Asterisk PBX               | FR      |               | 62.210.146.237        |
 | 185012 | 1334405281 |     2 | 2019-08-22 00:56:21 | VaxSIPUserAgent/3.5        | FR      |               | 79.137.104.161        |
 | 185327 |   87965663 |    25 | 2019-08-22 07:07:17 | pplsip                     | US      | New York            | 5.62.63.223           |
 | 187784 | 1090198397 |     2 | 2019-08-23 05:41:24 | friendly-scanner           | US      | Fort Lauderdale     | 64.251.27.125         |
 | 187866 | 2731950313 | 12958 | 2019-08-22 04:06:58 | Asterisk PBX               | US      | Provo               | 162.214.64.233        |
 | 202877 | 3107280937 |     1 | 2019-08-22 06:18:42 | friendly-scanner           | NL      |               | 185.53.88.41          |
 | 206150 | 3221475752 |     2 | 2019-08-23 05:25:06 | friendly-scanner           | US      | Buffalo             | 192.3.209.168         |
 | 207013 | 3106407447 |     2 | 2019-08-22 10:45:26 | MizuPhone                  | RU      |               | 185.40.4.23           |
 | 207253 | 3537074702 |     1 | 2019-08-22 11:00:38 | friendly-scanner           | VN      |               | 210.211.122.14        |
 | 207349 | 1611166586 |    75 | 2019-08-23 17:39:14 | Conaito                    | US      | Buffalo             | 96.8.115.122          |
 | 207758 | 2207850059 |     2 | 2019-08-23 07:04:37 | friendly-scanner           | US      | Atlanta             | 131.153.30.75         |
 | 207804 | 3644668999 |     1 | 2019-08-22 11:35:07 | friendly-scanner           | IT      | Ponte San Pietro    | 217.61.60.71          |
 | 208524 | 1066345266 |     1 | 2019-08-22 12:22:59 | friendly-scanner           | US      | Dallas              | 63.143.35.50          |
 | 212570 | 1051564796 |     1 | 2019-08-22 16:38:40 | friendly-scanner           | RU      | Moscow              | 62.173.154.252        |
 | 214517 | 1168277118 |    25 | 2019-08-23 06:00:47 |                      | US      | Dallas              | 69.162.126.126        |
 | 214658 | 3107280948 |     1 | 2019-08-22 18:58:40 | friendly-scanner           | NL      |               | 185.53.88.52          |
 | 215069 | 2382630478 | 12911 | 2019-08-22 19:31:21 | Asterisk PBX               | US      | Provo               | 142.4.14.78           |
 | 229501 | 3507576850 |     1 | 2019-08-22 22:57:06 | VnghmpQZ                   | US      |               | 209.17.96.18          |
 | 229709 | 1446618158 |     1 | 2019-08-22 23:53:04 | friendly-scanner           | BY      | Hrodna              | 86.57.164.46          |
 | 229774 | 1051560609 |     1 | 2019-08-23 00:10:51 | friendly-scanner           | RU      |               | 62.173.138.161        |
 | 229848 |  602425983 |     1 | 2019-08-23 01:44:50 | friendly-scanner           | US      |               | 35.232.74.127         |
 | 229891 | 1051563810 |     1 | 2019-08-23 04:02:30 | friendly-scanner           | RU      | Moscow              | 62.173.151.34         |
 | 229907 | 1551278113 |     1 | 2019-08-23 04:56:25 | vdjbTQHQ                   | LT      |               | 92.118.160.33         |
 | 230115 | 1168270182 |     1 | 2019-08-23 14:01:07 | friendly-scanner           | US      | Dallas              | 69.162.99.102         |
 | 230116 |   87965622 |    31 | 2019-08-23 21:29:53 | pplsip                     | US      | New York            | 5.62.63.182           |
 | 230122 |  392083557 |     1 | 2019-08-23 14:31:38 | friendly-scanner           | US      | Buffalo             | 23.94.184.101         |
 | 230125 | 1870059136 |     1 | 2019-08-23 15:01:48 | friendly-scanner           | IN      |               | 111.118.214.128       |
 +--------+------------+-------+---------------------+----------------------------+---------+---------------------+-----------------------+
 172 rows in set (0.00 sec)

RTPengine Python API Calls via ng Control Protocol

RTPengine has an API / control protocol, which is what Kamailio / OpenSER uses to interact with RTPengine, called the ng Control Protocol.

Connection is based on Bencode encoded data and communicates via a UDP socket.

I wrote a simple Python script to pull active calls from RTPengine, code below:

#Quick Python library for interfacing with Sipwise's fantastic rtpengine - https://github.com/sipwise/rtpengine
#Bencode library from https://pypi.org/project/bencode.py/ (Had to download files from webpage (PIP was out of date))

import bencode
import socket
import sys
import random
import string

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('188.0.169.13', 2224)     #Your server address

cookie = "0_2393_6"
data = bencode.encode({'command': 'list'})

message = str(cookie) + " " + str(data)
print(message)


sent = sock.sendto(message, server_address)

print('waiting to receive')
data, server = sock.recvfrom(4096)
print('received "%s"' % data)
data = data.split(" ", 1)       #Only split on first space
print("Cookie is: " + str(data[0]))
print("Data is: " + str(bencode.decode(data[1])))
print("There are " + str(len(bencode.decode(data[1])['calls'])) + " calls up on RTPengine at " + str(server_address[0]))
for calls in bencode.decode(data[1])['calls']:
    print(calls)
    cookie = "1_2393_6"
    data = bencode.encode({'command': 'query', 'call-id': str(calls)})
    message = str(cookie).encode('utf-8') + " ".encode('utf-8') + str(data).encode('utf-8')
    sent = sock.sendto(message, server_address)
    print('\n\nwaiting to receive')
    data, server = sock.recvfrom(8192)

    data = data.split(" ", 1)       #Only split on first space
    bencoded_data = bencode.decode(data[1])

    for keys in bencoded_data:
        print(keys)
        print("\t" + str(bencoded_data[keys]))

sock.close()

Kamailio Bytes – Databases

We’ve touched a tiny bit on basic database functionality in Kamailio, using MySQL to store User Data for authentication, ACLs or Dispatcher entries.

But with all those we were using Databases to load the config / dynamic data for a module.

We’ll build upon that, to connect to a Database that we can INSERT, UPDATE and SELECT data from within the dialplan.

For today’s example we’ll lookup the To address from a SIP INVITE and send back

Heads Up

There’s a lot of different use cases for reading and writing data from a database, but Kamailio also has a lot of native modules that handle this better, for example:

  • You might want to store a record of each INVITE and BYE you recieve for accounting, a better option is to use the Accounting Module in Kamailio.
  • You might want to authenticate user’s based on ACLs stored in a database, a better option would be to use Permissions Module.
  • User authentication info is best handled by Auth DB module.
  • The Dialplan module handles number translation really well and is lightning quick.

Just keep this in mind before jumping in that a lot of use cases have already been covered by a Kamailio module.

The Architecture

For today’s example we’ll be using MySQL as the database backend (db_mysl), but the db_mysql module simply connects us to a database, a bit like ODBC.

The real MVP is the SQLops module, that does all the heavy lifting by running the queries and managing the responses.

The majority of this config would work fine for other database backends, like PostGres, MongoDB, Oracle, etc.

I’ll demonstrate this same setup using different database backends in future posts.

MySQL Database

Before we get too excited we’ll need to setup a database to query. I’ll create a database called dyamic_routing with a table called routing storing source / destinations.

CREATE DATABASE phonebook;
USE  phonebook;
CREATE TABLE contacts (
     id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
     source TEXT,
     name TEXT
 );
INSERT INTO contacts VALUES (1, '200', 'Nick Deskphone');

I’ll setup a MySQL user to INSERT/UPDATE/SELECT data from the MySQL database the normal way.

Modparam

The module parameters for connecting to a database backend are fairly straight forward, but we’ll go into a bit of depth here to drive home the point.

# ----- SQL params -----
loadmodule "db_mysql.so"
loadmodule "sqlops.so"

#Create a new MySQL database connection called contacts_db
modparam("sqlops","sqlcon","contacts_db=>mysql://root:youshouldntrealluseroot@localhost/phonebook")

#Set timeouts for MySQL Connections
modparam("db_mysql", "ping_interval", 60)
modparam("db_mysql", "auto_reconnect", 1)
modparam("db_mysql", "timeout_interval", 2)

First off we load the two modules we need for this, db_mysql and sqlops. This is fairly self explanatory, if we were using db_postgres, db_mongodb or even db_text we’d load them instead of db_mysql.

The sqlops “sqlcon” modparam is where we define our MySQL database connection.

In this case we create a new database connection object called contacts_db– We can have connections to multiple databases, hence requiring a name.

The MySQL URL is fairly straightforward, database type, username, password, host and database:

mysql://root:password@localhost/phonebook

In production obviously you shouldn’t use root as the user account to log into the database, and lock it down to only the source IP of your Kamailio instance and with only the permissions it needs. (If it’s just selecting data there’s no need for GRANT…)

Basic Query

Now we’ve created a database connection it’s time to start using it.

request_route {
        if(method=="INVITE"){
                xlog("New request to $tU");

                #Query database object called "contacts_db", run the below query and store the output to a variable called result_sql
                sql_query("contacts_db", "select * from contacts;", "result_sql");

                #output number of rows in database returned
                xlog("number of rows in table is $dbr(result_sql=>rows)\n");
}
}

If the method is an INVITE we’ll query the database object called “contacts_db” to run the query select * from contacts;

We’ll then output the number of rows in the table to xlog.

The query actually happens in the sql_query() command, which takes the name of the database object ( contacts_db ), the query itself ( select * from contacts; ) and stores it into a variable called result_sql.

Finally xlog references the variable we stored our result in (result_sql) using the $dbr() handler to output the number of rows in the table.

If you save this and send an INVITE to any destination and watch the syslog you should see something along the lines of this:

/usr/sbin/kamailio[7815]: ERROR: : New request to 200
/usr/sbin/kamailio[7815]: ERROR: <script>: number of rows in table is 1

This means we’ve got a connection to the database and we can run queries.

Accessing the Output

Now we’ve got the data back from the database and stored it in result_sql we probably want to do something with the outputted data.

By wrapping the result_sql variable in the $dbr() tags we can access it’s jucy insides, let’s take a look:

                #output number of columns
                xlog("Result has $dbr(result_sql=>cols) Columns");
                #output number of rows
                xlog("Result has $dbr(result_sql=>rows) rows");
                #output contents of row 0, column 2
                xlog("Contents of row 0 col 2 is $dbr(result_sql=>[0,2]) ");
                #output name of column 2
                xlog("name of column 2 is $dbr(result_sql=>colname[2]) ");

If we add this after our last xlog line, restart Kamailio and view syslog it should look something like this:

/usr/sbin/kamailio[8249]: ERROR: <script>: New request to 200
/usr/sbin/kamailio[8249]: ERROR: <script>: number of rows in table is 1
/usr/sbin/kamailio[8249]: ERROR: <script>: Result has 3 Columns
/usr/sbin/kamailio[8249]: ERROR: <script>: Result has 1 rows
/usr/sbin/kamailio[8249]: ERROR: <script>: Contents of row 0 column 2 is Nick Deskphone

Now we can see the data in the result we’ll start to refine this down a bit, we’ll begin by limiting the SQL query to search for the called number.

For this we’ll update the sql_query(); function to:

sql_query("contacts_db", "select * from contacts where source = $tU;", "result_sql");

This will include the the To URI Username pseudo variable in our query, so will only return results if the number we dial has one or more matching “source” entries in the database.

If we dial 200 the query that’s actually run against the database will look like this:

select * from contacts where source = '200';

Now once we save and try again our traffic will look the same, except it’ll only return data if we dial 200, if we dial 201 the SQL server won’t have any matching results to return:

/usr/sbin/kamailio[9069]: ERROR: : New request from 2029
/usr/sbin/kamailio[9069]: ERROR: number of rows in table is 0
/usr/sbin/kamailio[9069]: ERROR: Result has 0 Columns

So that’s all well and good but we haven’t really got the data easily yet, while we’re outputting the contents of row 0 col 2 to syslog, it’s not going to handle multiple results being returned, or 0 results being returned, so we need a better way to handle this.

We’ll use a for loop to loop through all the results returned and output the second column of each (the “name” field in the database).

                #Loop through results
                #Create variable i to use as the counter
                $var(i) = 0;
                #While the contents of row i, position 2, is not null:
                while ($dbr(result_sql=>[$var(i),2]) != $null) {
                        #Output row i, position 2 (name)
                        xlog("name is $dbr(result_sql=>[$var(i),2])");
                        #increment i by 1
                        $var(i) = $var(i) + 1;
                }

So while the contents of row i, position 2, is not null, we’ll output the contents and increment i to get the next row in the database until there are none left.

Now we can give our code a bit of a clean up:

request_route {
        if(method=="INVITE"){
                xlog("New request from $tU");

                #Query database object called "contacts_db", run the below query and store the output to a variable called result_sql
                sql_query("contacts_db", "select * from contacts where source = $tU;", "result_sql");

                #Loop through results
                #Create variable i to use as the counter
                $var(i) = 0;
                #While the contents of row i, position 2, is not null:
                while ($dbr(result_sql=>[$var(i),2]) != $null) {
                        #Output row i, position 2 (name)
                        xlog("name $dbr(result_sql=>[$var(i),2])");
                        #increment i by 1
                        $var(i) = $var(i) + 1;
                }


        }
        if(method=="REGISTER"){ sl_reply('200', 'OK'); }
}

I’ve removed many of our xlog entries we put in for debugging and also added a handler to handle REGISTER requests to keep my IP phone happy.

Now if we make a call to number 200:

/usr/sbin/kamailio[9686]: ERROR: New request from 200
/usr/sbin/kamailio[9686]: ERROR: name Nick Deskphone

And for comparison a call to 201 (no matching database entry):

/usr/sbin/kamailio[9686]: ERROR: New request from 200

Using the Resulting Output

Now we’ve got access to the data from the database let’s do something with it.

Inside our loop we’ll send a reply to the SIP requester, with a 410 “Gone” response with the body containing the data returned from the database:

                        #Loop through results
                        #Create variable i to use as the counter
                        $var(i) = 0;
                        #While the contents of row i, position 2, is not null:
                        while ($dbr(result_sql=>[$var(i),2]) != $null) {
                                #Output row i, position 2 (name)
                                xlog("name $dbr(result_sql=>[$var(i),2])");
                                $var(name) = $dbr(result_sql=>[$var(i),2]);
                                #increment i by 1
                                $var(i) = $var(i) + 1;

                                #Reply with a 410 (User Gone) response with the name returned from the database
                                sl_reply("410", "Sorry $var(name) has gone home");
                                exit;
                        }

Now calls to 200 will get the message “Sorry Nick desk phone has gone home”.

Lastly we probably want to only loop through the output if there’s more than one row returned from the database, so we’ll put the looping code in an if statement that evaluates if the number of returned rows from the database is 1 or more, and if not just send a 404 response:


                #if one or more results are returned from the database
                if($dbr(result_sql=>rows)>0){

                        #Loop through results
                        #Create variable i to use as the counter
                        $var(i) = 0;
                        #While the contents of row i, position 2, is not null:
                        while ($dbr(result_sql=>[$var(i),2]) != $null) {
                                #Output row i, position 2 (name)
                                xlog("name $dbr(result_sql=>[$var(i),2])");
                                $var(name) = $dbr(result_sql=>[$var(i),2]);
                                #increment i by 1
                                $var(i) = $var(i) + 1;

                                #Reply with a 410 (User Gone) response with the name returned from the database
                                sl_reply("410", "Sorry $var(name) has gone home");
                                exit;
                        }
                }else{
                        #if 0 results are returned from database
                        sl_reply("404", "Never heard of them");
                }

INSERT, DELETE, UPDATE, etc

Although we only covered SELECT, queries that don’t return data like an INSERT, UPDATE, DELETE etc, can all be run the same way but we just don’t need to worry about managing the returned data.

For example we could delete a record using:

sql_query("contacts_db", "delete * from contacts where source = $tU;");

We don’t even need to store the output unless we need to.

Summary

Hopefully you’ve now got an idea how to query data from a database and view / manipulated the returned data.

As always I’ve posted my running source code for you to play with on GitHub, and I’ll do a few follow up posts on other database backends you may want to use other than MySQL.