Category Archives: VoIP

SIP Route, Contact, From Headers – Which to use?

SIP Proxies are simple in theory but start to get a bit more complex when implemented.

When a proxy has a response to send back to an endpoint, it can have multiple headers with routing information for how to get that response back to the endpoint that requested it.

So how to know which header to use on a new request?

Routing SIP Requests

Record-Route

If Route header is present (Like Record-Route) the proxy should use the contents of the Record-Route header to route the traffic back.

The Record-Route header is generally not the endpoint itself but another proxy, but that’s not an issue as the next proxy will know how to get to the endpoint, or use this same logic to know how to get it to the next proxy.

Contact

If no Route headers are present, the contact header is used.

The contact provides an address at which a endpoint can be contacted directly, this is used when no Record-Route header present.

From

If there is no Contact or Route headers the proxy should use the From address.

A note about Via

Via headers are only used in getting responses back to a client, and each hop removes it’s own IP on the response before forwarding it onto the next proxy.

This means the client doesn’t know all the Via headers that were on this SIP request, because by the time it gets back to the client they’ve all been removed one by one as it passed through each proxy.

A client can’t send a SIP request using Via’s as it hasn’t been through the proxies for their details to be added, so Via is only used in responding to a request, for example responding with a 404 to an INVITE, but cannot be used on a request itself (For example an INVITE).

SNgrep – Command line SIP Debugging

If you, like me, spend a lot of time looking at SIP logs, sngrep is an awesome tool for debugging on remote machines. It’s kind of like if VoIP Monitor was ported back to the days of mainframes & minimal remote terminal GUIs.

Installation

It’s in the Repos for Debian and Ubuntu:

apt-get install sngrep

GUI Usage

sngrep can be used to parse packet captures and create packet captures by capturing off an interface, and view them at the same time.

We’ll start by just calling sngrep on a box with some SIP traffic, and waiting to see the dialogs appear.

Here we can see some dialogs, two REGISTERs and 4 INVITEs.

By using the up and down arrow keys we can select a dialog, hitting Enter (Return) will allow us to view that dialog in more detail:

Again we can use the up and down arrow keys to view each of the responses / messages in the dialog.

Hitting Enter again will show you that message in full screen, and hitting Escape will bring you back to the first screen.

From the home screen you can filter with F7, to find the dialog you’re interested in.

Command Line Parameters

One of the best features about sngrep is that you can capture and view at the same time.

As a long time user of TCPdump, I’d been faced with two options, capture the packets, download them, view them and look for what I’m after, or view it live with a pile of chained grep statements and hope to see what I want.

By adding -O filename.pcap to sngrep you can capture to a packet capture and view at the same time.

You can use expression matching to match only specific dialogs.

Kamailio Bytes – Permissions Module

Kamailio’s permissions module is simple to use, and we’ve already touched upon it in the security section in our Kamailio 101 series, but I thought I’d go over some of it’s features in more detail.

At it’s core, Kamailio’s Permissions module is a series of Access Control Lists (ACLs) that can be applied to different sections of your config.

We can manage permissions to do with call routing, for example, is that source allowed to route to that destination.

We can manage registration permissions, for example, is this subnet allowed to register this username.

We can manage URI permissions & address permissions to check if a specific SIP URI or source address is allowed to do something.

We’ll touch on a simple IP Address based ACL setup in this post, but you can find more information in the module documentation itself.

The Setup

We’ll be using a database backend for this (MySQL), setup the usual way.

We’ll need to load the permissions module and setup it’s basic parameters, for more info on setting up the database side of things have a look here.

loadmodule "permissions.so"
...
modparam("permissions", "db_url", DBURL)
modparam("permissions", "db_mode", 1)

Next we’ll need to add some IPs, we could use Serimis for this, or a straight MySQL INSERT, but we’ll use kamctl to add them. (kamcmd can reload addresses but doesn’t currently have the functionality to add them)

kamctl address add 250 10.8.203.139 32 5060 TestServer
kamctl address add 200 192.168.1.0 24 5060 OfficeSubnet

The above example we added a two new address entries,

The first one added a new entry in group 250 of “10.8.203.139”, with a /32 subnet mask (Single IP), on port 5060 with the label “TestServer”,

The second one we added to group 200 was a subnet of 192.168.1.0 with a /24 subnet mask (255 IPs), on port 5060 with the label “OfficeSubnet”

On startup, or when we manually reload the addressTable, Kamailio grabs all the records and stores them in RAM. This makes lookup super fast, but the tradeoff is you have to load the entries, so changes aren’t immediate.

Let’s use Kamcmd to reload the entries and check their status.

kamcmd permissions.addressReload

kamcmd permissions.addressDump

kamcmd permissions.subnetDump

You should see the single IP in the output of the permissions.addressDump and see the subnet on the subnetDump:

Usage

It’s usage is pretty simple, combined with a simple nested if statement.

if (allow_source_address("200")) {
	xlog("Coming from address group 200");
};
if (allow_source_address("250")) {
	xlog("Coming from address group 250");
};

The above example just outputs to xlog with the address group, but we can expand upon this to give us our ACL service.

if (allow_source_address("200")) {
	xlog("Coming from address group 200");
}else if (allow_source_address("250")) {
	xlog("Coming from address group 250");
}else{
        sl_reply("401", "Address not authorised");
        exit;
}

If we put this at the top of our Kamailio config we’ll reply with a 401 response to any traffic not in address group 200 or 250.

Stateless, Stateful, Dialog Stateful and Transaction Stateful SIP Proxies

If you’ve ever phoned a big company like a government agency or an ISP to get something resolved, and been transferred between person to person, having to start again explaining the problem to each of them, then you know how frustrating this can be.

If they stored information about your call that they could bring up later during the call, it’d make your call better.

If the big company, started keeping a record of the call that could be referenced as the call progresses, they’d be storing state for that call.

Let’s build on this a bit more,

You phone Big Company again, the receptionist answers and says “Thank you for calling Big Company, how many I direct your call?”, and you ask to speak to John Smith.

The receptionist puts you through to John Smith, who’s not at his desk and has setup a forward on his phone to send all his calls to reception, so you ring back at reception.

A stateful receptionist would say “Hello again, it seems John Smith isn’t at his desk, would you like me to take a message?”.

A stateless receptionist would say “Thank you for calling Big Company, how many I direct your call?”, and you’d start all over again.

Our stateful receptionist remembered something about our call, they remembered they’d spoken to you, remembered who you were, that you were trying to get to John Smith.

While our stateless receptionist remembered nothing and treated this like a new call.

In SIP, state is simply remembering something about that particular session (series of SIP messages).

SIP State just means bits of information related to the session.

Stateless SIP Proxy

A Stateless SIP proxy doesn’t remember anything about the messages (sessions), no state information is kept. As soon as the proxy forwards the message, it forgets all about it, like our receptionist who just forwards the call and doesn’t remember anything.

Going back to our Big Company example, as you can imagine, this is much more scaleable, you can have a pool of stateless receptionists, none of whom know who you are if you speak to them again, but they’re a lot more efficient because they don’t need to remember any state information, and they can quickly do their thing without looking stuff up or memorising it.

The same is true of a Stateless SIP proxy.

Stateless proxies are commonly used for load balancing, where you want to just forward the traffic to another destination (maybe using the Dispatcher module) and don’t need to remember anything about that session.

It sounds obvious, but because a Stateless SIP proxy it stateless it doesn’t store state, but that also means it doesn’t need to lookup state information or write it back, making it much faster and generally able to handle larger call loads than a stateful equivalent.

Dialog Stateful SIP Proxy

A dialog stateful proxy keeps state information for the duration of that session (dialog).

By dialog we mean for the entire duration on the call/session (called a dialog) from beginning to end, INVITE to BYE.

While this takes more resources, it means we can do some more advanced functions.

For example if we want to charge based on the length of a call/session, we’d need to store state information, like the Call-ID, the start and end time of the call. We can only do this with a stateful proxy, as a stateless proxy wouldn’t know what time the call started.

Also if we wanted to know if a user was on a call or not, a Dialog Stateful proxy knows there’s been a 200 OK, but no Bye yet, so knows if a user is on a call or not, this is useful for presence. We could tie this in with a NOTIFY so other users could know their status.

A Dialog Stateful Proxy is the most resource intensive, as it needs to store state for the duration of the session.

Transaction Stateful SIP Proxy

A transactional proxy keeps state until a final response is received, and then forgets the state information after the final response.

A Transaction Stateful proxy stores state from the initial INVITE until a 200 OK is received. As soon as the session is setup it forgets everything. This means we won’t have any state information when the BYE is eventually received.

While this means we won’t be able to do the same features as the Dialog Stateful Proxy, but you’ll find that most of the time you can get away with just using Transaction Stateful proxies, which are less resource intensive.

For example if we want to send a call to multiple carriers and wait for a successful response before connecting it to the UA, a Transactional proxy would do the trick, with no need to go down the Dialog Stateful path, as we only need to keep state until a session is successfully setup.

For the most part, SIP is focused on setting up sessions, and so is a Transaction Stateful Proxy.

Typical Use Cases

StatelesssDialog StatefulTransaction Stateful
Load balancer,
Redirection server,
Manipulate headers,
Call charging,
CDR generation,
User status (Knows if on call)
All features of transaction stateful
Dispatch to destinations until successful
Call forward on Busy / No Answer
SIP Registrar
Call forking

Kamailio Bytes – Dialplan Module

Kamalio’s dialplan is a bit of a misleading title, as it can do so much more than just act as a dialplan.

At it’s core, it runs transformations. You feed it a value, if the value matches the regex Kamailio has it can either apply a transformation to that value or return a different value.

Adding to Config

For now we’ll just load the dialplan module and point it at our DBURL variable:

loadmodule "dialplan.so"
modparam("dialplan", "db_url", DBURL);                 #Dialplan database from DBURL variable

Restart Kamailio and we can get started.

Basics

Let’s say we want to take StringA and translate it in the dialplan module to StringB, so we’d add an entry to the database in the dialplan table, to take StringA and replace it with StringB.

We’ll go through the contents of the database in more detail later in the post

Now we’ll fire up Kamailio, open kamcmd and reload the dialplan, and dump out the entries in Dialplan ID 1:

dialplan.reload
dialplan.dump 1

You should see the output of what we just put into the database reflected in kamcmd:

Now we can test our dialplan translations, using Kamcmd again.

dialplan.translate 1 StringA

All going well Kamailio will match StringA and return StringB:

So we can see when we feed in String A, to dialplan ID 1, we get String B returned.

Database Structure

There’s a few fields in the database we populated, let’s talk about what each one does.

dpid

dpid = Dialplan ID. This means we can have multiple dialplans, each with a unique dialplan ID. When testing we’ll always need to specific the dialplan ID we’re using to make sure we’re testing with the right rules.

priority

Priorities in the dialplan allow us to have different weighted priorities. For example we might want a match all wildcard entry, but more specific entries with lower values. We don’t want to match our wildcard failover entry if there’s a more specific match, so we use priorities to run through the list, first we try and match the group with the lowest number, then the next lowest and so on, until a match is found.

match_op

match_op = Match Operation. There are 3 options:

  • 0 – string comparison;
  • 1 – regular expression matching (pcre);
  • 2 – fnmatch (shell-like pattern) matching

In our first example we had match_op set to 0, so we exactly matched “StringA”. The real power comes from Regex Matching, which we’ll cover soon.

match_exp

match_exp = Match expression. When match_op is set to 0 this matches exactly the string in match_exp, when match_op is set to 1 this will contain a regular expression to match.

match_len

match_len = Match Length. Allows you to match a specific length of string.

subst_exp

subst_exp = Substitute Expression. If match_op is set to 0 this will contain be empty If match_op is 1 this will contain the same as match_exp.

repl_exp

repl_exp = replacement expression. If match_op is set to 0 this will contain the string to replace the matched string.

If match_op is set to 1 this can contain the regex group matching (\1, \2, etc) and any suffixes / prefixes (for example 61\1 will prefix 61 and add the contents of matched group 1).

attrs

Attributes. Often used as a descriptive name for the matched rule.

Getting Regex Rules Setup

The real power of the dialplan comes from Regular Expression matching. Let’s look at some use cases and how to solve them with Dialplans.

Note for MySQL users: MySQL treats \ as the escape character, but we need it for things like matching a digit in Regex (that’s \d ) – So keep in mind when inserting this into MySQL you may need to escale the escape, so to enter \d into the match_exp field in MySQL you’d enter \\d – This has caught me in the past!

The hyperlinks below take you to the examples in Regex101.com so you can preview the rules and make sure it’s matching what it should prior to putting it into the database.

Speed Dial

Let’s start with a simple example of a speed dial. When a user dials 101 we want to translate it to a PSTN number of 0212341234.

Without Regex this looks very similar to our first example, we’ve just changed the dialplan id (dpid) and the match_op and repl_exp.

Once we’ve added it to the database we’ll reload the dialplan module and dump dialplan 2 to check it all looks correct:

Now let’s test what happens if we do a dialplan translate on dialplan 2 with 101.

Tip: If you’re testing a dialplan and what you’re matching is a number, add s: before it so it matches as a number, not a string.

dialplan.translate 2 s:101

Here we can see we’ve matched 101 and the output is the PSTN number we wanted to translate too.

Interoffice Dial

Let’s take a slightly more complex example. We’ve got an office with two branches, office A’s phone numbers start with 0299991000, and they have 4 digit extensions, so extension 1002 maps to 0299991002, 0299991003 maps to extension 1003, etc.

From Office B we want to be able to just dial the 4 digit extensions of a user in Office A.

This means if we receive 1003 we need to prefix
029999 + 10003.

We’ll use Regular Expressions to achieve this.

We can use a simple Regular Expression to match any number starting with 1 with 3 digits after it.

But the problem here is we want to collect the output into a Regex Group, and then prefix 029999 and the output of that group.

So let’s match it using a group.

([1]\d{3})

So let’s put this into the database and prefix everything in matching group 1 with 029999.

We’ll use dialplan ID 3 to separate it from the others, and we’ll set match_op to 1 to use Regex.

As you can see in repl_exp we’ve got our prefix and then \1.

\1 just means the contents of regex matching group 1.

After running dialplan reload let’s try this one out:

dialplan.reload
dialplan.translate 3 s:1003

We tested with 1003, but we could use 1000 through to 1999 and all would match.

But if we’ve only got a 100 number range (0299991000 to
0299991099) we’ll only want to match the first 100 numbers, so let’s tweak our regex to only allow the first two digits to be wildcards.

([1][0]\d{2})

Now let’s update the database:

Then another reload and translate, and we can test again.

dialplan.reload
dialplan.translate 3 s:1003 (Translates to 0299991003)
dialplan.translate 3 s:1101 (no translation)

Interoffice Dial Failure Route (Priorities)

So let’s say we’ve got lots of branches configured like this, and we don’t want to just get “No Translation” if a match isn’t found, but rather send it to a specific destination, say reception on extension 9000.

So we’ll keep using dpid 3 and we’ll set all our interoffice dial rules to have priority 1, and we’ll create a new entry to match anything 4 digits long and route it to the switch.

This entry will have a higher priority value than the other so will only mach if nothing else with a lower priority number matches.

We’ll use this simple regex to match anything 4 digits long into group 1.

 (\d{4})

Now let’s run through some test again.

dialplan.reload
dialplan.translate 3 s:1003 (Translates to 0299991003)
dialplan.translate 3 s:1101 (Translates to 9000 (Attributes: Interoffice Dial - Backup to Reception)

Translate 0NSN to E.164 format numbers

Let’s say we’ve got a local 10 digit number. In 0NSN format it looks like 0399999999 but we want it in E.164 so it looks like 613999999999.

Let’s use Kamailio to translate this from 0NSN to E.164.

The first thing we’ll need to do is create a regular expression to match
0399999999.

We’ll match anything starting with 03, with 9 digits after the 0 matched in Group 2.

([0][3])(\d{8})

Now we’ve got Group 2 containing the data we need, we just need to prefix 613 in front of it.

Let’s go ahead an put this into the database, with dialplan ID set to 4, match_op set to 1 (for regex)

Then we’ll do a dialplan reload and a dialplan dump for dialplan ID 4 to check everything is there:

Now let’s put it to the test.

dialplan.translate 4 s:0399999999

Bingo, we’ve matched the regex, and returned 613 and the output of Regex Match group 2. (999999999)

Let’s expand upon this a bit, a valid 0NSN number could also be a mobile (0400000000) or a local number in a different area code (0299999999, 0799999999 or 0899999999).

We could create a dialplan entry for each, our we could expand upon our regex to match all these scenarios.

So let’s update our regex to match anything starting with 0 followed by either a 2, 3, 4, 7 or 8, and then 8 digits after that. 

([0])([23478]\d{8})

Now let’s update the database so that once we’re matched we’ll just prefix 61 and the output of regex group 2.

Again we’ll do a dialplan reload and a dialplan dump to check everything.

Now let’s run through our examples to check they correctly translate:

And there you go, we’re matched and the 0NSN formatted number was translated to E.164.

Adding to Kamailio Routing

So far we’ve just used kamcmd’s dialplan.translate function to test our dialplan rules, now let’s actually put them into play.

For this we’ll use the function

dp_translate(id, [src[/dest]])

dp_translate is dialplan translate. We’ll feed it the dialplan id (id) and a source variable and destination variable. The source variable is the equivalent of what we put into our kamcmd dialplan.translate, and the destination is the output.

In this example we’ll rewrite the Request URI which is in variable $rU, we’ll take the output of $rU, feed it through dialplan translate and save the output as $rU (overwrite it).

Let’s start with the Speed Dial example we setup earlier, and put that into play.

   if(method=="INVITE"){
                xlog("rU before dialplan translation is $rU");
                dp_translate("2", "$rU/$rU");
                xlog("rU after dialplan translation is $rU");
}

The above example will output our $rU variable before and after the translation, and we’re using Dialplan ID 2, which we used for our speed dial example.

So let’s send an INVITE from our Softphone to our Kamailio instance with to 101, which will be translated to 0212341234.

Before we do we can check it with Kamcmd to see what output we expect:

dialplan.translate 2 s:101

Let’s take a look at the out put of Syslog when we call 101.

But our INVITE doesn’t actually go anywhere, so we’ll add it to our dispatcher example from the other day so you can see it in action, we’ll relay the INVITE to an active Media Gateway, but the $rU will change.

   if(method=="INVITE"){
                xlog("rU before dialplan translation is $rU");
                dp_translate("2", "$rU/$rU");
                xlog("rU after dialplan translation is $rU");
                ds_select_dst(1, 12);
                t_on_failure("DISPATCH_FAILURE");
                route(RELAY);
        }

Let’s take a look at how the packet captures now look:

UA > Kamailio: INVITE sip:101@kamailio SIP/2.0
Kamailio > UA: SIP/2.0 100 trying -- your call is important to us
Kamailio > MG1: INVITE sip:0212341234@MG1 SIP/2.0

So as you can see we translated 101 to 0212341234 based on the info in dialplan id 2 in the database.

That’s all well and good if we dial 101, but what if we dial 102, there’s no entry in the database for 102, as we see if we try it in Kamcmd:

dialplan.translate 2 s102

And if we make a call to 102 and check syslog:

rU before dialplan translation is 102
rU after dialplan translation is 102

Let’s setup some logic so we’ll respond with a 404 “Not found in Dialplan” response if the dialplan lookup doesn’t return a result:

if(dp_translate("2", "$rU/$rU")){
  xlog("Successfully translated rU to $rU using dialplan ID 2");
}else{
  xlog("Failed to translate rU using dialplan ID 2");
  sl_reply("404", "Not found in dialplan");
  exit;
}

By putting dp_translate inside an if we’re saying “if dp_translate is successful then do {} and the else will be called if dp_translate wasn’t successful.

Let’s take a look at a call to 101 again.

UA > Kamailio: INVITE sip:101@kamailio SIP/2.0
Kamailio > UA: SIP/2.0 100 trying -- your call is important to us
Kamailio > MG1: INVITE sip:0212341234@MG1 SIP/2.0

Still works, and a call to 102 (which we don’t have an entry for in the dialplan).

UA > Kamailio: INVITE sip:102@kamailio SIP/2.0
Kamailio > UA: SIP/2.0 404 Not found in dialplan

Hopefully by now you’ve got a feel for the dialplan module, how to set it up, debug it, and use it.

As always I’ve put my working code on GitHub.

SIP Extensions – 100rel SIP (RFC3262)

When a final response, like a 200 OK, or a 404, etc, is sent, the receiving party acknowledges that it received this with an ACK.

By provisional responses, such as 180 RINGING, are not acknowledged, this means we have no way of knowing for sure if our UAC received the provisional response.

The issues start to arise when using SIP on Media Gateways or inter-operating with SS7 / ISUP / PSTN, all of which have have guaranteed delivery of a RINGING response, but SIP doesn’t. (Folks from the TDM world will remember ALERTING messages)

The IETF saw there was in some cases, a need to confirm these provisional responses were received, and so should have an ACK.

They created the Reliability of Provisional Responses in the Session Initiation Protocol (SIP) under RFC3262 to address this.

This introduced the Provisional Acknowledgement (PRACK) and added the 100rel extension to Supported / Requires headers where implemented.

This means when 100rel extension is not used a media gateway that generates a 180 RINGING or a 183 SESSION PROGRESS response, sends it down the chain of proxies to our endpoint, but could be lost anywhere along the chain and the media gateway would never know.

When the 100rel extension is used, our media gateway generates a 18x response, and forwards it down the chain of proxies to our endpoint, and our 18x response now also includes a RSeq which is a reliable sequence number.

The endpoint receives this 18x response and sends back a Provisional Acknowledgement or PRACK, with a Rack header (Reliable Acknowledgement) header with the same value as the RSeq of the received 18x response.

The media gateway then sends back a 200 OK for the PRACK.

In the above example we see a SIP call to a media gateway,

The INVITE is sent from the caller to the Media Gateway via the Proxy. The caller has included value “100rel” in the Supported: header, showing support for RFC3262.

The Media gateway looks at the destination and knows it needs to translate this SIP message to a different a different protocol. Our media gateway is translating our SIP INVITE message into it’s Sigtran equivalent (IAM), and forward it on, which it does, sending an IAM (Initial Address Message) via Sigtran.

When the media gateways gets confirmation the remote destination is ringing via Sigtran (ACM ISUP message), it translates that to it’s SIP equivalent message which is, 180 RINGING.

The Media Gateway set a reliable sequence number on this provisional response, contained in the RSeq header.

This response is carried through the proxy back to the caller, who signals back to the media gateway it got the 180 RINGING message by sending a PRACK (Provisional ACK) with the same RSeq number.

The call is eventually answered and goes on.

Kamailio Bytes – Dispatcher Module

The Dispatcher module is used to offer load balancing functionality and intelligent dispatching of SIP messages.

Let’s say you’ve added a second Media Gateway to your network, and you want to send 75% of traffic to the new gateway and 25% to the old gateway, you’d use the load balancing functionality of the Dispatcher module.

Let’s say if the new Media Gateway goes down you want to send 100% of traffic to the original Media Gateway, you’d use the intelligent dispatching to detect status of the Media Gateway and manage failures.

These are all problems the Dispatcher Module is here to help with.

Before we get started….

Your Kamailio instance will need:

  • Installed and running Kamailio instance
  • Database configured and tables created (We’ll be using MySQL but any backed is fine)
  • kamcmd & kamctl working (kamctlrc configured)
  • Basic Kamailio understanding

The Story

So we’ve got 4 players in this story:

  • Our User Agent (UA) (Softphone on my PC)
  • Our Kamailio instance
  • Media Gateway 1 (mg1)
  • Media Gateway 2 (mg2)

Our UA will make a call to Kamailio. (Send an INVITE)

Kamailio will keep track of the up/down status of each of the media gateways, and based on rules we define pick one of the Media Gateways to forward the INVITE too.

The Media Gateways will playback “Media Gateway 1” or “Media Gateway 2” depending on which one we end up talking too.

Configuration

Parameters

You’ll need to load the dispatcher module, by adding the below line with the rest of your loadmodules:

loadmodule "dispatcher.so"

Next we’ll need to set the module specific config using modparam for dispatcher:

modparam("dispatcher", "db_url", DBURL)                 #Use DBURL variable for database parameters
modparam("dispatcher", "ds_ping_interval", 10)          #How often to ping destinations to check status
modparam("dispatcher", "ds_ping_method", "OPTIONS")     #Send SIP Options ping
modparam("dispatcher", "ds_probing_threshold", 10)      #How many failed pings in a row do we need before we consider it down
modparam("dispatcher", "ds_inactive_threshold", 10)     #How many sucessful pings in a row do we need before considering it up
modparam("dispatcher", "ds_ping_latency_stats", 1)      #Enables stats on latency
modparam("dispatcher", "ds_probing_mode", 1)            #Keeps pinging gateways when state is known (to detect change in state)

Most of these are pretty self explanatory but you’ll probably need to tweak these to match your environment.

Destination Setup

Like the permissions module, dispatcher module has groups of destinations.

For this example we’ll be using dispatch group 1, which will be a group containing our Media Gateways, and the SIP URIs are sip:mg1:5060 and sip:mg2:5060

From the shell we’ll use kamctl to add a new dispatcher entry.

kamctl dispatcher add 1 sip:mg1:5060 0 0 '' 'Media Gateway 1'
kamctl dispatcher add 1 sip:mg2:5060 0 0 '' 'Media Gateway 2'

Alternately you could do this in the database itself:

INSERT INTO `dispatcher` (`id`, `setid`, `destination`, `flags`, `priority`, `attrs`, `description`) VALUES (NULL, '1', 'sip:mg3:5060', '0', '0', '', 'Media Gateway 3'); 

Or you could use Siremis GUI to add the entries.

You can use kamctl to show you the database entries:

kamctl dispatcher show

A restart to Kamailio will make our changes live.

Destination Status / Control

Checking Status

Next up we’ll check if our gateways are online, we’ll use kamcmd to show the current status of the destinations:

kamcmd dispatcher.list

Here we can see our two media gateways, quick response times to each, and everything looks good.

Take a note of the FLAGS field, it’s currently set to AP which is good, but there’s a few states:

  • AP – Active Probing – Destination is responding to pings & is up
  • IP – Inactive Probing – Destination is not responding to pings and is probably unreachable
  • DX – Destination is disabled (administratively down)
  • AX – Looks like is up or is coming up, but has yet to satisfy minimum thresholds to be considered up (ds_inactive_threshold)
  • TX – Looks like or is, down. Has stopped responding to pings but has not yet satisfied down state failed ping count (ds_probing_threshold)

Adding Additional Destinations without Restarting

If we add an extra destination now, we can add it without having to restart Kamailio, by using kamcmd:

kamcmd dispatcher.reload

There’s some sanity checks built into this, if the OS can’t resolve a domain name in dispatcher you’ll get back an error:

Administratively Disable Destinations

You may want to do some work on one of the Media Gateways and want to nicely take it offline, for this we use kamcmd again:

kamcmd dispatcher.set_state dx 1 sip:mg1:5060

Now if we check status we see MG1’s status is DX:

Once we’re done with the maintenance we could force it into the up state by replacing dx with ap.

It’s worth noting that if you restart Kamailio, or reload dispatcher, the state of each destination is reset, and starts again from AX and progresses to AP (Up) or IP (Down) based on if the destination is responding.

Routing using Dispatcher

The magic really comes down to single simple line, ds_select_dst();

The command sets the destination address to an address from the pool of up addresses in dispatcher.

You’d generally give ds_select_dst(); two parameters, the first is the destination set, in our case this is 1, because all our Media Gateway destinations are in set ID 1. The next parameter is is the algorithm used to work out which destination from the pool to use for this request.

Some common entries would be random, round robin, weight based or priority value.

In our example we’ll use a random selection between up destinations in group 1:

if(method=="INVITE"){
   ds_select_dst(1, 4);    #Get a random up destination from dispatcher
   route(RELAY);           #Route it
}

Now let’s try and make a call:

UA > Kamailio: SIP: INVITE sip:1111111@Kamailio SIP/2.0

Kamailio > UA: SIP: SIP/2.0 100 trying -- your call is important to us

Kamailio > MG1: SIP: INVITE sip:1111111@MG1 SIP/2.0

MG1 > Kamailio: SIP: SIP/2.0 100 Trying

Kamailio > UA : SIP: SIP/2.0 100 Trying

MG1 > Kamailio: SIP: SIP/2.0 200 OK

Kamailio > UA : SIP: SIP/2.0 200 OK

And bingo, we’re connected to a Media Gateway 1.
If I try it again I’ll get MG2, then MG1, then MG2, as we’re using round robin selection.

Destination Selection Algorithm

We talked a little about the different destination select algorithm, let’s dig a little deeper into the common ones, this is taken from the Dispatcher documentation:

  • “0” – hash over callid
  • “4” – round-robin (next destination).
  • “6” – random destination (using rand()).
  • “8” – select destination sorted by priority attribute value (serial forking ordered by priority).
  • “9” – use weight based load distribution.
  • “10” – use call load distribution. 
  • “12” – dispatch to all destination in setid at once

For select destination sorted by priority (8) to work you need to include a priority, you can do this when adding the dispatcher entry or after the fact by editing the data. In the below example if MG1 is up, calls will always go to MG1, if MG1 is down it’ll go to the next highest priority (MG2).

The higher the priority the more calls it will get

For use weight based load distribution (9) to work, you’ll need to set a weight as well, this is similar to priority but allows you to split load, for example you could put weight=25 on a less powerful or slower destination, and weight=75 for a faster or more powerful destination, so the better destination gets 75% of traffic and the other gets 25%. (You don’t have to do these to add to 100%, I just find it easier to think of them as percentages).

use call load distribution (10) allows you to evenly split the number of calls to each destination. This could be useful if you’ve got say 2 SIP trunks with x channels on each trunk, but only x concurrent calls allowed on each. Like adding a weight you need to set a duid= value with the total number of calls each destination can handle.

dispatch to all destination in setid at once (12) allows you to perform parallel branching of your call to all the destinations in the address group and whichever one answers first will handle the call. This adds a lot of overhead, as for each destination you have in that set will need a new dialog to be managed, but it sure is quick for the user. The other major issue is let’s say I have three carriers configured in dispatcher, and I call a landline.

That landline will receive three calls, which will ring at the same time until the called party answers one of the calls. When they do the other two calls will stop ringing. This can get really messy.

Managing Failure

Let’s say we try and send a call to one of our Media Gateways and it fails, we could forward that failure response to the UA, or, better yet, we could try on another Media Gateway.

Let’s set a priority of 10 to MG1 and a priority of 5 to MG2, and then set MG1 to reject the call.

We’ll also need to add a failure route, so let’s tweak our code:

   if(method=="INVITE"){
                ds_select_dst(1, 12);
                t_on_failure("DISPATCH_FAILURE");
                route(RELAY);
        }

And the failure route:

route[DISPATCH_FAILURE]{
        xlog("Trying next destination");
        ds_next_dst();
        route(RELAY);

}

ds_next_dst() gets the next available destination from dispatcher. Let’s see how this looks in practice:

 
UA > Kamailio: SIP: INVITE sip:1111111@Kamailio SIP/2.0

Kamailio > UA: SIP: SIP/2.0 100 trying -- your call is important to us

Kamailio > MG1: SIP: INVITE sip:1111111@MG1 SIP/2.0

MG1 > Kamailio: SIP: SIP/2.0 100 Trying

MG1 > Kamailio: SIP: SIP/2.0 404 Not Found

Kamailio > MG1 : SIP: SIP/2.0 ACK

Kamailio > MG2: SIP: INVITE sip:1111111@MG2 SIP/2.0

MG2 > Kamailio: SIP: SIP/2.0 100 Trying

MG2 > Kamailio: SIP: SIP/2.0 200 OK

Kamailio > UA : SIP: SIP/2.0 200 OK

Here’s a copy of my entire code as a reference.

IBM Watson – Speech to Text (SST)

I’ve been using IBM’s Watson’s Speech to Text engine for transcribing call audio, some possible use cases are speech driven IVRs, Voicemail to Email transcription, or making Call Recordings text-searchable.

The last time I’d played with Speech Recognition on Voice Platforms was in 2012, and it’s amazing to see how far the technology has evolved with the help of AI.

IBM’s offering is a bit more flexible than the Google offering, and allows long transcription (>1 minutes) without uploading the files to external storage.

Sadly, Watson doesn’t have Australian language models out of the box (+1 point to Google which does), but you can add Custom Language Models & train it.

Input formats support PCM coded data, so you can pipe PCMA/PCMU (Aka G.711 µ-law 7 a-law) audio straight to it.

Getting Setup

The first thing you’re going to need are credentials.

For this you’ll need to sign into https://console.bluemix.net

Select “Speech to Text” and you can view / copy your API key from the Credentials header.

Once you’ve grabbed your API key we can start transcribing.

Basic Transcription

I’ve got an Asterisk instance that manages Voicemail, so let’s fire the messages to Watson and get it to transcribe the deposited messages:

curl -X POST -u "apikey:yourapikey" --header "Content-Type: audio/wav" --data-binary @msg0059.wav "https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?model=en-US_NarrowbandModel"
“confidence”: 0.831,
“transcript”: “hi Nick this is Nick leaving Nick a test voice mail “

Common Transcription Options

speaker_labels=true

Speaker labels enable you to identify each speaker in a multi-party call.

This makes the transcription read more like a script with “Speaker 1: Hello other person” “Speaker 2: Hello there Speaker 1”, makes skimming through much easier.

timestamps=true

Timestamps timestamp each word based on the start of the audio file,

This reads poorly in CURL but when used with speaker_labels allows you to see the time and correlate it with a recording.

One useful use case is searching through a call recording transcript, and then jumping to that timestamp in the audio.

For example in a long conference call recording you might be interested in when people talked about “Item X”, you can search the call recording for “Item” “X” and find it’s at 1:23:45 and then jump to that point in the call recording audio file, saving yourself an hour and bit of listening to a conference call recording.

Audio formats (content types)

Unfortunately Watson, like GCP, only has support for MULAW (μ-law compounding) and not PCMA as used outside the US.

Luckily it has wide ranging WAV support, something GCP doesn’t, as well as FLAC, G.729, mpg, mp3, webm and ogg.

Speech Recognition Model

Watson has support for US and GB variants of speech recognition, wideband, narrowband and adaptive rate bitrates.

word_confidence=true

Per word confidence allows you to see a per word confidence breakdown, so you can mark unknown words in the final output with question marks or similar to denote if it’s not confident it has transcribed correctly.

Voice and mail Watson wasn’t sure of

max_alternatives

This allows you to specify on either a per-word basis or as a whole, the maximum number of alternatives Watson has for the conversation.

This is Neck a test voicemail

Kamailio 101 – Part 10 – Recap

So now we’ve made a functional bare-bones PBX using Kamailio, and we’ve touched upon a few of the key functions Kamailio can do, but let’s go over them again to recap.

Future Kamailio posts I’ll be talking about using specific modules and using Kamailio for specific use cases, such as load balancing traffic between carriers and monitoring their up/down status, scaling Asterisk by front ending it with Kamailio, adding/rewriting/removing headers with Kamailio and stateful vs stateless operation, so stick around, but here’s an overview of what we’ve learned.

Routing Blocks & Structure

Routing blocks make code cleaner and allow reuse of the same blocks, you can call a route to do a function without having to write out what to do every time.

In the below example we go from the default “request_route” block, where all new messages start, and jump to the block named “RESPOND_501”.

request_route {
        route(RESPOND_501);    #Jump to the RESPOND_501 block
}

route[RESPOND_501]{
        sl_reply("501", "Not Implemented");   #Send 501 reply
}

From RESPOND_501 we send a stateless reply to whoever sent us the message.

We introduced xlog to write data to the log, and then viewed that data in Syslog.

xlog("Hello, I am in the request_route");

We determined the SIP method of the request using

if(method=="INVITE"){

to tailor our responses based on the method used.

Kamailio as a SIP REGISTRAR

This was covered in Part 4 of Kamailio 101,

The main takeaway from this was the use of

save("location");

We expanded this to only for REGISTER messages and to stop processing after the location was saved.

        if(method=="REGISTER"){
                save("location");
                exit;
        }

Then we used kamcmd for the first time to dump the user location so we could see our registered endpoints.

 
kamcmd ul.dump

First Call

Building upon having saved the location of registered endpoints we looked up the locations we had Address on Record entries for and forwarded the traffic to them, to allow to make calls between registered UAs.

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

Quickly we saw issues with this though, if the UA we wanted to reach wasn’t registered, if we hung up before the called party answered, and a host of other scenarios,

We addressed the user not registered scenario and then talked briefly about the routing blocks that come with Kamailio and how they’ll be our savoir.

Reusing Code

We put back the default routing blocks that come with Kamailio after highlighting the difficulties with trying to do everything yourself.

We talked about how route(RELAY); will better handle message relaying than tm_relay(); alone, and using route(WITHINDLG); to manage within dialog requests & branching.

Security in Theory

We talked about the importance of AAA (Authentication, Authorisation & Accounting) and the perils of naming your son Bobby Droptables.

Security in Practice

We put what we talked about into practice.

Using a database back-end (MySQL) we setup our UAs to be authenticated when registering and before making calls, and listed our Carrier’s IPs so we’d only accept inbound calls from our carrier, not random folks online.

We used kamctl to add users and manage address groups.

route(REQINIT); was used to manage traffic validation.

auth_challenge(“$fd”, “0”); was used to authenticate UAs

and (allow_source_address(“200”)) was used to authenticate carriers.

Adding Carrier Links

Lastly we put all the pieces together and put in a carrier link / trunk to allow calls to be made / from to the PSTN using rewritehost()

So where to now?

You’ve made it to the end of this tutorial, but chances are it’s only the beginning of your Kamailio journey.

There’s a lot of posts on this site regarding Kamailio and it’s many modules, uses and functionalities, explore and good luck!

Kamailio 101 – Part 9 – Adding Carrier Links

So by now we’ve secured our box and we’re able to route calls between registered endpoints.

Next up we’ll need to add some external connectivity, meaning we can reach destinations that aren’t directly registered on our Kamailio instance.

We’ve signed up with imaginary carrier at “imaginarycarrier.com” so we can make / receive calls from the PSTN using them as a trunk. They’ll be authenticating us based on our Source IP which we’ve let them know.

These days you’d generally authenticate with a carrier by sending a REGISTER message to your carrier so they know your Address on Record, but to keep it simple we won’t be registering to a carrier, as that introduces another Kamailio module.

At the moment, when we receive an INVITE where the destination isn’t registered, we respond with a 404:

sl_reply("404", "User not Registered");     #If looking up location fails reply with 404

But now we’ve got a carrier we can send calls to if the destination isn’t on our PBX, so we won’t need to reply 404 anymore for calls from our users.

So let’s only give the 404 reply to calls in from our carrier (inbound calls), and instead of giving a 404 response to callers from within our organisation, let’s send the calls to the carrier to make an outbound call.

This means calls to any destination that isn’t registered on Kamailio will go to the Carrier.

But we’ll need to still respond with the 404 response if a carrier sends us a call to a destination that isn’t registered, like an inbound call to a user who isn’t registered.

route[ONNETINVITE]{
          if(!lookup("location")){    #Try looking up location
                #If looking up location fails then:
                if(allow_source_address("200")){                            #Check if the request has come from a carrier
                        sl_reply("404", "User not Registered");     #If if it is from a carrier to a destination we don't have registered reply with 404
                        exit;                                       #And exit

                }else{                                              #If it's not from a carrier
                        route(TOCARRIER);                           #Route the call out to the carrier (to make an external call)
                }
        }
            route(RELAY);                  #Relay traffic to endpoint
            exit();                     #Exit

}

Next we’ll need to create our TOCARRIER route,

route[TOCARRIER]{       #Route to send calls to a carrier at 192.168.200.130
        rewritehost("imaginarycarrier.com");    #Rewrite host to be the carrier's IP
        route(RELAY);                   #Route relay

}

So let’s put this together and try and make an outbound call.

Call Flow

Outbound call to Carrier

First we see our UA make the call leg to Kamailio

UA to Kamailio: SIP: INVITE sip:61299999999@kamailio SIP/2.0

Kamailio asks the UA to authenticate itself and send that again, the UA does:

Kamailio to UA: SIP: SIP/2.0 407 Proxy Authentication Required (with challenge)

UA to Kamailio: SIP: INVITE sip:61299999999@kamailio SIP/2.0 (with auth header)

Now Kamailio has authenticated the user it attempts to lookup the location of 61299999999 in the location table.

lookup("location")

As no user is registered as 61299999999 this fails, so Kamailio checks the UA is calling from an address in Address Group 200, which we configured to contain the IP Addresses of all our Carrier’s switches that will be sending us calls.

if(allow_source_address("200"))

It’s not in address group 200, as the from address isn’t one of our Carrier’s IPs, so it ends up at

route(TOCARRIER); 

The route block itself rewrites the host part of the request to be the Carrier’s IP, and then forwards it on to the carrier.

route[TOCARRIER]{       #Route to send calls to a carrier at 192.168.200.130
        rewritehost("imaginarycarrier.com");    #Rewrite host to be the carrier's IP
        route(RELAY);                   #Route relay

}
Kamailio > Carrier: SIP: INVITE sip:[email protected] SIP/2.0

So now the INVITE has been forwarded to imaginarycarrier.com, and because we called route(RELAY); it’ll handle all the in dialog requests.

Inbound call from Carrier

So now we know how an outbound call flows, let’s look at inbound calls from the carrier.

Carrier to Kamailio: INVITE sip:61312341234@kamailio SIP/2.0 

Kamailio responds with a provisional response of 100 Trying

Kamailio to Carrier: SIP: SIP/2.0 100 trying -- your call is important to us

Now Kamailio checks to see the method type – It’s INVITE, and if the source address is in Address Group 200 (As we defined in Part 8), it is, so it calls the route(ONNETINVITE) block.

if(method=="INVITE"){
        if(allow_source_address("200")){        #If from a Carrier IP
                route(ONNETINVITE);          #Call INVITE handling bloc

Once we get to the ONNETINVITE block Kamailio tries to lookup the location, to see if there’s a device registered with a username that matches 61312341234, using:

lookup("location")

There is, so the route(relay) is called, which forwards the INVITE to the IP it has an Address on Record for for 61312341234.

Kamailio > UA: SIP: INVITE sip:[email protected]:50017;ob SIP/2.0

The INVITE is sent to the UA and route(relay) handles all the in dialog requests.

The final scenario we might have is if a call is made to 61299999999 and it’s not registered on Kamailio, let’s take a look at that signalling path:

Carrier > Kamailio: SIP: INVITE sip:61299999999@Kamailio SIP/2.0

The carrier sends the INVITE to Kamailio, Kamailio calls lookup location, which fails to return a location as 61299999999 isn’t registered.

Next we check to see if the call is from a carrier by checking if the source address of the INVITE is equal to address group 200.

if(allow_source_address("200")){                            #Check if the request has come from a carrier
                        sl_reply("404", "User not Registered");     #If if it is from a carrier to a destination we don't have registered reply with 404
                        exit;         

As the source address is in address group 200, the carrier gets the 404 “User not Registered” reply, as we see in the packet capture:

Kamailio > Carrier: SIP: SIP/2.0 404 User not Registered

And that’s it.

I’ve put the complete code from this on Github.

In the next and final part, we’ll recap what we’ve learned.

Next Post – Kamailio 101 – Tutorial 10 – Recap

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Message Amplification & UDP Flooding with SIP

SIP was written to be fast and resonably lightweight.

Motorola StarTAC

At the time SIP was created in 1996, Motorola just had launched it’s first flip phone, the web was only 100,000 websites online and I was playing Pokémon.

Security wasn’t so much an afterthought, but rather not something everyone was as conscious of as they are today.

UDP is the protocol of choice for most SIP deployments, which opens it up for Message Amplification attacks.

As the world saw a few years back with DNS Amplification attacks (Good explanation of how Message Amplification works courtesy of Cloudflare), amplification attacks are enabled by DNS requests being smaller than DNS responses, and carrier networks that don’t verify the source of their traffic allowing someone to request a DNS lookup saying they’re from an IP that isn’t theirs, and that IP getting flooded.

SIP is vulnerable to this too, not exactly zero-day exploits, but something that hasn’t been looked at outside of the theoretical sphere, so I thought I’d roll up my sleeves and see how bad it can look.

For starters it’s worth remembering for a Message Amplification attack to work, it’s got to amplify. RF Engineering will teach you that amplification is the ratio of power in to power out, and it’s the same for Message Amplification, the size of the packet we send has to be smaller than the packet received, else we’re just using someone else to do our dirty work, but not amplifying.

Typical Response Sizes

These are small SIP messages I created in Python using sockets, they’re not the absolute smallest you could go, but they were as small as I could go and still get through the basic packet validation / sanity checks.

Some SIP Proxies drop traffic missing required headers while some don’t, I’ve included the required headers.

I’ve pointed the traffic at a Kamailio instance and measured the bytes sent vs bytes returned.

MethodRequest Size (bytes)Response Size (bytes)Gain
OPTIONS1682091.2x
REGISTER3804111x
INVITE1973771.9x
Content Length Mismatch339
400 Missing Required Header in Request300
Max Forwards Exceeded213

So the best we can get is a packet 1.9 times the size of the packet we put in, which means SIP isn’t the best for Message Amplification attacks, but passable, so long as it keeps responding.

INVITE gets our best amplification and we can tune this to get the request smaller.

The Workhorse

Carrier grade SIP servers are pretty powerful machines, able to handle huge amounts of traffic, quite literally hundreds of millions a day, generally split across geographic areas and clustered, all on high quality low loss, low latency IP links.

If you have 20,000 subscribers sending a keep alive every 60 seconds, you’re at 72 million dialogs consisting of two packets each (144,000,000 SIP messages).

So after some stripping down I managed to get a valid INVITE that would be responded too with an auth challenge (407 Proxy Authentication Required) which was 125 bytes on the wire, while the response was 330, giving me a gain of 2.64 times what I put in. (I send 125 bytes, I get back 330)

The Setup

We’ve got 3 IPs we’re dealing with here,

Our victim is on 10.0.1.15. UDP port 5060 won’t even be open for this poor fellow, but he’ll get flooded.

Next is our attacker who’s machine is on 10.0.1.12, but claiming their source IP is 10.0.1.15 (the Victim’s IP)

From here our attacker will be sending SIP traffic to 10.0.1.110 (our “carrier” / SIP server), which will send it’s responses to the victim. I’ve spun up an Asterisk instance because it’s the voice eng version of sticky tape, I’d love to test this against something a Broadsoft platform, but licences are hard to come by.

I setup the Asterisk instance to be single threaded, on a box with just enough resources to run, to try this small scale.

I wrote a threaded Python script that will ramp up the number of messages exponentially, we’ll start by sending one message per second, then two messages per second, and so on.

And we’ll do this until something breaks.

The Results

In short – inconclusive at first, but kinda scary after that.

Asterisk died really quickly. “Exceptionally long queue length” popped up after the first second. Interestingly, the box eventually came good and actually replied to every one of our requests, and even sent a BYE. Cute.

So I modified the script to be a bit less aggressive, a random wait time between 0 and 1 seconds between loops for each thread.

Struggling under the load.

I got about 60 seconds in before Asterisk really stopped responding to traffic.

So I tweaked my script again, enabled multi threading on the “carrier” and tried again.

So here’s the best rate (packets per second) I could get after a lot of tweaking:

Packets per Second – Red = Sent, Green = Received

Peak Receive: 14,000 packets per second
Peak Send: 20,000 packets per second

In terms of packet size – what we really care about, the results were actually pretty promising:

Click for full size

Peak receive rate of 67Mbps, for which we were putting in ~25Mbps.

So can SIP be used for message amplification attacks? Sure.

Is it particulary practical? Not really. There’s easier targets out there for the time being, so VoIP will be spared the worst of it.

But for a carrier weaponisation of carrier SIP server should be a real fear.

Protection for Carriers

Don’t use UDP for your SIP traffic.

It’s easier said than done, I know… But the reasoning for putting SIP on UDP was primarily speed and limited bandwidth, but with more and more fibre in the ground it’s no longer the case.

SIP over TCP (better yet use it as an excuse to move to TLS), will protect you from some of these attacks.

Flood protection is built into most SBCs these days, if your box is being used to hit a specific target, the source IP will be masquerading as the target. So blocking that and not responding is your best bet. Lots of SBCs still respond with a 4xx “Rate Limiting” response instead of just dropping the traffic, ideally you’d disable the nice “Rate liming” response and just drop the traffic.

Traffic modeling, GeoIP blocking and rate limiting per IP & destination port will also help, as well as monitoring.

Ultimately you can’t stop spoofed UDP traffic coming into your network, but you can stop UDP traffic leaving your network, and if everyone did that we wouldn’t be in this mess.

UDP spoofing is made possible by networks that don’t verify that the traffic that’s leaving their network is traffic that is sourced from your network.

Your core routers know what IPs are assigned to your network, and should be configured to drop traffic that’s leaving the network but not coming from those IPs.

IETF came up with this solution, and it’s built into all major router OSes:

Network Ingress Filtering:
Defeating Denial of Service Attacks which employ
IP Source Address Spoofing


Wireshark trace showing a "401 Unauthorized" Response to an IMS REGISTER request, using the AKAv1-MD5 Algorithm

All About IMS Authentication (AKAv1-MD5) in VoLTE Networks

I recently began integrating IMS Authentication functions into PyHSS, and thought I’d share my notes / research into the authentication used by IMS networks & served by a IMS capable HSS.

There’s very little useful info online on AKAv1-MD5 algorithm, but it’s actually fairly simple to understand.

RFC 2617 introduces two authentication methods for HTTP, one is Plain Text and is as it sounds – the password sent over the wire, the other is using Digest scheme authentication. This is the authentication used in standard SIP MD5 auth which I covered ages back in this post.

Authentication and Key Agreement (AKA) is a method for authentication and key distribution in a EUTRAN network. AKA is challenge-response based using symmetric cryptography. AKA runs on the ISIM function of a USIM card.

I’ve covered the AKA process in my post on USIM/HSS authentication.

The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN token from our AKA response. (Often called the Authentication Vectors).

That’s it!

It’s put in the SIP 401 response by the S-CSCF and sent to the UE. (Note, the Cyperhing Key & Integrity Keys are removed by the P-CSCF and used for IPsec SA establishment.

Wireshark trace showing a "401 Unauthorized" Response to an IMS REGISTER request, using the AKAv1-MD5 Algorithm
Click for Full Size version of this image

RTP – More than you wanted to know

There’s often a lot of focus on the signalling side of VoIP, but the media RTP (Real Time Protocol) is the protocol that actually transfers the voice over IP.

RTP is designed to be bare-bones and adaptable. A RTP packet doesn’t have pretty RFC822 style headers that are easy to read, but rather a fixed length formatted string of Hex values, with different positions denoting different values to keep the size down. There’s no checksum in the protocol, error correction, or anything else that might add overhead.

RTP is the transport of the media, it contains the media as a payload inside, but it’s up to the system creating the RTP packets as to what’s inside the payload. The header of an RTP packet does denote the payload type, but RTP has no way to verify that the contents of the payload match the payload type specified.

First defined in 1996, RTP hasn’t seen much evolution, primarily owing to it’s design being as lightweight and simple as possible. RTP  had a bit of an update in 2003 under RFC 3550, but that only touched upon changes to the timer algorithm. (That deserves a post of it’s own) There have been pushes in the past for a further cut down RTP with fewer fields, as being fixed-width some fields when not used are just padded with 00000s so the packet size on the wire remains the same regardless.

RTP is generally carried over UDP but it will run over TCP. Running your RTP traffic over TCP can be pretty costly due time-sensitive nature and the sheer volume of packets you’ll be seeing. If if you’re packetizing a G. 711 a-law call (sampled every 20 ms at 8,000 Hz) that’s a packet every 160ms – 375 packets each direction per minute on UDP. If you were to use TCP to transport these packets you’d need to add the 3-way-handshake giving you 3 times as many packets at 1125 packets per minute, not to mention much more jitter and PDV caused by 3 times the load.

Header Fields

The data in RTP headers is in Hexidecimal format, which keeps it’s size down and processing minimal, but also means it’s pretty rigidly defined in terms of spacing etc, it’s not like a SIP header which might look like To: [email protected]\n\r, this wastes precious space on the wire to add the “To: ” and the “\n\r”, so instead it’s fixed positioning all the way with just the data.

If you haven’t had the joys of working with 90’s data files in fixed width formats, the premise is fairly simple; each value has a start and end position within a document. More info on creating RTP headers can be found in the post “Crafting RTP Packets”.

Generally when working with RTP packets on the wire, all these headers are joined one after another, broken up into blocks of 8 (octets) and then converted to HEX, all to ensure it’s as small as practical when it’s transmitted.

Version (2 bits)

RTP has had two published versions, but in both the value to put in this field is 10 (Binary 2). If you are reading this on a machine that isn’t running DOS, there’s a good chance you’ll only see version 2.
If your traffic routed through a wormhole, or your network has some serious latency issues (Several decades) you could find yourself working with a media stream pre-1996 (hopefully not) using the draft version of RTP and has a value of 01 (Binary 1). But if you were dealing with RTP’s predecessor; vat, this value would be 0. (vat and RTP aren’t the same).

Padding (1 bit)

If you’re encrypting your packets you may need them to be a specific size, and for this you may need to pad the packets out at the end. To do this you’d enable padding by putting a 1 here and then specifying at the end of the payload how many octets of padding you need. In most cases this isn’t used though, and this value will be 0.

Extension (1 bit)

Unlike a lot of RFC documents that specify “must” “shall” etc, RTP was defined more as a guideline, a template for implementers. The extension field was added to allow individual implementations with additional custom data in the headers, while being ignored by other network elements that don’t support the extensions. If this is enabled it’s followed by 16 bits of you-decide.
However like the padding value, this is likely to be 0.

Contributing Source (CSRC) Count (4 bits)

RTP allows you to have multiple Contributing Sources. This means on a 3-way call, instead of your switch taking the two audio streams, joining them together (mux) and sending each endpoint a single media stream, you could have direct-media from one of the parties you’re on a 3 way call with, and the other party you’re on a call with added as a Contributing Source.
Again, it’s likely this is 0000.

Marker (1 bit)

If the marker bit is set or not is actually up to the underlying protocol. In video the marker bit is often used to signify the image has significantly changed, and in audio it’s generally to denote the end of silence & the start of talking – called a “talkspurt”.

Payload Type (7 bits)

The payload type is what specifies the contents of the payload. In voice terms this means the codec we’re using. RFC3551 defines some predefined payload type definitions and it’s Payload Type code.

Your values might not appear in the RFC3551 definitions if you’re using a non-standard codec, and that’s Ok. RTP could be used to play video games or pilot an RC plane, it’s really just a protocol to carry a stream of real time data quickly from point A to point B with as little overhead as possible.

PCMA / PCMU is king here thanks to it G.711’s widespread adoption due to being the codec used in TDM, and the fact you don’t need to transcode PCM to bring the traffic into the network, or compress it from a TDM source. TDM / circuit switched services are way less common on the network edge these days,  but G.711 still holds on as the defacto standard.

So for a G711 a-law (PCMA) payload this value would be 8, which is 0001000 in Binary (it’s also equivalent to 1000 in binary but we need to fill all 7 bits because we’re using fixed-width formatting, so we prefix it with zeros, if we were using GSM, which is 8 in decimal and 11 in binary, we’d format is 0000011)

For the full list of Payload types check out IANA’s Real-Time Transport Protocol (RTP) Parameters.

Sequence Number (16 bits)

The sequence number is a supposedly random number that increments by 1 for each packet sent.
This allows the receiving party to calculate packet loss, because if you receive packets with the sequence number 1,2,3,5,6 you know you’ve missed packet 4.
It also allows us to calculate our packet delay variation (PDV), and helps our jitterbuffer re-assemble packets, if we receive packets 1,3,2,4,5,6 we can see they’re out of sequence and know to play them back in the order 1,2,3,4,5,6, not the order we received them.

The sequence numbers are supposed to be random. By having this as a random number it adds an extra unknown part of the packet for someone trying to break any crypto on top to guess. Polycom however just start all theirs at 0. (Slow clap)

This is a 16 bit number, so like the payload type we’ll have to convert it from decimal to binary, then pad it to be 16 bits. So if our starting sequence number is 1234 we’d have to convert it to binary (10011010010) and then pad it to 16 characters (0000010011010010)

Timestamp (32 bits)

The timestamp, like the sequence number is supposed to begin with a random number, and then increased by the sampling instances between packets “monotonically and linearly in time”. In essence it means a random starting number + time between packets.

The value increments by the packetization time (ptime) in seconds x bandwidth in Hz.
So for a call with a ptime of 20ms at 8Khz this would be:

0.020 x 8000 = 160, so increment by 160 each packet.

Having an accurate source for the timestamp allows accurate stats to be generated for PDV, jitter etc, without this value being accurate your RTCP values will always appear off, even if the audio is fine.

If we wanted a timestamp of 837026880, we’d need to convert it to Binary (110001111001000000010001000000) and pad it to ensure it’s 32 bits long (this value already is so no need to pad).

SSRC (Synchronization Source Identifier) (32 bits)

The SSRC is like a Call-ID, a unique value that identifies one RTP stream from another. If you had two packets to the same port, from the same IP, with roughly the same sequence number & timestamp you’d need a way to determine which RTP stream is for which session. This is where the SSRC comes in, a unique identifier that identifies one RTP stream from the others.

To keep it random the spec’ even suggests ways to generate a random value based on other values.

If we wanted to use 185755418 as or SSRC we’d need to convert it to Binary (1011000100100110011100011010) and pad it to 32 bits (00001011000100100110011100011010)

CSRC List (Contributing source list) (0 to 15 individual 32 bit values)

This contains a list of the contributing sources. Depending on how many sources were specified in your CSRC Count, this can have any number of items from 0 to 15. So if you had one contributing source in the CSRC Count, then you’d have 1x 32 bit value to specify the details of the SSRC identifiers of the sources.

This will always not exist if the CSRC Count is 0.

Payload

The payload is whatever you want it to be, so long as you specify it in the Payload Type field, and pad it if enabled, any data can be put here.

 

Further Reading

I got a copy of the Colin Perkin’s book RTP: Audio and Video for the Internet, which covers everything you need to ever know about RTP. I’d highly recommend it. It was written in 2003, is still just as relevant as more and more traffic moves off circuit switched into packet switched.

Kamailio 101 – Part 8 – Security in Practice

In our last post we went over all the theory, now let’s get started implementing these security features.

Kamailio’s core is a basis to start from, but many common needs are covered by special modules that we need to load to handle certain scenarios.

In order to authenticate traffic, we’ll need to have a source of authentication info (auth_db module) and authorization (permissions module). For this we’ll be using MySQL (although you could use text files, PostGres, etc) to store both sets of data, and using phpMyAdmin to make everything a bit more accessible.

We’ll build upon our last tutorial but we’ll install MySQL and phpMyAdmin:

apt-get install mysql-server phpmyadmin

After following along the install prompts we’ll setup our database connection.

Each of the two modules we’ll be using (auth_db and permissions) require a database source. In each we could specify our database info but instead we’ll create a new variable and fill it with our database info so we only need to update it in one place.

Let’s setup a new MySQL user for our Kamailio instance (in production you’d only grant privileges on the DB we’re going to use):

mysql> CREATE USER 'kamailio'@'localhost' IDENTIFIED BY 'my5yhtY7zPJzV8vu';

mysql> GRANT ALL PRIVILEGES ON * . * TO 'kamailio'@'localhost';

mysql> FLUSH PRIVILEGES;

We’ll now use the kamdbctl tool, bundled with Kamailio to create the database tables for us:

kamdbctl create

You’ll be asked for the root password for MySQL and if you want some optional tables (we don’t just yet) and presto, all the tables are now created!

kamdbctl - Creating database tables
Using kamdbctl

We can now login with phpMyAdmin and see the tables we just added:

Adding Database Connectivity to Kamailio

The example config is designed to be nice and modular, so by simply un-commenting the WITH_MYSQL variable and setting the DBURL variable we’ll have set our MySQL database up for the modules we need.

We’ll change:

# *** To enable mysql:
#     - define WITH_MYSQL
#

To:

# *** To enable mysql:
#!define WITH_MYSQL
#

So now we’ve defined an variabled named WITH_MYSQL

You’ll see later in the config there’s conditional (if statement) that looks at if WITH_MYSQL has been defined:

# *** Value defines - IDs used later in config
#!ifdef WITH_MYSQL
# - database URL - used to connect to database server by modules such
#       as: auth_db, acc, usrloc, a.s.o.
#!ifndef DBURL
#!define DBURL "mysql://kamailio:my5yhtY7zPJzV8vu@localhost/kamailio"
#!endif
#!endif

We’ll change the !define DBURL to include the password in the Database connection string,

It breaks up like this:

mysql:// is the database type (you could use text:// for text based DB or pgsql:// for Postgres)

First part is the username:password@host/table

In this case, our username is kamailio, our password is the one we created (my5yhtY7zPJzV8vu), our host is localhost and our table is kamailio

Adding IP Authentication & Challenge / Response Auth

Like we defined the #!define WITH_MYSQL we’ll define two other blocks to add Authentication:

# *** To enable authentication execute:
#     - enable mysql
#     - define WITH_AUTH
#     - add users using 'kamctl'
#
# *** To enable IP authentication execute:
#     - enable mysql
#     - enable authentication
#     - define WITH_IPAUTH

We’ll change to:

# *** To enable authentication execute:
#     - enable mysql
#!define WITH_AUTH
#     - add users using 'kamctl'
#
# *** To enable IP authentication execute:
#     - enable mysql
#     - enable authentication
#!define WITH_IPAUTH

Adding Users & IP Addresses

Now we’ve gone and added these blocks to the code we’ll go about adding some users and IP addresses, to do this we’ll use the kamctl tool.

kamctl is another tool (like kamcmd) used to modify / change the Kamailio config, it’s a shell wrapper for managing Kamailio database among other things.

We do need to setup a few things to get the kamctl working, and to do that we’ve got to edit the kamctlrtc file in the /etc/kamailio directory, to include the details of the database we just setup.

Editing kamctlrc
Edit the file to define our database details

Adding Users

Now we can get to work adding some users from the command line:

kamctl add 61312341234 supersecretpassword
Adding users with Kamctl

Here we can see adding a user with the username 61312341234 and the password supersecretpassword. These will makeup the username and password we’ll have on our SIP endpoints. (We’ll make them match the phone numbers of our trunks to make the routing easier down the track)

We’ll add another user so we can make calls between users when we’re testing later too, we’ll add them using kamctl add USERNAME PASSWORD again.

Now if we have a look in the subscriber table in phpMyAdmin we can see the users we created:

Kamailio Subscribers Table - Users in our databasea

Adding Carrier IPs

Next we’ll add the IP Address our carrier is going to send us calls from, so we can allow them call our users.

Kamailio’s permissions module relies on address groups – This means we could have address group (we’ll just use the number 200), which we decide is for our carriers, and add all our carrier’s IP addresses in here, without needing to put each of them into the config.

We could create another address group 300 and put the subnets of our offices in there and only allow REGISTER messages from IPs in those groups.

We’ll go through how to use the groups later on, but for now we’ll just add a new IP address to group 200, with a /32 subnet mask, on port 5060 with the name “Carrier IP address” so we know what it is.

kamctl address add 200 10.0.1.102 32 5060 "Carrier IP address"

Now if we have a look in the permissions table in Kamailio you’ll see the info we just added.

By now you’ve probably caught on to the fact kamctl is just a command line tool to add data to the database, but it’s useful none the less.

Kamailio Permissions - Address Table (Authenticated by IP Address)

The final step is to reload the permissions address table – This is done when we restart but it’s good to know you can update it without a restart.

kamctl address reload

Adding authentication / authorization to REGISTER messages

Now let’s actually put this all into practice, the first thing we’ll do is call the REQINIT route to make sure our traffic if (reasonably) clean and take care of the basics.

request_route {

        route(REQINIT);         #Call REQINIT (Request Initial) route to filter out the worst of the bad traffic and take care of the basics.

Next we’ll setup how we handle REGISTER traffic, adding an auth challenge and only saving the location if the UA successfully responds to the challenge.

if(method=="REGISTER"){  # authenticate requests
if (!auth_check("$fd", "subscriber", "1")) { #If credentials don't match what we have in Subscriber table
   auth_challenge("$fd", "0");          #Send an Auth Challenge
   exit;                                #Stop processing
 }

save("location");                   #Save the location as an AoR
exit;                               #Stop processing
        }

This may all seem a bit backward, but this is an example from the Kamailio devs we’re using, so it shows “the right way” of doing this, let’s break it down.

We know our if(method==”REGISTER”) means we’ll only do this check for REGISTER messages.

The auth_check checks to see if the presented credentials in our auth header are correct. For the first INVITE we don’t have an auth header so it’s not correct, and if we have an invalid password it’s also not correct. You’ll notice it’s prefixed with a if(!auth_check) meaning this if conditional block is only called if it fails the authentication check, and if we do fail the authentication check we issue an auth_challenge to generate a 401 response with an authentication header and send it back to the UA. Then we exit (stop processing).

As the above had an exit we know we’ll only hit blocks below if our credentials are correct, otherwise we’d just get the auth_challenge and exit from the auth_check block.

So as we know these credentials are correct we’ll save the location as an address on record using the save(“location”) function and exit.

So that’s our REGISTER block now handling & requiring authentication, now after restarting Kamailio we can register SIP devices with the username and password we setup, but if we get the username or password wrong, we’ll get rejected.

We can add extra users using the kamctl add command we touched on earlier.

Authorising / Authenticating INVITE messages

INVITE messages are used to setup sessions (calls), so it’s important we secure this too. At this point we’re authenticating to REGISTER, but not create a call (INVITE).

First let’s add a simple check to see if the INVITE has come from the IP of one of the carriers we defined earlier.

For this we’ll use the allow_source_address() command to see if the source address matches what we defined earlier using kamctl address add to address group 200 in the MySQL database.

    if(method=="INVITE"){
        if(allow_source_address("200")){        #If from a Carrier IP
            if(!lookup("location")){    #Try looking up location
                            sl_reply("404", "User not Registered");     #If looking up location fails reply with 404
                            exit;                                       #And exit
            }

            t_relay();                  #Relay traffic to endpoint
            exit();                     #Exit
        }else{
                sl_reply("403", "Nope. Don't know who you are");
                }
}

So we’ve got a simple if for if the source address is in group 200.

Presto, this works for calls from carriers coming in to registered endpoints! We can get inbound calls.

Small catch is our users can’t dial each other any more, as their IP isn’t in address group 200, they just get the 403 “Don’t know who you are” response.

Now we could go and add the subnet where our users are located, but then there’d be no point in using passwords at all. But before we do this let’s create a new routing module, called INVITE to keep everything pretty.

At the very bottom of our config we’ll add

route[ONNETINVITE]{
          if(!lookup("location")){    #Try looking up location
                            sl_reply("404", "User not Registered");     #If looking up location fails reply with 404
                            exit;                                       #And exit
            }

            t_relay();                  #Relay traffic to endpoint
            exit();                     #Exit

}

And then we’ll remove most of the code in our if(method==”INVITE”){ block and replace it with this:

if(method=="INVITE"){
 if(allow_source_address("200")){        #If from a Carrier IP
    route(ONNETINVITE);          #Call INVITE handling bloc
  }else{
    sl_reply("403", "Nope. Don't know who you are");
    }
 }

Now we’ve made it so we just call ROUTE(INVITE); when we have an INVITE we’ve authenticated. This will save us a lot of extra code when we add our checks to see if the call is from a user we recognize, instead of running through the lookup(“location”) code and relying, we’ll just call route(ONNETINVITE); when we’re happy we know who they are and off we go.

if(method=="INVITE"){
if(allow_source_address("200")){        #If from a Carrier IP
     route(ONNETINVITE);          #Call INVITE handling bloc
}else{
     if (!auth_check("$fd", "subscriber", "1")) { #If credentials don't match what we have in Subscriber table
         auth_challenge("$fd", "0");          #Send an Auth Challenge
         exit;                                #Stop processing
      }
          route(ONNETINVITE);                  #Call invite handling block
 }
}

You may recognize the !auth_check blocks as the same code we used for authenticating REGISTER messages, we’re using it again as it’s the same auth mechanism.

If we pass it we call the route(ONNETINVITE);

If we look at the packet captures we can see our INVITE gets a 407 “Proxy Authentication required” response back from Kamailio.

Kamailio - 401 & REGISTER

And the UA then resends the INVITE with an authentication header with correct username and password and we’re on our way!

And that’s it! Phew.

In production we’d want to handle other types of messages that we’d also want to authenticate, we’ll talk about that further down the line, but keep in mind every feature you add what the security ramifications might be.

Next up we’ll use our new found sense of security to add the ability to call numbers off-net (on the PSTN) via a SIP provider!

Here’s a complete copy of my running code for your reference.

Next Post – Kamailio 101 – Tutorial 9 – Adding Carrier Links | This Post – Kamailio 101 – Tutorial 8 – Security in Practice | Previous Post – Kamailio 101 – Tutorial 7 – Security in Theory| Previous Post – Kamailio 101 – Tutorial 6- Reusing Code| Kamailio 101 – Tutorial 5 – First Call| Kamailio 101 – Tutorial 4- Taking Registrations | Kamailio 101 – Tutorial 2 – Installation & First Run | Kamailio 101 – Tutorial 1 – Introduction

Kamailio 101 – Part 7 – Security in Theory

Before we get too much further, we’ll take a breather and talk about security. We saw in the last tutorial how we’ve got to have a way to handle every scenario, like how to handle a CANCEL to an INVITE, and security is the same.

You are responsible for the code you write being secure.

In this series we’ll ultimately end up with a basic PBX with the ability to make and receive calls between registered users and via trunks from a carrier.

Soon we’ll add the ability to call numbers not registered on our system (trunks) to our system, but now is a good time to talk about securing your system, as the threat of toll fraud is very real, as well as calls from random devices waking you up in the middle of the night.

VoIP security is a big topic, there’s a whole lot of wrong ways to do things, so think before you do.

Authentication, Authorisation and Accounting

Most telephony platforms employ some form of AAA, it’s a good minimum starting point for our system to ensure we Authenticate traffic, to make sure it is who it claims to be, we Authorise traffic to make sure it’s allowed to traverse this part of the network and use theses resources, and finally we’ll Account for resources used, for example recording CDRs and cost for the services used.

Authentication

Authentication in this example we’ll using the Challenge / Response based authentication for traffic from users (Such as REGISTER and INVITE messages), as shown in this example.

When we get an INVITE or a REGISTER from a user, we’ll check their username / password matches what we’ve got on record.

For this we’ll use Kamailio’s Auth_DB module.

Authorization

We’ll use IP Address authorisation for inbound traffic from carriers (so we can make sure that we’ll only allow calls from carriers, not just any IP on the internet sending INVITES).

This means when we get an INVITE from a Carrier to send us an inbound call, we’ll make sure it’s from the Carrier’s IP address before we start our user’s phones ringing.

For this we’ll use Kamailio’s Permissions module.

Accounting

We’ll skip Accounting for now as we’re not going to be charging users at this stage.

Kamailio has the Accounting module you can use for this, which we’ll talk about another day.

Traffic Validation

Now’s probably a good time to talk about validating your traffic in the wild west world that is the internet.

Let’s say we implement a database lookup in Kamailio to take the To part of the Request URI and lookup where to route it, we receive something like:

INVITE sip:[email protected]

So we’ve got that and we lookup 1234567 in our database and get where we need to route it to:

SELECT 'forward_destination' FROM 'imaginary_route_database' WHERE 'dialed_number' = '1234567';

Seems harmless enough right?

Now imagine someone sends an INVITE that looks like this:

INVITE sip:'; DROP TABLE *;@example.com

Now let’s look at how our SQL query would look:

SELECT 'forward_destination' FROM 'imaginary_route_database' WHERE 'dialed_number' = ''; 
DROP TABLE *;
'';

And suddenly we find all our tables have been deleted.

“Exploits of a Mom” – Source – xkcd

So it’s important we filter everything, and make sure the traffic is valid.

SQL injection is a fun example, but more common examples could include not checking the Max Forwards header and looping packets out and back in infinitely (imagine no spanning tree and plugging a switch into itself) and not responding to known bad user agents like sipcli and sipvicious.

Luckily again the Kamailio team have covered this before, Kamailio comes with a module for handling a lot of the common threats.

With the boilerplate routes we talked about in the last tutorial all you have to do is add route(REQINIT); at the start of your routing block and you’ll get:

  • Dodgy UA filtering (You may want to add your own)
  • Basics of Flood Prevention (if you want it)
  • max_forward checking (for handling routing loops)
  • OPTIONS response handling
  • Sanity checking

In the next post we’ll go about putting these things into practice.

Next Post – Kamailio 101 – Tutorial 8 – Security in Practice | This Post – Kamailio 101 – Tutorial 7 – Security in Theory| Previous Post – Kamailio 101 – Tutorial 6- Reusing Code|

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Kamailio 101 – Part 6 – Reusing Code

In the last tutorial we saw some issues, calls hung up before they were answered (CANCEL), we also would have run into issues with timeouts, issues if the remote end was registered but no longer responding, behind a NAT etc.

We saw a real example of what I touched on on the opener to this series, that you have to deal with everything, and it’s a daunting task.

Kamailio allows you to deal with all these problems yourself, writing your own routing blocks, but it also comes with a bunch of useful routing blocks in the example config, that we can re-use so we don’t need to specify how to manage every little thing ourselves – unless we want to.

So lets add some of these useful routing blocks,

We’ll add this at the start of our request_route{ block

request_route {

        if (is_method("CANCEL")) {
                if (t_check_trans()) {
                        route(RELAY);
                }
                exit;
        }


        if (!is_method("ACK")) {
                if(t_precheck_trans()) {
                        t_check_trans();
                        exit;
                }
                t_check_trans();
        }

        # handle requests within SIP dialogs
        route(WITHINDLG);


So now our config looks like this:

request_route {



        if (is_method("CANCEL")) {
                if (t_check_trans()) {
                        route(RELAY);
                }
                exit;
        }


        if (!is_method("ACK")) {
                if(t_precheck_trans()) {
                        t_check_trans();
                        exit;
                }
                t_check_trans();
        }

        # handle requests within SIP dialogs
        route(WITHINDLG);


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

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


        if(method=="REGISTER"){
                save("location");
                exit;
        }
        xlog("No idea how to respond to method $rm");
        sl_reply("501", "Not Implemented");
}

As you can see we’ve added an if statement to match if the method is CANCEL or ACK, and referenced some routing blocks:

  • route(RELAY)
  • route(WITHINDLG);

We’ve also added some code to manage ACKs, but we’ll go through that on our lesson on Statefulness, for now just roll with it.

You’ll probably notice if you try and use this config that it won’t work, that’s because we’re referencing these two routing blocks without actually having defined them.

To keep us moving I’ve added all the routing blocks that come with the default Kamailio config, and added in our code to the link below;

GitHub – NickvsNetworking – Kamailio101

And finally we’ll restart and be good to go:

Restarting Kamailio

To recap, we added the boilerplate routes that come with Kamailio and referenced them in our code to better handle in dialog responses.

This is because handling all these possible scenarios, like NAT, cancel, no response, REINVITE, UPDATE, etc, etc, would take us ages to cover, and require a pretty good understanding of Kamailio and of SIP in practice.

So use the example I’ve linked above and tune in next time, where we’ll talk about adding security and authentication to our system before we connect it to the outside world.

Kamailio 101 – Tutorial 7 – Security in Theory| This Post – Kamailio 101 – Tutorial 6| Previous Post – Kamailio 101 – Tutorial 5 – First Call

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Kamailio 101 – Part 5- First Call

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

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

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

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

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

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

lookup(“location”);

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

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

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

t_relay();

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

exit();

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

The Setup

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

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

INVITE -> Trying -> Ringing -> OK

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

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

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

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

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

SIP Cancel / Not Registered

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

When things don’t go to plan…

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

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

Handling no registered user

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

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

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

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

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

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

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

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

Handling CANCEL (Calls terminated before answered)

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

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

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

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


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

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

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

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

“Homer the Vigilante” Season 5 / Episode 11

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

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

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

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

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Kamailio 101 – Part 4 – SIP Registrar

So far we’ve made Kamailio tell clients it can’t do things, today this changes!

In this example we’ll implement a simple SIP Registrar, without authentication.

Please don’t use this in the real world – You really need to authenticate REGISTER traffic and we cover security later in the series

As we talked about in the post on SIP Registrars, SIP Registrars take the REGISTER requests from SIP endpoints and store their contact details in the form of an Address on Record (AoR). This AoR contains the URI and the endpoints’ current IP it just sent the REGISTER message from.

The primary use of this is it allow us to know how to reach people. It’s kind of like an address book for mapping current IP against a SIP URI, as IP Addresses of SIP endpoints may change often.

So our Kamailio instance is going to receive an REGISTER message, store the Contact as an address on record, and respond 200 OK. Let’s build upon the config we started with in the last tutorial:

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

So let’s add an if statement to manage REGISTER messages, and save their location:

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

So we’ve added an IF statement to find if the SIP method is a REGISTER message, and if it is, we’ll call the save(“location”); function.

The save() function saves the Contact address we just received to a database (in this case one in memory) in the form of a SIP URI and current Contact location, these two bits of info combined are known as an address on record (AoR) and the save function, if successful, responds with a 200 OK automatically.

I’ve pointed a SIP endpoint at it again, we revive the REGISTER, respond with 200 OK,

REGISTER -> 200 OK

As you can see REGISTER sent to us, 200 OK responded.

So that’s it – Our SIP endpoint is happy and by calling the save(“location”) we’ve called the built in function to store the Contact as an Address on Record and respond with 200 OK.

So how do we access this information and what can we do with it?

Kamailio comes with two tools for accessing Kamailio while it’s running. In this example we’ll use kamcmd to check what’s registered on our system. After we’ve seen a device register, from command line we’ll run:

kamcmd ul.dump

This calls kamcmd the Kamailio command line tool, and calls the ul.dump function. ul is short for userloc – The module for user location management, and dump outputs all the contents of the userloc table.

kamcmd ul.dump output

Here we can see the info we’ve stored from the URI of the user (sip:[email protected]:60153) who sent a REGISTER at the Unix timestamp in “Last-Modified”.

So now we’ve created a SIP registrar, in the next tutorial we’ll use this information to route SIP INVITE messages to a registered endpoint, looking up it’s IP and get a call happening!

You can find the full Kamailio config for this in the GitHub repo.

Next Post – Kamailio 101 – Tutorial 5| Kamailio 101 – Tutorial 4 | Kamailio 101 – Tutorial 2 | Kamailio 101 – Tutorial 1

In production you’d generally not have a SIP registrar like this open to accept any registrations, you’d authenticate SIP endpoints using the REGISTER -> 401 Unauthorised -> REGISTER -> 200 OK process outlined here.
To do this using the usrloc module we’d need to introduce the concept of a stateful proxy, as we need to remember what the challenge we sent them was to compare it with what we’re expecting, but we’ll touch upon this later in the series.

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Kamailio 101 – Part 3 – Routing Blocks & Structure

I know when I look back on code I’ve written in the past I cringe a little. As you go along you learn more efficient ways to do things, so before we hop to far into Kamailio we’re going to talk about how we’ll write our kamailio.cfg file.

General Rules

Some rules of thumb to get you started with writing Kamailio configs:

  • All lines that do things (instructions) have to end with a semicolon
  • Comments start with # if single line or /* blah */ for multi-line
  • Changes to the config only take effect when you restart Kamailio
  • With what we have setup restarting Kamailio causes it to forget all about your active Registrations (Even though your devices might say they’re still registered) (For now, although we can change this behaviour)

Now we’ve got that out of the way let’s continue on from our last tutorial and start routing requests!

(We’ll skip all the config above the request_route{} block that configures the modules and their setup, it’s all very important, but we’ll touch upon that in a later tutorial I promise)

As I touched upon in the Introduction post, you define what Kamailio is and does in terms of routing SIP requests, so let’s jump straight in and get started on the blocks that take care of this.

The request_route{} Block

The request_route{} block is where all our incoming SIP requests start off. Replies / responses are handled a bit differently (more on that later), but essentially every new SIP request / message / dialog starts off here.

We can create new blocks other than request_route{} to help keep our code clean, in the same way we might define functions while programming.

Let’s take a look:

request_route {
        route(RESPOND_501);    #Jump to the RESPOND_501 block
}

route[RESPOND_501]{
        sl_reply("501", "Not Implemented");   #Send 501 reply
}

We can see in the above example, any SIP Request coming in will enter our request_route{},

In our request_route{} block we only have one instruction, which is route().

The route() command allows us to then specify another block of the config file / code to continue on from. Think of it kind of like calling a function or god forbid – a GoTo. So in this example the call comes in and then routed off to a new routing block called route[RESPOND_501],

This allows us to reuse bits of code multiple times and generally keep everything a lot cleaner.

It’s worth noting that unless you tell it to, routing to another block won’t stop Kamailio from continuing to execute it’s way through the code, it’ll do what it’s told in the other routing block and then finish what it started.

Let’s look at an example:

request_route {
        xlog("Hello, I am in the request_route");
        route(RESPOND_501);    #Jump to the RESPOND_501 block
        xlog("Back in request_route");
}

route[RESPOND_501]{
        xlog("Now I am in the respond 501 route");
        sl_reply("501", "Not Implemented");   #Send 501 reply
}

If we’re to run this code and send a SIP Request to it, we’d see each of these xlog entries in your syslog,

  • As the message firsts hits the xlog(“Hello, I am in the request_route”);, then is routed off to route[RESPOND_501]
  • In route[RESPOND_501] we’ll see the xlog(Now I am in the respond 501 route”); – Kamailio has now executed the route[RESPOND_501] block and resumes from where it was in request_route{}
  • When we get back to request_route we get the final xlog(“Back in request_route”);.

We can stop routing in a specific block using the exit; command which stops processing that request once hit. For example:

request_route {
        xlog("Hello, I am in the request_route");
        route(RESPOND_501);    #Jump to the RESPOND_501 block
        xlog("Back in request_route");
}

route[RESPOND_501]{
        xlog(Now I am in the respond 501 route");
        sl_reply("501", "Not Implemented");   #Send 501 reply
        exit;
}

Because of the exit we added in the route[RESPOND_501] Kamailio stops executing at that point, so we don’t continue passing through the config file and this time we won’t get the final xlog(“Back in request_route”); call.

Boilerplate Routes

Kamailio’s example config by default comes with a lot of preconfigured routes that can be reused over and over again, so you don’t have to create everything from scratch if you don’t want to. We’ll talk about using these in an upcoming tutorial, but for now just keep in mind there’s pre-written routing blocks for things like managing NAT, sanity checking traffic, etc.

Basic Message Routing

Let’s take a very basic use case, and write some code in the request_route{} block.

We’ll set it up so if we receive an INVITE request, we’re going to respond with a 480 Temporarily Unavailable message, and for everything else, we’ll respond with 501 “Not Implemented”.

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

So let’s break this down,

First we have a an if statement – If the method of the SIP message is an INVITE request, then execute the code inside the curly brackets.

Inside the curly brackets we’ll respond with 480 Temporarily Unavailable, using sl_reply() (You may remember from the last post that sl_reply sends a reply back to the sender of the message with the response code and text specified) and then exit, meaning we won’t continue executing what’s next in the config.

Finally outside of our if statement (so catching any SIP requests who’s method isn’t INVITE) we’ll respond with 501 “Not Implemented”.

We’ll restart Kamailio & fire up a packet capture as we did in Part 2 of the series. Let’s take a look:

REGISTER - Not Implemented - INVITE - Temporarily Unavailable

Here we can see our REGISTER message got back a 501 Not Implemented response, while our INVITE got a 480 Temporarily Unavailable response.

We’ve now expanded upon our useless SIP server, in the next tutorial we’ll cover doing something useful!

Link to Kamailio 101 – Tutorial 4 | Kamailio 101 – Tutorial 2 | Kamailio 101 – Tutorial 1

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap

Kamailio 101 – Part 2 – Installation & First Run


This tutorial will use Kamailio on Ubuntu 18.04, installed from the default repos using apt-get, but these concepts will apply to any version 4.x Kamailio instance, though some of your directories & file names may differ.

After setting up our Ubuntu box we’ll update our repos and install Kamailio

apt-get update 
apt-get install kamailio*

Now we’ve installed Kamailio with all the extra modules and plugins.

In production you’d only install what you need, but this would mean our development environment won’t be complaining about not having modules, so we’re installing the lot.

So now Kamailio is installed we can get to work making it do something.

Kamailio is driven by a text based config file that defines the routing rules and how we’ll handle SIP messages.

You need to specify how to handle the different types of SIP messages (aka SIP methods / requests), in a way the other devices communicating with it will understand and that generally follows the standards.

But for our example, we’ll create a really basic config that replies to any SIP message sent to it, saying it doesn’t know how to handle that message.

We’ll open kamailio.cfg – the text file that contains all our routing info, and get to work.

vi /etc/kamailio/kamailio.cfg

You’ll see the config starts by defining what modules to load, and the config for each of these modules. For us, the defaults will work for now, let’s get to the juicy bits. Keep moving down the config file until you hit this section:

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

The request_route section is what handles the initial SIP message that comes through.

We’ll remove all the text after request_route { leaving us with a blank canvas in terms of how we handle messages. We’ll then put in a single line to log to syslog when we get a SIP message:

/* 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("I got a message");
}

If we were to stop here and restart Kamailio, when a SIP message comes into our SIP server, we’d just write an entry to syslog saying “I got a message” each time we get a SIP message, but Kamailio won’t respond to the received SIP message, it’ll just enter a log entry each time it gets a SIP message. So let’s respond to any SIP message we get with a 501 “Not Implemented” message.

/* 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("I got a message");
        sl_reply("501", "Not Implemented");
}

So let’s break down the two functions we just used.

xlog(“”);

xlog is your friend.
Put simply xlog prints whatever you put inside the parenthesis to syslog.
You can use it to check to see if your messages are getting to the part of the config you want, check what pseudovariables are doing and many other things, but I’m getting ahead of myself. For now you just need to know xlog is like NoOp() in Asterisk, print() in Python, etc.

sl_reply();

sl_reply is stateless reply.
It takes two parameters, the SIP Response Code and the text to go with it, when called it sends the SIP response code and text back to the requester.
In this case we’re using SIP Response Code 501, which translates to Not Implemented, meaning the server does not support the functionality required to fulfill the request aka:

Essentially what a 501 response translates to

Putting it into Play

Now let’s restart Kamailio to make our config live, point a SIP endpoint at it and see what happens!

First we’ll restart Kamailio:

/etc/init.d/kamailio restart

All going well you’ll restart Kamailio and be good to go:

After restarting via init.d you should see [ok] if it restarted sucesfully

Now let’s fire some traffic at it and see what happens.
I’m using tcpdump to capture traffic on port 5060, and I’ve setup a SIP endpoint that’s going to try and register to our IP to see what we get:

Presto – REGISTER message received and 501 response

So this is exciting, we now have a SIP server that can do nothing except let people know it can do nothing.

Let’s dig a bit deeper into the fundamentals and then we’ll have a good foundation to start building from, and get to work on stitching everything together to create something useful.

Link to Part 3 | Back to Part 1

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

Kamailio 101 – Tutorial 2 – Installation & First Run

Kamailio 101 – Tutorial 3 – Routing Blocks & Structure

Kamailio 101 – Tutorial 4 – Taking Registrations

Kamailio 101 – Tutorial 5 – First Call

Kamailio 101 – Tutorial 6 – Reusing Code

Kamailio 101 – Tutorial 7 – Security in Theory

Kamailio 101 – Tutorial 8 – Security in Practice

Kamailio 101 – Tutorial 9 – Adding Carrier Links

Kamailio 101 – Tutorial 10 – Recap