At Sipfront, we use sipp to perform load tests for our customers' infrastructure. The systems under test can be anything from a simple PBX to a powerful C4 or C5 or a flexible CPaaS, and their behavior might vary heavily. In this blog post, we’ll look into how we use sipp to match SIP response codes as dynamically as possible to fit sipp scenarios for most purposes.

A basic sipp scenario

A typical SIP UAS (user agent server) scenario to register at a SuT (System under Test) in order to receive subsequent calls looks like this:

 1<scenario name="uas-register">
 3    <send retrans="500">
 4        <![CDATA[
 5            REGISTER sip:[field1 file="callee.csv"] SIP/2.0
 6            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
 7            Max-Forwards: 70
 8            From: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>;tag=[call_number]
 9            To: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>
10            Call-ID: [call_id]
11            CSeq: [cseq] REGISTER
12            Contact: <sip:[field0 file="callee.csv"]@[local_ip]:[local_port]>
13            Expires: [reg_expire]
14            Content-Length: 0
15            User-Agent:
16            X-SF-Destination: [target_uri]
17        ]]>
18    </send>  
20    <recv response="100" optional="true" />
21    <recv response="401" auth="true" />
23    <send retrans="500">
24        <![CDATA[
25            REGISTER sip:[field1 file="callee.csv"] SIP/2.0
26            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
27            Max-Forwards: 70
28            From: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>;tag=[call_number]
29            To: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>
30            Call-ID: [call_id]
31            CSeq: [cseq] REGISTER
32            Contact: <sip:[field0 file="callee.csv"]@[local_ip]:[local_port]>
33            Expires: [reg_expire]
34            Content-Length: 0
35            User-Agent:
36            X-SF-Destination: [target_uri]
37            [field3 file="callee.csv"]
38        ]]>
39    </send>
41    <recv response="100" optional="true" />
42    <recv response="200" />
44    <pause /> 

If you’re certain that this call flow (REGISTER with a 100/401 and another REGISTER with a 100/200) is the only scenario going to happen, you’re lucky and you’re done at this point. Otherwise you’re set up for false negative test results.

If at this point you’re wondering about the pause after the scenario: Did you know you can have your credentials file configured with a USER header instead of RANDOM or SEQUENTIAL, and start sipp with -users 10 (or however much entries your credentials file contains), and let sipp keep the users registered by repeating the scenario after it ends?

The problem

In real life, things are not always as easy as sending a request and receiving a pre-defined response. The SuT might respond with a 500 on the first REGISTER. This will cause sipp to bail out and immediately re-run the scenario, flooding your SuT. You can catch the 500 reply if you know your SuT reacts like this, but you might get any arbitrary response code. Even more so if you start doing calls and get your responses from your upstream carrier, which you do not control anymore.

Fact is, you need a way to efficiently match them all, and react accordingly.

The solution

The answer to this problem is to dynamically match response codes, and the most elegant solution is to match them by a regular expression. This feature was requested first in 2007 on the sipp mailing list, and it’s still not available in the upstream sipp (yet - we will try to get it there), but here is our take on it, and how it works:

 1<?xml version="1.0" encoding="utf-8" ?>
 2<!DOCTYPE scenario SYSTEM "sipp.dtd">
 4<scenario name="uas-register">
 6    <send retrans="500">
 7        <![CDATA[
 8            REGISTER sip:[field1 file="callee.csv"] SIP/2.0
 9            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
10            Max-Forwards: 70
11            From: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>;tag=[call_number]
12            To: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>
13            Call-ID: [call_id]
14            CSeq: [cseq] REGISTER
15            Contact: <sip:[field0 file="callee.csv"]@[local_ip]:[local_port]>
16            Expires: [reg_expire]
17            Content-Length: 0
18            User-Agent:
19            X-SF-Destination: [target_uri]
20        ]]>
21    </send>
23    <recv response="100" optional="true" timeout="10000" ontimeout="do_pause" />
24    <recv response="200" optional="true" timeout="10000" ontimeout="do_pause" next="do_pause" />
25    <recv response="401" optional="true" auth="true" timeout="10000" ontimeout="do_pause" next="do_auth" />
26    <recv response=".+" regexp_match="true" timeout="10000" ontimeout="do_pause" next="do_pause" />
28    <label id="do_auth" />
29    <send retrans="500">
30        <![CDATA[
31            REGISTER sip:[field1 file="callee.csv"] SIP/2.0
32            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
33            Max-Forwards: 70
34            From: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>;tag=[call_number]
35            To: "[field2 file="callee.csv"]" <sip:[field0 file="callee.csv"]@[field1 file="callee.csv"]>
36            Call-ID: [call_id]
37            CSeq: [cseq] REGISTER
38            Contact: <sip:[field0 file="callee.csv"]@[local_ip]:[local_port]>
39            Expires: [reg_expire]
40            Content-Length: 0
41            User-Agent:
42            X-SF-Destination: [target_uri]
43            [field3 file="callee.csv"]
44        ]]>
45    </send>
47    <recv response=".+" regexp_match="true" timeout="10000" ontimeout="do_pause" next="do_pause" />
49    <label id="do_pause" />
50    <pause />

What’s happening here? In essence, we’re checking after the first response whether we’re getting an optional 100 (pretty likely) or an optional 200 (rather unlikely, why would a registrar allow us without authorization?). Next, we’re checking for a 401 (you might also want to check for a 407, but that’s usually only used for calls) and jump to the part where we re-send the REGISTER including an authorization header. Anything else goes to the pause action, and your logic might vary here.

Here is how our callee.csv looks like for a re-registration scenario, in case you struggle with that:

+12345;;+12345;[authentication username="testuser" password="testpass"]

The last element before the authorization part and the element after the authorization is the interesting one: We’re matching any response code with .+ and set regexp_match="true". This will cause sipp to match any response code, and jump to the next label, which is do_pause in our case. Your milage might vary here, where you can branch off to any logic you see fit.

IMPORTANT: Using <recv response=".+" /> will make your stock sipp bail out, because you normally have to put a number there. The patch we linked above will allow this.


Although we work with sipp all the time, we often uncover config gems by accident. Yes, I sometimes read the sipp documentation in my spare time for fun, and there’s usually something new that I learn.

And if that doesn’t help, we study the source code.

And if that doesn’t help either, we patch it.

So, if you need an expert tackling your testing challenges, feel free to contact us.

comments powered by Disqus