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
HOMER Captagent Interface Setup
Next we’ll edit the port that we send HEP data on
vi /etc/captagent/transport_hep.xml
Set HEP Port for Transport
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.
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.
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.
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 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.
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
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:
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.
“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.
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 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.
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.
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.
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.
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.
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.
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:
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
}
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.
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
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.
Method
Request Size (bytes)
Response Size (bytes)
Gain
OPTIONS
168
209
1.2x
REGISTER
380
411
1x
INVITE
197
377
1.9x
Content Length Mismatch
339
400 Missing Required Header in Request
300
Max Forwards Exceeded
213
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:
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.
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.
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.
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)
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).
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.
Want more telecom goodness?
I have a good old fashioned RSS feed you can subscribe to.