Kamailio WebRTC SIP Server

  1. Why Kamailio?
  2. Kamailio SIP Signaling Proxy
  3. System Architecture
  4. TLS module Configuration Guide
    1. Step 1: Create Certificate Directory Structure
    2. Step 2: Generate Certificate Authority (CA)
    3. Step 3: Generate Server Certificate
    4. Step 4: Install Certificates
    5. Step 5: Create CA Certificate List
    6. Step 6: Configure TLS in kamailio.cfg
    7. Testing TLS Configuration
  5. Websocket module
    1. Configuration
    2. NAT Detection and Handling
  6. RTPengine
    1. Installation
    2. Running RTP Engine
    3. Kamailio RTP Engine Integration
    4. Testing RTP Engine
  7. JSSIP
    1. JSSIP WebRTC client for Kamailio
  8. SIP over WEBSOCKET messages and Kamailio processing
    1. Registration Flow
    2. INVITE (Call Setup) Flow
  9. References and Resources
    1. Official Documentation
    2. Example Configurations
    3. Related Projects
    4. WebRTC Clients
    5. Learning Resources

Kamailio is an open-source SIP (Session Initiation Protocol) server that provides advanced routing, protocol support, and media handling capabilities. This article demonstrates how to configure Kamailio with RTP Engine to enable WebRTC-to-SIP interoperability, allowing web-based real-time communication clients to seamlessly connect with traditional SIP User Agents (UAs).

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.

  • Protocol Bridging: Convert between WebRTC’s secure SRTP (Secure RTP) and traditional SIP’s RTP
  • NAT Traversal: Provide address/port translation for endpoints behind firewalls/NATs
  • Security: Enable TLS/WSS encryption for WebRTC endpoints
  • Cross-Protocol Communication: Allow WebRTC clients (e.g., JSSIP) to communicate with legacy SIP endpoints (e.g., X-Lite, Linphone)

Why Kamailio?

Kamailio (formerly OpenSER) is widely adopted in carrier-grade VoIP infrastructure because it:

  • Scales horizontally to handle millions of concurrent sessions
  • Provides modular architecture with 100+ loadable modules
  • Supports advanced SIP features (Presence, SUBSCRIBE/NOTIFY, GRUU, etc.)
  • Offers excellent NAT traversal and media relay capabilities
  • Has active community support and comprehensive documentation

Official Websitekamailio.org

Kamailio SIP Signaling Proxy

Kamailio functions as the central SIP routing engine, handling:

  • User registration and location management
  • SIP message routing and forwarding
  • NAT detection and traversal
  • Session state management
  • Media negotiation

Essential modules for WebRTC support:

loadmodule "tm.so"           # Transaction Manager - handles request/response matching
loadmodule "sl.so"           # Stateless Reply - quick responses
loadmodule "rr.so"           # Record-Route - tracks SIP hop path
loadmodule "pv.so"           # Pseudo-Variables - $ru, $Rp, $si, etc.
loadmodule "maxfwd.so"       # Max-Forwards header processing
loadmodule "usrloc.so"       # User Location - registration database
loadmodule "textops.so"      # Text operations on message bodies
loadmodule "siputils.so"     # SIP utility functions
loadmodule "xlog.so"         # Logging module
loadmodule "sanity.so"       # SIP message sanity validation
loadmodule "ctl.so"          # Control socket for management
loadmodule "kex.so"          # Kamailio extensions
loadmodule "corex.so"        # Core extensions
loadmodule "tls.so"          # TLS/SSL support for secure connections
loadmodule "xhttp.so"        # HTTP request handler
loadmodule "websocket.so"    # WebSocket transport (RFC 7118)
loadmodule "nathelper.so"    # NAT detection and manipulation

A complete working configuration is available at: kamailioexamples/webrtc_to_webrtc_ws

System Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Web Browser │
│ (JsSIP WebRTC Client) │
│ getUserMedia, RTCPeerConnection │
└────────────────────────────┬────────────────────────────────────┘

WSS (WebSocket Secure)
TLS 1.2+ Encrypted

┌────────────────────────────▼────────────────────────────────────┐
│ │
│ KAMAILIO SIP SERVER │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Modules: TLS, WebSocket, NATHelper, Registrar, Routing │ │
│ │ Listens: WSS:443 (WebSocket Secure) │ │
│ │ TCP:5060 (SIP) │ │
│ │ Functions: Registration, Call Routing, NAT Detection │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└────────────────────┬─────────────────────────┬──────────────────┘
│ │
│ │ TCP/UDP
│ SIP Signaling
│ │
UDP:2222 (NG Control Protocol) │
UDP/TCP (RTP Media) │
│ │
┌────────────────────▼──────────────────┐ ┌──▼──────────────────────┐
│ │ │ │
│ RTP ENGINE (Media Relay) │ │ Legacy SIP Endpoint │
│ • Codec Transcoding (OPUS→PCMU, etc) │ │ (X-Lite, Polycom, │
│ • SRTP↔RTP Conversion │ │ Cisco, Linphone, etc) │
│ • NAT Traversal │ │ │
│ • ICE Handling │ │ Traditional Phone PBX │
│ │ │ │
└───────────────────────────────────────┘ └─────────────────────────┘

Media Flow Example:

  • Media streams: Browser → RTP Engine (SRTP/Opus) → SIP Phone (RTP/PCMU)
  • Browser user initiates call with Opus codec (48kHz)
  • Kamailio routes INVITE to RTP Engine
  • RTP Engine negotiates codec with legacy endpoint (accepts PCMU/G.711)

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

Configuration of the important modules

TLS module Configuration Guide

WebRTC endpoints require secure connections (WSS – WebSocket Secure), necessitating proper SSL/TLS certificates and keys.

Step 1: Create Certificate Directory Structure

mkdir -p certs/{private,newcerts}
touch certs/index.txt
echo 01 > certs/serial
echo 01 > certs/crlnumber

ls -la certs/
# Output:
# -rw-r--r--  1 root root    0 Dec 29 10:00 index.txt
# -rw-r--r--  1 root root    3 Dec 29 10:00 serial
# -rw-r--r--  1 root root    3 Dec 29 10:00 crlnumber
# drwxr-xr-x  2 root root 4096 Dec 29 10:00 newcerts/
# drwx------  2 root root 4096 Dec 29 10:00 private/

Step 2: Generate Certificate Authority (CA)

Create CA private key (2048-bit RSA):

openssl genrsa -out certs/private/cakey.pem 2048
chmod 600 certs/private/cakey.pem

Output:

Generating RSA private key, 2048 bit long modulus (2 primes)
.................................................................................+++++
........................................+++++
e is 65537 (0x010001)

Create self-signed CA certificate (valid for 365 days):

openssl req -out certs/cacert.pem -x509 -new -key certs/private/cakey.pem \
  -days 365 -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/CN=kamailio-ca"

Verify CA certificate:

openssl x509 -in certs/cacert.pem -text -noout | head -20

Step 3: Generate Server Certificate

Create Certificate Signing Request (CSR):

openssl req -out kamailio1_cert_req.pem -new -nodes \
  -keyout privkey.pem \
  -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/CN=kamailio.example.com"

Sign with CA:

openssl ca -in kamailio1_cert_req.pem -out kamailio1_cert.pem \
  -config /etc/ssl/openssl.cnf -days 365

Interactive output:

Using configuration from /etc/ssl/openssl.cnf
Check that the request matched the signing rules
Signature ok
Certificate Details:
    Serial Number: 1 (0x1)
    Not Before: Dec 29 10:00:00 2024 GMT
    Not After : Dec 29 10:00:00 2025 GMT
    ...
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

Step 4: Install Certificates

# Create system certificate directory
sudo mkdir -p /etc/pki/CA/
sudo mkdir -p /etc/pki/tls/private

# Copy certificates
sudo cp kamailio1_cert.pem /etc/pki/CA/
sudo cp privkey.pem /etc/pki/CA/
sudo cp certs/cacert.pem /etc/pki/CA/

# Set proper permissions
sudo chmod 644 /etc/pki/CA/kamailio1_cert.pem
sudo chmod 644 /etc/pki/CA/cacert.pem
sudo chmod 600 /etc/pki/CA/privkey.pem

# Verify installation
ls -la /etc/pki/CA/
# -rw-r--r--  1 root root 1200 Dec 29 10:00 cacert.pem
# -rw-r--r--  1 root root 1456 Dec 29 10:00 kamailio1_cert.pem
# -r--------  1 root root 1708 Dec 29 10:00 privkey.pem

Step 5: Create CA Certificate List

For client certificate verification:

# Compile system CA certificates
cat /etc/ssl/certs/ca-certificates.crt > /etc/pki/CA/calist.pem

# Add your custom CA
cat certs/cacert.pem >> /etc/pki/CA/calist.pem

# Verify
openssl crl2pkcs7 -nocrl -certfile /etc/pki/CA/calist.pem | \
  openssl pkcs7 -print_certs -noout | grep Subject

Step 6: Configure TLS in kamailio.cfg

#!ifdef WITH_TLS
enable_tls=1
#!endif

# TLS module parameters
modparam("tls", "tls_method", "TLSv1_2+")
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")

# Security options
modparam("tls", "verify_certificate", 0)
modparam("tls", "require_certificate", 0)
modparam("tls", "cipher_list", "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK")

# SSL/TLS session cache
modparam("tls", "session_cache", 1)
modparam("tls", "session_id", "kamailio-webrtc")

Testing TLS Configuration

# Test certificate validity
openssl s_client -connect localhost:443 -CAfile certs/cacert.pem

# Expected output:
# Verification: OK
# Verify return code: 0 (ok)

# Check certificate details
echo | openssl s_client -connect localhost:443 2>/dev/null | \
  openssl x509 -noout -dates

Websocket module

WebSocket (RFC 6455) enables full-duplex communication over HTTP/HTTPS, ideal for browsers. In Kamailio, WebSocket is treated as a transport protocol like TCP/UDP. 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

Configuration

Define WebSocket address constants:

#!substdef "!MY_IP_ADDR!192.168.1.100!g"
#!substdef "!MY_WS_PORT!80!g"
#!substdef "!MY_WSS_PORT!443!g"

#!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 on all available addresses
listen=MY_IP_ADDR

#!ifdef WITH_WEBSOCKETS
listen=MY_WS_ADDR    # ws://192.168.1.100:80
#!ifdef WITH_TLS
listen=MY_WSS_ADDR   # wss://192.168.1.100:443
#!endif
#!endif
#!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

Port validation (reject non-WebSocket traffic on WS ports):

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 .


NAT Detection and Handling

WebSocket clients typically operate behind NATs and firewalls. Kamailio uses nat_uac_test() to detect this:

NAT detection flags:

  • 1: Check Via/Source IP mismatch
  • 2: Check Via/Source port mismatch
  • 4: Check Received header presence
  • 8: Check proto!=udp
  • 64: Check Contact header against source IP:port (most comprehensive)

WebSocket NAT handling implementation:

#!ifdef WITH_WEBSOCKETS
if (nat_uac_test(64)) {
    # Force symmetric RTP using source address/port
    force_rport();
    force_local_rport();
    
    if (is_method("REGISTER")) {
        # Fix Contact header for REGISTER messages
        fix_nated_register();
        xlog("L_INFO", "Fixed NAT registration for $fu\n");
    } else {
        # Add contact alias for INVITE/MESSAGE/etc.
        if (!add_contact_alias()) {
            xlog("L_ERR", "Error aliasing contact <$ct> from $si:$sp\n");
            sl_send_reply("400", "Bad Request");
            exit;
        }
        xlog("L_INFO", "Added contact alias from $si:$sp\n");
    }
}
#!endif

How contact aliasing works:

Original Contact in REGISTER:
  Contact: <sip:john@192.168.1.50:5060>;expires=3600

Received from public IP 203.0.113.10:19035

Kamailio modifies it:
  Contact: <sip:john@203.0.113.10:19035>;alias=203.0.113.10~19035~6;expires=3600
            ^────────────────────────────^
            Public IP:Port for routing

Then when sending messages back to John:
  Route via: sip:john@203.0.113.10:19035 (public address)
  handle_ruri_alias() translates back to 192.168.1.50:5060 (private address)

Complete WebSocket routing block:

request_route {
    # 1. Port validation
    if (($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT) && !(proto == WS || proto == WSS)) {
        xlog("L_WARN", "Non-WS message on WS port\n");
        sl_send_reply("403", "WebSocket Required");
        exit;
    }

    # 2. NAT detection and handling
    #!ifdef WITH_WEBSOCKETS
    if (nat_uac_test(64)) {
        force_rport();
        force_local_rport();
        
        if (is_method("REGISTER")) {
            fix_nated_register();
        } else {
            if (!add_contact_alias()) {
                xlog("L_ERR", "Bad contact alias\n");
                sl_send_reply("400", "Bad Request");
                exit;
            }
        }
    }
    #!endif

    # 3. Route to appropriate handler
    if (is_method("REGISTER")) {
        route(REGISTRAR);
    } else {
        route(INVOKE);
    }
}

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/

RTP Engine is critical for WebRTC-to-SIP bridging because it:

  • Converts SRTP to RTP: WebRTC uses Secure RTP; legacy phones use plaintext RTP
  • Transcodes Codecs: Bridges Opus (WebRTC) ↔ PCMU/G.711 (SIP phones)
  • Handles ICE Candidates: Manages WebRTC’s ICE protocol for NAT traversal
  • Provides Media Relay: Acts as SDP answer/offer intermediary
  • Records Calls: Saves media streams for compliance/audit

Installation

Install dependencies (Ubuntu/Debian):

sudo apt-get update
sudo apt-get remove rtpproxy -y  # Remove if conflicts

sudo apt-get install -y \
  debhelper iptables-dev \
  libcurl4-openssl-dev libglib2.0-dev \
  libxmlrpc-core-c3-dev libhiredis-dev \
  markdown build-essential git \
  libpcre3-dev libssl-dev

# Optional: for transcoding support
sudo apt-get install -y libavformat-dev libavcodec-dev libavdevice-dev

Build from source:

git clone https://github.com/sipwise/rtpengine.git
cd rtpengine

# Check if no_ngcp flavor is available
ls -la debian/flavors/

# Build
./debian/flavors/no_ngcp

# This creates debs in parent directory
cd ..
ls -la ngcp-rtpengine*.deb

# Install
sudo dpkg -i ngcp-rtpengine-daemon_*.deb
sudo dpkg -i ngcp-rtpengine-ctl_*.deb
sudo dpkg -i ngcp-rtpengine_*.deb

Running RTP Engine

Basic startup:

rtpengine --interface=192.168.1.100 \
  --listen-ng=127.0.0.1:2223 \
  --listen-cli=127.0.0.1:2224 \
  --foreground --log-stderr

Advanced configuration with NG (Next Generation) protocol:

rtpengine \
  --interface=192.168.1.100:203.0.113.100 \
  --listen-ng=127.0.0.1:2223 \
  --listen-cli=127.0.0.1:2224 \
  --listen-udp=2222 \
  --listen-tcp=25060 \
  --max-sessions=10000 \
  --max-load=800 \
  --timeout=60 \
  --silent-timeout=900 \
  --foreground --log-stderr --log-level=5

Parameters explained:

  • --interface: IP to use for RTP media (can have public:private pair)
  • --listen-ng: Control protocol port (localhost port 2223)
  • --listen-cli: CLI management port (localhost port 2224)
  • --max-sessions: Maximum concurrent RTP sessions
  • --max-load: CPU load threshold before rejecting new sessions
  • --timeout: Timeout for idle sessions (seconds)
  • --silent-timeout: Timeout when no media detected (seconds)

As systemd service (/etc/systemd/system/rtpengine.service):

[Unit]
Description=RTP Engine
After=network.target

[Service]
Type=simple
ExecStart=/usr/sbin/rtpengine \
  --interface=192.168.1.100 \
  --listen-ng=127.0.0.1:2223 \
  --listen-cli=127.0.0.1:2224 \
  --max-sessions=5000 \
  --foreground --log-stderr
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Kamailio RTP Engine Integration

Load module:

loadmodule "rtpengine.so"

Configure module parameters:

modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
modparam("rtpengine", "rtpengine_allow_op", 1)
modparam("rtpengine", "log_level", 5)

modparam("nathelper", "received_avp", "$avp(s:rcv)")

Implement media relay in routing logic:

route[RELAY] {
    # Enable RTP relaying with options
    # trust-address: trust INVITE source over From header
    # replace-origin: fix SDP o= line with media source
    # replace-session-connection: fix c= line
    # ICE=force: force ICE for all calls
    
    if (!rtpengine_manage("trust-address replace-origin replace-session-connection ICE=force")) {
        xlog("L_ERR", "RTP Engine management failed\n");
        sl_send_reply("488", "Not Acceptable Here");
        exit;
    }
    
    if (!t_relay()) {
        xlog("L_ERR", "Relay failed\n");
        sl_send_reply("500", "Server Internal Error");
    }
    exit;
}

Common RTP Engine flags:

FlagPurpose
trust-addressUse INVITE source instead of From header
replace-originFix SDP origin (o=) line
replace-session-connectionFix SDP connection (c=) line
symmetricUse symmetric RTP (outgoing = incoming addresses)
ICE=forceForce ICE for all calls
ICE=removeRemove ICE from all calls
DTLS=offerOffer DTLS if peer supports
DTLS=requireRequire DTLS

Example: WebRTC-to-SIP bridging:

# For WebRTC→SIP calls (RTC client calling legacy phone)
rtpengine_manage("trust-address replace-origin replace-session-connection");

# For SIP→WebRTC calls (legacy phone calling RTC client)
rtpengine_manage("trust-address replace-origin replace-session-connection ICE=force");

Testing RTP Engine

# Check if RTP Engine is responding
echo -n "ping\n" | nc -u 127.0.0.1 2223

# Expected: "pong\n"

# View RTP Engine statistics
rtpengine-ctl status

# View active sessions
rtpengine-ctl list

# Clear all sessions
rtpengine-ctl purge

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 WebSocket

JSSIP WebRTC client for Kamailio

For the 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 oneself 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 registration . Note if not using Kamailio as proxy to SBC, it is recommended to add registration 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

Registration Flow

JsSIP Client                              Kamailio
│ │
│─────────────REGISTER────────────────→ │
│ Via: SIP/2.0/WSS client.invalid │
│ From: sip:user@example.com;tag=xxx │
│ To: sip:user@example.com │
│ Contact: sip:user@203.x.x.x:19035 │
│ Expires: 600 │
│ │ [Save location]
│← ────────────200 OK────────────────── │
│ Contact: sip:user@203.x.x.x:19035 │
│ ;received="sip:203.x.x.x..."│
│ ;expires=600 │
│ │

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 (Call Setup) Flow

JsSIP (WebRTC)                         Kamailio                      X-Lite (SIP)
│ │ │
│─────────────INVITE─────────────────→│ │
│ + SDP (Opus/SRTP) │ │
│ │ [Pass to RTP Engine] │
│ │ [RTP Relay]│
│ │──────────INVITE──────────────→
│ │ + SDP (PCMU/RTP) │
│ │ │
│← ────────100 Trying──────────────── │← ────────100 Trying─────────│
│ │ [RTP Engine handles] │
│← ────────180 Ringing─────────────── │← ────────180 Ringing────────│
│ (with early media SDP) │ │
│ │ │
│ [User picks up phone] │ │
│← ────────200 OK──────────────────── │← ────────200 OK────────────│
│ + SDP (Answer) │ + SDP (Answer) │
│ [RTP Engine updates connection] │ │
│ │ │
│─────────────ACK───────────────────→ │──────────ACK──────────────→ │
│ │ │
│ [MEDIA FLOWS] │
│ Opus SRTP ←→ RTP Engine ←→ PCMU RTP │
│ │ │
│─────────────BYE───────────────────→ │──────────BYE──────────────→ │
│ │ │
│← ────────200 OK──────────────────── │← ────────200 OK────────────│

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.

References and Resources

Official Documentation

Example Configurations

WebRTC Clients

Learning Resources

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.