I’m a big fan of RTPengine, and I’ve written a bit about it in the past.
Let’s say we’re building an Australia wide VoIP network. It’s a big country with a lot of nothing in the middle. We’ve got a POP in each of Australia’s capital cities, and two core softswitch clusters, one in Melbourne and one in Sydney.
These two cores will work fine, but a call from a customer in Perth, WA to another customer in Perth, WA would mean their RTP stream will need to go across your inter-caps to Sydney or Melbourne only to route back to Perth.
That’s 3,500Km each way, which is going to lead to higher latency, wasted bandwidth and decreased customer experience.
What if we could have an RTPengine instance in our Perth POP, handling RTP proxying for our Perth customers? Another in Brisbane, Canberra etc, all while keeping our complex expensive core signalling in just the two locations?
RTPengine to the rescue!
Preparing our RTPEngine Instances
In each of our POPs we’ll spin up a box with RTPengine,
The only thing we’d do differently is set the listen-ng value to be 0.0.0.0:2223 and the interface to be the IP of the box.
By setting the listen-ng value to 0.0.0.0:2223 it’ll mean that RTPengine’s management port will be bound to any IP, so we can remotely manage it via it’s ng-control protocol, using the rtpengine Kamailio module.
Naturally you’d limit access to port 2223 only to allowed devices inside your network.
Next we’ll need to add the details of each of our RTP engine instances to MySQL, I’ve used a different setid for each of the RTPengines. I’ve chosen to use the first digit of the Zipcode for that state (WA’s Zipcodes / Postcodes are in the format 6xxx while NSW postcodes are look like 2xxx), we’ll use this later when we select which RTPengine instances to use.
I’ve also added localhost with setid of 0, we’ll use this as our fallback route if it’s not coming from Australia.
Bingo, we’re connected to three RTPengine instances,
Next up we’ll use the Geoip2 module to determine the source of the traffic and route to the correct, I’ve touched upon the Geoip2 module’s basic usage in the past, so if you’re not already familiar with it, read up on it’s usage and we’ll build upon that.
We’ll load GeoIP2 and run some checks in the initial request_route{} block to select the correct RTPengine instance:
if(geoip2_match("$si", "src")){
if($gip2(src=>cc)=="AU"){
$var(zip) = $gip2(src=>zip);
$avp(setid) = $(var(zip){s.substr,0,1});
xlog("rtpengine setID is $avp(setid)");
}else{
xlog("GeoIP not in Australia - Using default RTPengine instance");
set_rtpengine_set("0");
}
}else{
xlog("No GeoIP Match - Using default RTPengine instance");
set_rtpengine_set("0");
}
In the above example if we have a match on source, and the Country code is Australia, the first digit of the ZIP / Postcode is extracted and assigned to the AVP “setid” so RTPengine knows which set ID to use.
In practice an INVITE from an IP in WA returns setID 6, and uses our RTPengine in WA, while one from NSW returns 2 and uses one in NSW. In production we’d need to setup rules for all the other states / territories, and generally have more than one RTPengine instance in each location (we can have multiple instances with the same setid).
Hopefully you’re starting to get an idea of the fun and relatively painless things you can achieve with RTPengine and Kamailio!
The History Info extension defined in RFC7044 sets a way for an INVITE to include where the session (call) has been before that.
For example a call may be made to a desk phone, which is forwarded (302) to a home phone. The History Info extension would add a History Info header to the INVITE to the home phone, denoting the call had come to it via the desk phone.
Each Diameter packet has at a the following headers:
Version
This 1 byte field is always (as of 2019) 0x01 (1)
Length
3 bytes containing the total length of the Diameter packet and all it’s contained AVPs.
This allows the receiver to know when the packet has ended, by reading the length and it’s received bytes so far it can know when that packet ends.
Flags
Flags allow particular parameters to be set, defining some possible options for how the packet is to be handled by setting one of the 8 bits in the flags byte, for example Request Set, Proxyable, Error, Potentially Re-transmitted Message,
Command Code
Each Diameter packet has a 3 byte command code, that defines the method of the request,
The IETF have defined the basic command codes in the Diameter Base Protocol RFC, but many vendors have defined their own command codes, and users are free to create and define their own, and even register them for public use.
To allow vendors to define their own command codes, each command code is also accompanied by the Application ID, for example the command code 257 in the base Diameter protocol translates to Capabilities Exchange Request, used to specify the capabilities of each Diameter peer, but 257 is only a Capabilities Exchange Request if the Application ID is set to 0 (Diameter Base Protocol).
If we start developing our own applications, we would start with getting an Application ID, and then could define our own command codes. So 257 with Application ID 0 is Capabilities Exchange Request, but command code 257 with Application ID 1234 could be a totally different request.
Hop-By-Hop Identifier
The Hop By Hop identifier is a unique identifier that helps stateful Diameter proxies route messages to and fro. A Diameter proxy would record the source address and Hop-by-Hop Identifier of a received packet, replace the Hop by Hop Identifier with a new one it assigns and record that with the original Hop by Hop Identifier, original source and new Hop by Hop Identifier.
End-to-End Identifier
Unlike the Hop-by-Hop identifier the End to End Identifier does not change, and must not be modified, it’s used to detect duplicates of messages along with the Origin-Host AVP.
AVPs
The real power of Diameter comes from AVPs, the base protocol defines how to structure a Diameter packet, but can’t convey any specific data or requests, we put these inside our Attribute Value Pairs.
Let’s take a look at a simple Diameter request, it’s got all the boilerplate headers we talked about, and contains an AVP with the username.
Here we can see we’ve got an AVP with AVP Code 1, containing a username
Let’s break this down a bit more.
AVP Codes are very similar to the Diameter Command Codes/ApplicationIDs we just talked about.
Combined with an AVP Vendor ID they define the information type of the AVP, some examples would be Username, Session-ID, Destination Realm, Authentication-Info, Result Code, etc.
AVP Flags are again like the Diameter Flags, and are made up a series of bits, denoting if a parameter is set or not, at this stage only the first two bits are used, the first is Vendor Specific which defines if the AVP Code is specific to an AVP Vendor ID, and the second is Mandatory which specifies the receiver must be able to interpret this AVP or reject the entire Diameter request.
AVP Length defines the length of the AVP, like the Diameter length field this is used to delineate the end of one AVP.
AVP Vendor ID
If the AVP Vendor Specific flag is set this optional field specifies the vendor ID of the AVP Code used.
AVP Data
The payload containing the actual AVP data, this could be a username, in this example, a session ID, a domain, or any other value the vendor defines.
AVP Padding
AVPs have to fit on a multiple of a 32 bit boundary, so padding bits are added to the end of a packet if required to total the next 32 bit boundary.
3GPP selected Diameter protocol to take care of Authentication, Authorization, and Accounting (AAA).
It’s typically used to authenticate users on a network, authorize them to use services they’re allowed to use and account for how much of the services they used.
In a EPC scenario the Authentication function takes the form verifying the subscriber is valid and knows the K & OP/OPc keys for their specific IMSI.
The Authorization function checks to find out which features, APNs, QCI values and services the subscriber is allowed to use.
The Accounting function records session usage of a subscriber, for example how many sessional units of talk time, Mb of data transferred, etc.
Diameter Packets are pretty simple in structure, there’s the packet itself, containing the basic information in the headers you’d expect, and then a series of one or more Attribute Value Pairs or “AVPs”.
These AVPs are exactly as they sound, there’s an attribute name, for example username, and a value, for example, “Nick”.
This could just as easily be for ordering food; we could send a Diameter packet with an imaginary command code for Food Order Request, containing a series of AVPs containing what we want. The AVPs could belike Food: Hawian Pizza, Food: Garlic Bread, Drink: Milkshake, Address: MyHouse. The Diameter server could then verify we’re allowed to order this food (Authorization) and charge us for the food (Accounting), and send back a Food Order Response containing a series of AVPs such as Delivery Time: 30 minutes, Price: $30.00, etc.
Diameter packets generally take the form of a request – response, for example a Capabilities Exchange Request contains a series of AVPs denoting the features supported by the requester, which is sent to a Diameter peer. The Diameter peer then sends back a Capabilities Exchange Response, containing a series of AVPs denoting the features that it supports.
Diameter is designed to be extensible, allowing vendors to define their own type of AVP and Diameter requests/responses and 3GPP have defined their own types of messages (Diameter Command Codes) and types of data to be transferred (AVP Codes).
LTE/EPC relies on Diameter and the 3GPP/ETSI defined AVP / Diameter Packet requests/responses to form the S6a Interface between an MME and a HSS, the Gx Interface between the PCEF and the PCRF, Cx Interface between the HSS and the CSCF, and many more interfaces used for Authentication in 3GPP networks.
NAT is still common in Voice networks, and while we’re all awaiting the full scale adoption of IPv6, it’s still going to be a thing for some time.
I thought I’d dive into some of the NAT “solutions” that are currently in use.
Old RFC 3489 Definitions
These were the first NAT implementations used, and are still often used today.
Full cone NAT
A request from a private address is mapped to a public address and a publicly available port.
Traffic can be sent from any external device to this public address / port combination, and will be sent the internal device.
This is often statically setup, where you’d log into your router and put a NAT rule saying “Traffic on Port 5060 I want forwarded to my desk phone on 192.168.1.2” for example, and is sometimes just called a “Port forward”.
This can work fine if you’ve just got one unchanging internal address, but starts to have issues with multiple devices or dynamically assigned IPs.
Restricted Cone NAT
A request from a private address is mapped to a public address.
Traffic sent to this public address from an allowed IP will be routed to the internal device, regardless of port used.
Port Restricted Cone
Like restricted cone but only a single port may be used, traffic sent to any other port will not be routed to the internal device.
Symmetric NAT
Each request to an external destination gets a unique Public IP / Port combination to be used only by that destination, and each new request with a different source port on the internal side, or different destination on the external side, sets up a new NAT path.
RFC 5389 NAT Definitions
Endpoint Independent Mapping
Each request to an external destination gets the same public IP address / Port combination used for the outbound traffic.
Return traffic from the external destination is routed based on the source address, to the internal IP of the originating user.
It’s possible to have multiple internal devices communicating with multiple external destinations, using the same public IP address / port combination for each of them.
The source IP address of the traffic back from the external destination is used to map the path back to the internal IP.
This is efficient (doesn’t need to keep using outbound ports on the public IP) but means that it’ll only work to the requested external destination’s IP.
If you register to a SIP server on one IP, and media comes in on another, an Endpoint Independent Mapping NAT will see you with one-way audio.
Address Dependant Mapping
Each request to an external destination gets a unique public IP address / port combination used for outbound traffic.
It is reused for packets sent to the same destination, regardless of which destination port is used.
Address and Port Dependant Mapping
Same as Address Dependant Mapping but a new mapping is created for each destination and port.
You’ll often see numbers listed in different formats, which often leads to confusion.
Australian SIP networks may format numbers in either 0NDC-SN or E.164 format, leading some confusion. There’s no “correct” way, ACMA format in 0-NDC-SN, while most Australian tier 1 carriers store the records in E.164 format.
There’s no clear standard, so it’s always best to ask.
Let’s say my number is in Melbourne and is 9123 4567,
This could be expressed in Subscriber Number (SN) format:
9123 4567
The problem is a caller from Perth calling that number wouldn’t get through to me, there’s a good chance they’d get a totally unrelated business.
To stop this we can add the National Destination Code (NDC), for Victoria / Tasmania this is 3, however when dialling domestically a 0 is prefixed.
The leading 0 is a carry over from the days of step-by-step based switching, which had technical and physical design constraints that dictated the dialling plan we see today, which I’ll do a post about another day.
So to put it in 0-NDC-S format we’d list
03 9123 4567
But an international caller wouldn’t be able to reach this from their home country, they’d need to add the Country Code (CC) which for Australia is 61, so they’d dial the CC-NDC-SN
Sometimes this is listed with the plus symbol in front of it, like
+61 3 9123 4567
Each country has it’s own international dialling prefix, and the plus symbol is to be replaced by the international dialling prefix used in the calling country. In Australia, we replace the + with 0011, but it’s different from country to country.
I’ve talked a bit in the past about using RTPengine to act as an RTP proxy / media proxy in conjunction with Kamailio.
Recently transcoding support was added to RTPengine, and although the Kamailio rtpengine module doesn’t yet recognise the commands when we put them in, they do work to transcode from one codec to another.
This will mask all the other codecs and transcode into PCMU, simple as that.
Beware software based transcoding is costly to resources, this works fine in small scale, but if you’re planning on transcoding more than 10 or so streams you’ll start to run into issues, and should look at hardware based transcoding.
Siremis is a web interface for Kamailio, created by the team at Asipto, who contribute huge amounts to the Kamailio project.
Siremis won’t create your Kamailio configuration file for you, but allows you to easily drive the dynamic functions like dialplan, subscribers, dispatcher, permissions, etc, from a web interface.
Siremis essentially interfaces with the Kamailio database, kamcmd and kamctl to look after your running Kamailio instance.
Installation
I’ll be installing on Ubuntu 18.04, but for most major distributions the process will be the same. We’re using PHP7 and Apache2, which are pretty much universal available on other distros.
First we need to install all the packages we require:
Next we’ll download Siremis from the Git repo, and put it into a folder, which I’ve named the same as my Kamailio version.
cd /var/www/html/ git clone https://github.com/asipto/siremis kamailio-5.1.2
Now we’ll move into the directory we’ve created (called kamailio-5.1.2) and build the apache2 config needed:
cd kamailio-5.1.2/ make apache24-conf
This then gives us a config except we can put into our Apache virtual host config file:
We can now copy and paste this into the end of an existing or new Apache virtual host file.
If this is a fresh install you can just pipe the output of this into the config file directly:
make apache24-conf >> /etc/apache2/sites-available/000-default.conf service apache2 restart
Now if you browse to http://yourserverip/siremis you should be redirected to http://yourserverip/siremis/install and have a few errors, that’s OK, it means our Apache config is working.
Next we’ll set the permissions, create the folders and .htaccess. The Siremis team have also created make files to take care of this too, so we can just run them to set everything up:
make prepare24 make chown
With that done we can try browsing to our server again ( http://yourserverip/siremis ) and you should hit the installation wizard:
Now we’ll need to setup our database, so we can read and write from it.
We’ll create new MySQL users for Kamailio and Seremis:
mysql> GRANT ALL PRIVILEGES ON siremis.* TO siremis@localhost IDENTIFIED BY 'siremisrw';
mysql> CREATE USER 'kamailio'@'localhost' IDENTIFIED BY 'my5yhtY7zPJzV8vu';
mysql> GRANT ALL PRIVILEGES ON * . * TO 'kamailio'@'localhost';
mysql> FLUSH PRIVILEGES;
Next up we’ll need to configure kamctlrc so it knows the database details, we covered this the Security in Practice tutorial.
We’ll edit /etc/kamalio/kamctlrc and add our database information:
Once that’s done we can create the database and tables using kamdbctl the database tool:
kamdbctl create
I’ve selected to install the optional tables for completeness.
Once this is done we can go back to the web page and complete the installation wizard:
We’ll need to fill the password for the Siremis DB we created and for the Kamailio DB, and ensure all the boxes are ticked.
Next, Next, Next your way through until you hit the login page, login with admin/admin and you’re away!
Troubleshooting
If you have issues during the installation you can re-run the installation web wizard by removing the install.lock file in /var/www/html/kamailio-5.1.2/siremis
You can also try dropping the Siremis database and getting the installer to create it again for you:
HTable is Kamailio’s implimentation of Hash Tables a database-like data structure that runs in memory and is very quick.
It’s uses only become apparent when you’ve become exposed to it.
Let’s take an example of protecting against multiple failed registration attempts.
We could create a SQL database called registration attempts, and each time one failed log the time and attempted username.
Then we could set it so before we respond to traffic we query the database, find out how many rows there are that match the username being attempted and if it’s more than a threshold we set we send back a rate limit response.
The problem is that’s fairly resource intensive, the SQL data is read and written from disks and is slow to do both.
Enter HTable, which achieves the same thing with an in-memory database, that’s lightning fast.
Basic Setup
We’ll need to load htable and create an htable called Table1 to store data in:
$sht(MessageCount=>test) is the logical link to the Htable called MessageCount with a key named test. We’re making that equal itself + 1.
We’re then outputting the content of $sht(MessageCount=>test) to xlog too so we can see it’s value in Syslog.
Now each time a new dialog is started the MessageCount htable key “test” will be incremented.
We can confirm this in Syslog:
ERROR: : MessageCount is 1 ERROR: : MessageCount is 2
We can also check this in kamcmd too:
htable.dump MessageCount
Here we can see in MessageCount there is one key named “test” with a value of 6, and it’s an integer. (You can also store Strings in HTable).
So that’s all well and pointless, but let’s do make it a bit more useful, report on how many SIP transactions we get per IP. Instead of storing our values with the name key “test” we’ll name it based on the Source IP of the message, which lives in Psedovariable $si for Source IP Address.
I’m calling the boilerplate AUTH block, and I’ve added some logic to increment the AuthCount for each failed auth attempt, and reset it to $null if authentication is successful, thus resetting the counter for that IP Address.
Now we’ve done that we need to actually stop the traffic if it’s failed too many times. I’ve added the below check into REQINIT block, which I call at the start of processing:
if($sht(AuthCount=>$si) > 5){
xlog("$si is back again, rate limiting them...");
sl_send_reply("429", "Rate limiting");
exit;
}
Now if AuthCount is more than 5, it’ll respond with a Rate Limiting response.
Because in our modparam() setup for AuthCount we set an expiry, after 360 seconds (10 minutes), after 10 minutes all will be forgiven and our blocked UA can register again.
Advanced Usage / Notes
So now we’ve got Kamailio doing rate limiting, it’s probably worth mentioning the Pike module, which can also be used.
You’ll notice if you reboot Kamailio all the htable values are lost, that’s because the hashes are stored in memory, so aren’t persistent.
You have a few options for making this data persistent,
By using DMQ you can Sync data between Kamailio instances including htable values.
kamcmd can view, modify & manipulate htable values.
As we’ve seen before we can dump the contents of an htable using:
kamcmd htable.dump MessageCount
We can also add new entries & modify existing ones:
kamcmd htable.seti MessageCount ExampleAdd s:999
htable.seti is for setting integer values, we can also use htable.sets to set string values:
htable.sets MessageCount ExampleAdd Iamastring
We can also delete values from here too, which can be super useful for unblocking destinations manually:
htable.delete MessageCount ExampleAdd
As always code from this example is on GitHub. (Please don’t use it in production without modification, Authentication is only called on Register, and it’s just built upon the previous tutorials).
There are a number of ways to feed Homer data, in this case we’re going to use Kamailio, which has a HEP module, so when we feed Kamailio SIP data it’ll use the HEP module to encapsulate it and send it to the database for parsing on the WebUI.
We won’t actually do any SIP routing with Kamailio, we’ll just use it to parse copies of SIP messages sent to it, encapsulate them into HEP and send them to the DB.
We’ll be doing this on the same box that we’re running the HomerUI on, if we weren’t we’d need to adjust the database parameters in Kamailio so it pushes the data to the correct MySQL database.
Next we’ll need to configure captagent to capture data and feed it to Kamailio. There’s two things we’ll need to change from the default, the first is the interface we capture on (By default it’s eth0, but Ubuntu uses eth33 as the first network interface ID) and the second is the HEP destination we send our data to (By default it’s on 9061 but our Kamailio instance is listening on 9060).
We’ll start by editing captagent’s socket_pcap.xml file to change the interface we capture on:
vi /etc/captagent/socket_pcap.xml
Next we’ll edit the port that we send HEP data on
vi /etc/captagent/transport_hep.xml
And finally we’ll restart captagent
/etc/init.d/captagent
Now if we send SIP traffic to this box it’ll be fed into HOMER.
In most use cases you’d use a port mirror so you may need to define the network interface that’s the destination of the port mirror in socket_pcap.xml
HOMER is a popular open source SIP / RTP debug / recording tool.
It’s architecture is pretty straight forward, we have a series of Capture Agents feeding data into a central HOMER Capture Server, which runs a database (today we’re using MySQL), a Homer-UI (Running on Apache), a Homer-API (Also running on Apache) and a HEP processor, which takes the HEP encoded data from the Capture Agents and runs on Kamailio. (That’s right, I’m back rambling about Kamailio)
So this will get the web interface and DB backend of HOMER setup,
For HOMER to actually work you’ll need to feed it data, in the next tutorial we’ll cover configuring a capture agent to feed the HEP processor (Kamailio) which we’ll also setup, but for now we’ll just setup the web user interface for HOMER, API and Database.
Caller-ID spoofing has been an issue in most countries since networks went digital.
SS7 doesn’t provide any caller ID validation facilities, with the assumption that everyone you have peered with you trust the calls from. So because of this it’s up to the originating switch to verify the caller ID selected by the caller is valid and permissible, something that’s not often implemented. Some SIP providers sell the ability to present any number as your CLI as a “feature”.
There’s heaps of news articles on the topic, but I thought it’d be worth talking about RFC4474 – Designed for cryptographically identifying users that originate SIP requests. While almost never used it’s a cool solution to a problem that didn’t take off.
It does this by adding a new header field, called Identity, for conveying a signature used for validating the identity of the caller, and Identity-Info for a reference to the certificate signing authority.
The calling proxy / UA creates a hash of it’s certificate, and inserts that into the SIP message in the Identity header.
The calling proxy / UA also inserts a “Identity-Info” header containing
The called party can then independently get the certificate, create it’s own hash of it, and if they match, then the identity of the caller has been verified.
Now we’ll restart Kamailio and use kamcmd to check the status of our rtpengine instance:
kamcmd rtpengine.show all
All going well you’ll see something like this showing your instance:
Putting it into Practice
If you’ve ever had experience with the other RTP proxies out there you’ll know you’ve had to offer, rewrite SDP and accept the streams in Kamailio.
Luckily rtpengine makes this a bit easier, we need to call rtpengine_manage(); when the initial INVITE is sent and when a response is received with SDP (Like a 200 OK).
So for calling on the INVITE I’ve done it in the route[relay] route which I’m using:
And for the reply I’ve simply put a conditional in the onreply_route[MANAGE_REPLY] for if it has SDP:
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).
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’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.
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)
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.
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.
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.
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.
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.
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.
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.