The purpose of this article if to demo the process of using Kamailio + RTP Engine to enable SIP based WebRTC call to a traditional SIP UA like Xlite. Kamailio Will thus provide not only call routing but also NATing , TLS and websocket support for webrtc endpoints.
For this bridging of SRTP from WebRTC endpoint like JSSIP to RTP for SIP UA like Xlite , we will use RTP engine .
Kamailio
Kamailio with modules like websocket , TLS , NATHelper which help it to support websocket based SIP which default kamailio configuration doesn’t.
Snippets from kamailio config to support webrtc endpoints is below . the detailed explanation is at kamailio config for webrtc
loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "kex.so"
loadmodule "corex.so"
loadmodule "tls.so"
loadmodule "xhttp.so"
loadmodule "websocket.so"
loadmodule "nathelper.so"
Configuration of some important modules
TLS module
To provide the SSL support which webrtc endpoints require Certificate Authority CA and provate certs signed by it
creating file paths
mkdir certs mkdir certs/private mkdir certs/newcerts touch certs/index.txt echo 01 >certs/serial echo 01 >certs/crlnumber
list the files
/home/ubuntu/certs# ls crlnumber index.txt newcerts private serial
create ca private key
openssl genrsa -out certs/private/cakey.pem 2048 chmod 600 certs/private/cakey.pem
create ca self signed certificate
openssl req -out certs/cacert.pem -x509 -new -key certs/private/cakey.pem
create server / client certificate, a private key (by name privkey.pem)
openssl req -out kamailio1_cert_req.pem -new -nodes openssl ca -in kamailio1_cert_req.pem -out kamailio1_cert.pem
output should be like
…
Certificate is to be certified until Jun 25 11:02:41 2020 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
and files genrated shoudl look like
/home/ubuntu# ls certs kamailio1_cert.pem kamailio1_cert_req.pem privkey.pem
copy the newly created certs to their respective paths
mkdir /etc/pki/CA/ cp kamailio1_cert.pem /etc/pki/CA/ cp privkey.pem /etc/pki/CA/
make list of ca certs by finidng all cacerts accross root firectory and appending them to a catlist pem
find / -name cacert.pem cat /usr/share/doc/libssl-doc/demos/cms/cacert.pem >> /home/ubuntu/catlist.pem cat /usr/share/doc/libssl-doc/demos/smime/cacert.pem >> /home/ubuntu/catlist.pem cat /home/ubuntu/kamailio_source_code/misc/tls-ca/rootCA/cacert.pem >> /home/ubuntu/catlist.pem ... cp /home/ubuntu/catlist.pem /etc/pki/CA/
update kamailio.cfg
#!ifdef WITH_TLS enable_tls=1 #!endif ... modparam("tls", "tls_method", "SSLv23") modparam("tls", "certificate", "/etc/pki/CA/kamailio1_cert.pem") modparam("tls", "private_key", "/etc/pki/CA/privkey.pem") modparam("tls", "ca_list", "/etc/pki/CA/calist.pem")
Websocket module
Websocket is considered a transport option just as TCP or UDP in kamailio config , hence just as one defines IP addr and ports for TCP, UDP protocol , we need to define the same for WS or WSS
#!substdef "!MY_WS_ADDR!tcp:MY_IP_ADDR:MY_WS_PORT!g" #!substdef "!MY_WSS_ADDR!tls:MY_IP_ADDR:MY_WSS_PORT!g" ... listen=MY_IP_ADDR #!ifdef WITH_WEBSOCKETS listen=MY_WS_ADDR #!ifdef WITH_TLS listen=MY_WSS_ADDR #!endif #!endif
check if port in R-URI meant for ws or wss, did not receive websocket or secure websocket
if (($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) && !(proto == WS || proto == WSS)) { xlog("L_WARN", "SIP request received on $Rp\n"); sl_send_reply("403", "Forbidden"); exit; }
request_route for websocket , included checking is client is behind NAT using nat_uac_test methods from NAThelper. If it is then for REGISTER methods do fix_nated_register
and for other add_contact_alias
#!ifdef WITH_WEBSOCKETS if (nat_uac_test(64)) { # NAT traversal WebSocket force_rport(); if (is_method("REGISTER")) { fix_nated_register(); } else { if (!add_contact_alias()) { xlog("L_ERR", "Error aliasing contact <$ct>\n"); sl_send_reply("400", "Bad Request"); exit; } } } #!endif
RTP engine
RTP relay and NAT helps with RTP packets
For detailed steps goto https://telecom.altanai.com/2018/04/03/rtp-engine-on-kamailio-sip-server/
dependencies
apt-get remove rtpproxy sudo apt install debhelper iptables-dev libcurl4-openssl-dev libglib2.0-dev libxmlrpc-core-c3-dev libhiredis-dev markdown build-essential:native
source
git clone https://github.com/sipwise/rtpengine.git cd rtpengine ./debian/flavors/no_ngcp
run
rtpengine --interface=54.86.35.95 --listen-ng=25061 --listen-cli=25062 --foreground --log-stderr --listen-udp=2222 --listen-tcp=25060
integrate with kamailio using
loadmodule "rtpengine.so" ... modparam("rtpengine", "rtpengine_sock", "udp:localhost:7722") modparam("nathelper", "received_avp", "$avp(s:rcv)") ... tbd
JSSIP
Like most other WebRTC libraries , JSSIP is event driven and provides provide core WEBRTC API like getUserMedia and RTP PeerConnection providing STUN,ICE,DTLS, SRTP features. It also integrated with rtcninja to provide cross browser accessibility. The differentiators with JSSIP lies in the fact that it supports SIP stack over websockets
Building JSSIP client for kamailio
Can use CDN based https://cdnjs.cloudflare.com/ajax/libs/jssip/3.1.2/jssip.min.js
or can take a pull from JSSIP repo and build urself using gulp https://github.com/versatica/JsSIP
instantiate JSSIP websocket interface with kamailio IP
var socket = new JsSIP.WebSocketInterface('wss://<kamailio_ip>:443');
Add configuration for registeration . Note if not using kamailio as proxy to SBC, it is recommended to add regiseteration features to provide user reachability for incoming calls and NAT pings
var configuration = {
sockets : socket,
uri : 'sip:username@example.com',
password : 'password'
};
create UA and start
var ua = new JsSIP.UA(configuration);
ua.start();
SIP over WEBSOCKET messages and kamailio processing
REGISTER sip JSSIP UA with user altanai , domina voiptelcom.com
REGISTER sip:voiptelco.com SIP/2.0 Via: SIP/2.0/WSS 830p2l39g8bg.invalid;branch=z9hG4bK242397 Max-Forwards: 69 To: From: ;tag=3jaad0q8l8 Call-ID: fvn2cd1b9gqh6kd7nqdpj5 CSeq: 1 REGISTER Contact: ;+sip.ice;reg-id=1;+sip.instance="";expires=600 Expires: 600 Allow: INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO Supported: path,gruu,outbound User-Agent: JsSIP 3.1.2 Content-Length: 0
Processed by kamailio using REGISTRAR route block
route(REGISTRAR); .. route[REGISTRAR] { if (is_method("REGISTER")) { if (!save("location")) { sl_reply_error(); } exit; } }
SIP/2.0 200 OK Via: SIP/2.0/WSS 830p2l39g8bg.invalid;branch=z9hG4bK242397;rport=19035;received=x.x.x.x To: ;tag=4dad943d40a0a309c33d64467664aa30.f6d3 From: ;tag=3jaad0q8l8 Call-ID: fvn2cd1b9gqh6kd7nqdpj5 CSeq: 1 REGISTER Contact: ;expires=600;received="sip:x.x.x.x:19035;transport=ws";pub-gruu="sip:altanai@voiptelco.com;gr=urn:uuid:80fa65e7-1cd7-4e40-bbee-c07f7a1ae9a5";temp-gruu="sip:uloc-5d260578-4b58-c-eeac6bd6@voiptelco.com;gr";+sip.instance="";reg-id=1 Server: kamailio (5.2.3 (x86_64/linux)) Content-Length: 0
INVITE from user1 altanai to john , notice that TO header doesnt have tag . this will handy for recognizing whether it is first message of dialog of=r and indialog message such as ACK , RE-INVITE , BYE etc
INVITE sip:john@voiptelco.com SIP/2.0 Via: SIP/2.0/WSS ipoct61ao12v.invalid;branch=z9hG4bK4220209 Max-Forwards: 69 To: sip:john@voiptelco.com From: sip:altanai@voiptelco.com;tag=2q0lecmbsn Call-ID: s8bnv5869fp68d1ju8c1 CSeq: 1799 INVITE Contact: Content-Type: application/sdp Session-Expires: 90 Allow: INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO Supported: timer,gruu,ice,replaces,outbound User-Agent: JsSIP 3.1.2 Content-Length: 1823
with SDP containing codecs and ICE details. Supporting audio over UDP/ TLS/RTL /SAVPF . Codecs beings
- 111 OPUS
- 103 ISAC/16000
- 104 ISAC/32000
- 9 G722
- 0 PCMU / G.711u narrowband
- 8 PCMA / G.711
- 106 , 105 , 13 – CN / comfort noise
- 110 , 112 , 113 , 126 – telephone-event / DTMF
v=0 o=- 4779000713447952953 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE 0 a=msid-semantic: WMS SFIXFrpsOUskJ5JQhp1mIARlDk6S3hVFTOBb m=audio 55839 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 c=IN IP4 192.168.0.3 a=rtcp:9 IN IP4 0.0.0.0 a=candidate:3802297132 1 udp 2122260223 192.168.0.3 55839 typ host generation 0 network-id 1 network-cost 10 a=candidate:2887880668 1 tcp 1518280447 192.168.0.3 9 typ host tcptype active generation 0 network-id 1 network-cost 10 a=ice-ufrag:0PIq a=ice-pwd:i7ccvGPXLDO5JqMwbCUqMcyN a=ice-options:trickle a=fingerprint:sha-256 AB:50:70:E3:57:E3:0C:7B:61:3B:03:5B:0F:54:14:14:9C:49:50:16:07:DC:E7:09:3E:4D:B5:A0:2B:EC:84:A1 a=setup:actpass a=mid:0 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=sendrecv a=msid:SFIXFrpsOUskJ5JQhp1mIARlDk6S3hVFTOBb e803836d-249a-4b81-b73f-17e0f08dde5a a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 a=ssrc:2800821831 cname:kLWViBLDLVfCXY8x a=ssrc:2800821831 msid:SFIXFrpsOUskJ5JQhp1mIARlDk6S3hVFTOBb e803836d-249a-4b81-b73f-17e0f08dde5a a=ssrc:2800821831 mslabel:SFIXFrpsOUskJ5JQhp1mIARlDk6S3hVFTOBb a=ssrc:2800821831 label:e803836d-249a-4b81-b73f-17e0f08dde5a
100 trying from callee . Note the to and from headers remain same for request or responses . This is send automatically by kamailio for INVITE.
SIP/2.0 100 trying -- your call is important to us Via: SIP/2.0/WSS ipoct61ao12v.invalid;branch=z9hG4bK4220209;rport=17502;received=x.x.x.x To: sip:john@voiptelco.com From: sip:altanai@voiptelco.com;tag=2q0lecmbsn Call-ID: s8bnv5869fp68d1ju8c1 CSeq: 1799 INVITE Server: kamailio (5.2.3 (x86_64/linux)) Content-Length: 0
180 ringing from Callee , note the addition of contact header
SIP/2.0 180 Ringing Record-Route: Via: SIP/2.0/WSS ipoct61ao12v.invalid;rport=17502;received=x.x.x.x;branch=z9hG4bK4220209 To: sip:john@voiptelco.com;tag=pvm73e3t89 From: sip:altanai@voiptelco.com;tag=2q0lecmbsn Call-ID: s8bnv5869fp68d1ju8c1 CSeq: 1799 INVITE Contact: sip:john@voiptelco.com;alias=x.x.x.x~17510~6;gr=urn:uuid:2e560b36-3ea8-41fd-80e3-ede66babb8a7 Supported: timer,gruu,ice,replaces,outbound Content-Length: 0
200 OK with SDP
SIP/2.0 200 OK Record-Route: Via: SIP/2.0/WSS ipoct61ao12v.invalid;rport=17502;received=x.x.x.x;branch=z9hG4bK4220209 To: sip:altanai@voiptelco.com;tag=pvm73e3t89 From: sip:john@voiptelco.com ;tag=2q0lecmbsn Call-ID: s8bnv5869fp68d1ju8c1 CSeq: 1799 INVITE Contact: sip:john@voiptelco.com;alias=x.x.x.x~17510~6;gr=urn:uuid:2e560b36-3ea8-41fd-80e3-ede66babb8a7 Session-Expires: 90;refresher=uas Supported: timer,gruu,ice,replaces,outbound Content-Type: application/sdp Content-Length: 1477
v=0 o=- 4562215268128860297 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE 0 a=msid-semantic: WMS oxU77z9z9RfNL4CayvM1cMJKI0r7u6ZdqLBd m=audio 55380 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 c=IN IP4 192.168.0.3 a=rtcp:9 IN IP4 0.0.0.0 a=candidate:3802297132 1 udp 2122260223 192.168.0.3 55380 typ host generation 0 network-id 1 network-cost 10 a=ice-ufrag:40h+ a=ice-pwd:6zJo50N7Bb2mqnrHq+jniukk a=ice-options:trickle a=fingerprint:sha-256 8A:F9:BE:8D:8A:80:FF:8C:89:3D:3A:D2:A1:36:B2:EC:11:53:81:7E:F4:53:E7:40:1E:B9:1E:A2:0F:D4:EA:2E a=setup:active a=mid:0 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:2 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id a=sendrecv a=msid:oxU77z9z9RfNL4CayvM1cMJKI0r7u6ZdqLBd cb6da6b5-d5b8-460e-88bc-1458ebc718e6 a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 a=ssrc:3489110087 cname:oZ2LjwJD385qPHHH
Kamailio handling replies using reply_route
onreply_route { if (nat_uac_test(64)) { add_contact_alias(); } }
Sending ACK .
ACK sip:john@voiptelco.com;alias=x.x.x.x~17510~6;gr=urn:uuid:2e560b36-3ea8-41fd-80e3-ede66babb8a7 SIP/2.0 Route: Via: SIP/2.0/WSS ipoct61ao12v.invalid;branch=z9hG4bK1876245 Max-Forwards: 69 To: sip:altanai@voiptelco.com ;tag=pvm73e3t89 From: sip:john@voiptelco.com;tag=2q0lecmbsn Call-ID: s8bnv5869fp68d1ju8c1 CSeq: 1799 ACK Allow: INVITE,ACK,CANCEL,BYE,UPDATE,MESSAGE,OPTIONS,REFER,INFO Supported: outbound User-Agent: JsSIP 3.1.2 Content-Length: 0
since ACK is a Within dialog message and sequential request withing a dialog should take the path determined by record-routing , we first check if it has to tag. Having a to tag validates that it is a in-dialog request .
After this validate if is loose_route()
and has no destination URI $du
, then try to add rui alias using handle_ruri_alias()
, if that fails , reject the request .
If it is not loose_route()
and method is ACK then check if the ACK matches a transaction t_check_trans()
ie is stateful . If it is then relay otherwise reject.
route(WITHINDLG); ... route[WITHINDLG] { if (has_totag()) { if (loose_route()) { if ($du == "") { if (!handle_ruri_alias()) { xlog("L_ERR", "Bad alias <$ru>\n"); sl_send_reply("400", "Bad Request"); exit; } } route(RELAY); } else { if ( is_method("ACK") ) { if ( t_check_trans() ) { t_relay(); exit; } else { exit; } } sl_send_reply("404", "Not Found"); } exit; } }
Also required to convert ICE packet fromWebRTC to non ICE for Xlite.