- Kamailio
- TLS module
- Websocket module
- RTPengine
- JSSIP
- JSSIP WebRTC client for kamailio
- SIP over WEBSOCKET messages and kamailio processing
- REGISTER sip JSSIP UA
- Kamailio REGISTRAR
- INVITE + SDP
- 100 trying from callee
- 180 ringing from Callee
- 200 ok + SDP
- Kamailio’s reply_route
- ACK
The purpose of this article is 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 the RTP engine.
Kamailio
Kamailio’s modules on WebSocket, TLS, NATHelper help it to support WebSocket based SIP which the default Kamailio configuration doesn’t. Snippets from Kamailio config to support webrtc endpoints is below. A full kamailio config is present here https://github.com/altanai/kamailioexamples/blob/master/webrtc_to_webrtc_ws/websocket_tls_webrtc_kamailio.cfg
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 the 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
RTPengine
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
JSSIP WebRTC client for kamailio
For tghe JSSIP default cllient UI and library one 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 be handy for recognizing whether it is first message of dialog offer and in-dialog 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.
Resources :
- Kamailio Scripts for Webrtc sessions
- Webrtc Clients
- Webrtc Solution Architecture