Our core expertise at Sipfront is testing complex call scenarios, so I got really excited when I recently received the following request:
Could you simulate an E911 emergency call like this with a multipart-part/mixed body and an SRTP stream, to test an emergency server implementation of ours?
Of course one could run off and implement its own dedicated SIP client, perhaps using pjsip or something similar. At Sipfront, we rather lean towards generic solutions you can easily script, and since our framework already includes sipp, kamailio and rtpengine, a different idea took shape.
To accomplish a call with custom SIP headers and a multipart/mixed body including SRTP, you need three things:
- Signaling the call flow
- Generating the encrypted media
- Embedding its negotiation into the call flow
Following the Unix philosophy to combine multiple tools, each specialized to its use case, I came up with the following approach.
Signaling the call flow
Generating custom SIP call scenarios is the job of sipp at Sipfront. Since we got the Microsoft example of an E911 call, the first step was to create the call flow using the INVITE of the example, with user/domain/IP information replaced by what we’d later inject via CSV files and CLI options dynamically.
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="uac-call-e911">
<send>
<![CDATA[
INVITE sip:[field0 file="callee.csv"];phone-context=[field0 file="callee.csv"]@[field1 file="callee.csv"];user=phone SIP/2.0
...
Priority: emergency
Supported: geolocation
Geolocation: <cid:sip:[field0 file="caller.csv"]@[field1 file="caller.csv"]>;inserted-by="sip:[field0 file="caller.csv"]@[field1 file="caller.csv"]"
Content-Type: multipart/mixed; boundary=sipfront-msg-boundary
Content-Length: [len]
--sipfront-msg-boundary
Content-Type: application/sdp
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv
--sipfront-msg-boundary
Content-Type: application/pidf+xml
Content-ID: <[field0 file="caller.csv"]@[field1 file="caller.csv"]>
<?xml version="1.0" encoding="utf-8"?>
<presence xmlns="urn:ietf:params:xml:ns:pidf" ... >
...
</presence>
--sipfront-msg-boundary
]]>
</send>
<recv response="100" optional="true" timeout="10000" />
...
With a full sipp scenario in place, we launched sipp towards a Sipwise CE soft-switch with the called party forwarded to voicemail, to check if we could successfully setup a call. You’ll recognize at this point that the provided Microsoft example has quite a couple of syntax errors in it, but anyways…
If SDP offer/answer is your thing, you’ll also recognize that we’re using RTP/AVP
here, meaning plain RTP instead of SRTP as requested. Accept that as-is
for now, we’ll get to that below, because it’s not exactly WYSIWYG (What You See Is What You Get).
Generating the encrypted media
Once the correct signaling was confirmed, we needed to play the media, and this is where it usually gets tricky. SRTP keys could be exchanged via DTLS or directly within the SDP, and you definitely want to use the former, making the key exchange highly dynamic. This means you can’t just put the crypto keys into the SDP and replay a pcap with the encrypted stream. And even if that’d be acceptable, you don’t want to manage crypto keys and corresponding pcap streams, because what if you want to change the content being played? We can do better than hard-coding the pcap and putting the SDES keys into the SDP.
Since we’re using kamailio and rtpengine in front of sipp to perform IPv4/IPv6 bridging and transforming calls between UDP and TCP/TLS (as described in a previous post over here), I utilized rtpengine and its kamailio module to negotiate the crypto stuff and play the media.
And it couldn’t get much simpler than this in our kamailio.cfg (or your OpenSips config, if that’s your thing):
request_route {
...
if (is_method("INVITE") && sdp_content() && /* your call goes towards the E911 service */) {
rtpengine_offer("RTP/SAVPF");
}
...
}
onreply_route {
if (status =~ "18[0-9]|2[0-9][0-9]" && is_method("INVITE") && sdp_content()) {
rtpengine_answer();
if (/* your reply comes from the E911 service */) {
play_media("file=/path/to/media.wav");
}
...
}
}
On one hand, we’re telling the receiving end of the call to use RTP/SAVPF
for encryption, and once the SDP answer comes back in a 18x or 200,
we start playing our recording.
Note that you can control the codec being used directly in your sipp scenario xml file. rtpengine will honor that and will transcode your WAV input as long as your ffmpeg libs compiled into rtpengine support it.
Embedding its negotiation into the call flow
Remember the WYSIWYG from above? The great thing about kamailio and rtpengine running in tandem, and you instructing it to use SRTP by providing
the RTP/SAVPF
option in the offer is, that rtpengine is taking the SDP provided by our sipp scenario, and replaces it by its own version:
v=0
o=user1 53655765 2353687637 IN IP4 1.2.3.4
s=-
c=IN IP4 1.2.3.4
t=0 0
m=audio 10000 RTP/SAVPF 8 101
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=sendrecv
a=rtcp:10001
a=crypto:1 AEAD_AES_256_GCM inline:4N6S8Nci6odSWMhQQJ5BRnACPlrCKoc5Mh7SNJizvRaMDUDJO8r7ZXINfPU
a=crypto:2 AEAD_AES_128_GCM inline:7e/HvwJLXf/z9dH0UyNitVAnAUHK4/DGojOtYg
a=crypto:3 AES_256_CM_HMAC_SHA1_80 inline:gLv3g7gC0eGTGWMsCoLNqRe40o8tWdVQ/4TtVyrI+vHHSBPTEhAo+Q+PXAmKJA
a=crypto:4 AES_256_CM_HMAC_SHA1_32 inline:T5AbfwKyCoRukmUwGpcLGgaY3sFqyip+JFcDGSbYTHd3yO8+JVzOmTim5CIbnQ
a=crypto:5 AES_192_CM_HMAC_SHA1_80 inline:N2YDbo9FMIimUF+Gju+5p7E0zvA2LDaAAJLhXpR6s4GHMvRD1pQ
a=crypto:6 AES_192_CM_HMAC_SHA1_32 inline:CANcfFbr/SQ+ejUIqHPI4V+KUY7uja3h4XamqeT6JrSJXCvth9c
a=crypto:7 AES_CM_128_HMAC_SHA1_80 inline:qmbct9x2yy21Kpjk7N0s4HmJbTa5Q68qQOvl3Wem
a=crypto:8 AES_CM_128_HMAC_SHA1_32 inline:wcZZBqk7usZ9Ledgb1HeexNtR+vbfalxVDBmlVgC
a=crypto:9 F8_128_HMAC_SHA1_80 inline:5W1UNow/rbug1TxV+d+5sxPz/hrDyGihQYXSL+NE
a=crypto:10 F8_128_HMAC_SHA1_32 inline:V5Mxy41cOiNXwt0BN0+gVKRUgJAVSLSbvdXnIqap
a=crypto:11 NULL_HMAC_SHA1_80 inline:OpMm6hXFpdoN9180N6GSnkJsm0xQCBqS0o+dkATq
a=crypto:12 NULL_HMAC_SHA1_32 inline:CmIRYkIf3ONDV3xe9pXC1/yJ90WHra3pe2WhYOVs
a=setup:actpass
a=fingerprint:sha-256 84:E1:41:FD:9F:2B:CB:48:2E:AC:43:F2:40:87:FA:A2:41:EA:08:C7:2E:4D:68:91:8F:11:29:79:B7:C1:F1:3C
On the receiving end of the call, the crypto attributes required to set up an SRTP call are present automatically because rtpengine is generating them on demand and kamailio is inserting the SDP part into the offer automatically. Also, rtpengine is prepared to negotiate keys via DTLS to set up SRTP.
Conclusion
You will recognize that the solution to the request is not so much about E911. It’s about getting the SIP headers right, and making sure you’re able to establish an SRTP connection. If you struggle with that on your side, we’re happy to help out and provide you with a runnable test you can just start as needed, or schedule at given times.
sipp is great at constructing complex and exotic call scenarios, which would otherwise be hard to reproduce in a normal SIP client, but it sucks at handling media. On the other hand, kamailio and rtpengine are extremely flexible tools to manipulate signaling and media, but they suck at generating them, because they’re typically used on the server side to relay and transform them.
Putting them together on a client-side setup takes some fine-tuning, but once there, the combo turns out to be extremely powerful. We are doing
exactly this since years, so if you need help, ping me at agranig@sipfront.com, so you just click Run
instead of
struggling with creating your call scenario.