Elixir integration for the Kamailio SIP server.
ex_kamailio speaks the rtpengine ng control protocol over a WebSocket, so
a Kamailio routing script can hand media setup to an Elixir application.
Why
Kamailio does the SIP signaling and leaves the media to rtpengine. With
ex_kamailio an Elixir application takes rtpengine's place: one behaviour,
called with each call's SDP, decides where the media goes.
It was written to put a Membrane pipeline between two SIP peers, but it neither depends on Membrane nor assumes it.
Installation
Add ex_kamailio to your dependencies:
def deps do
[
{:ex_kamailio, "~> 0.1.0"}
]
endWriting a call handler
defmodule MyApp.KamailioHandler do
use ExKamailio.CallHandler
@impl true
def init(_session, _opts), do: {:ok, %{pipeline: nil}}
@impl true
def handle_offer(offer, session, state) do
{:ok, reply_sdp, pid} = MyApp.MediaPipeline.start_link(session.call_id, offer)
{:ok, reply_sdp, %{state | pipeline: pid}}
end
@impl true
def handle_answer(answer, _session, state) do
{:ok, reply_sdp} = MyApp.MediaPipeline.add_answerer(state.pipeline, answer)
{:ok, reply_sdp, state}
end
@impl true
def handle_delete(_session, state) do
MyApp.MediaPipeline.terminate(state.pipeline)
:ok
end
endThe callbacks get the peer's SDP as %ExSDP{} and return the %ExSDP{} to
send on. Each call runs in its own process, with its own state.
Configuration
config :ex_kamailio,
# Your `ExKamailio.CallHandler` module, or `{module, opts}`. Default:
# `ExKamailio.CallHandler.Default`, which returns each peer's SDP unchanged.
call_handler: MyApp.KamailioHandler,
# Where the WebSocket server listens for Kamailio. Defaults: loopback, 4003.
ws_ip: :loopback,
ws_port: 4003,
# Idle time before `handle_idle/2` fires. Default: 30 min.
idle_timeout: :timer.minutes(30),
# Per-callback deadline; keep under Kamailio's `rtpengine_tout_ms`. Default: 800 ms.
callback_timeout: 800The WebSocket is plain ws, no TLS. When Kamailio runs on another host or in
another container, set ws_ip to an address it can reach, {0, 0, 0, 0}
binds every interface, and keep the port off the public internet.
examples/recording_relay is a complete
demo: a handler that relays the media through a Membrane pipeline and records
it, with Kamailio and SIPp in Docker and an end-to-end test.
Call flow
Peers are named by their RFC 3264 roles: the offerer proposes SDP, the
answerer responds. In the initial INVITE the offerer is the caller.
- The offerer sends
INVITEwith SDP. Kamailio passes the SDP toex_kamailioas anoffercommand.ex_kamailiostarts a call process and callsExKamailio.CallHandler.handle_offer/3; the SDP it returns goes into theINVITEforwarded to the answerer. - The answerer replies
200 OKwith SDP. Kamailio passes it as ananswercommand, and the SDPExKamailio.CallHandler.handle_answer/3returns goes into the200 OKforwarded to the offerer. - On
BYE, Kamailio sendsdeleteandExKamailio.CallHandler.handle_delete/2releases what the handler allocated. A cancelled or rejectedINVITEsends nothing; that call ends by the idle timeout below.
Idle calls
When delete never comes, because a peer crashed or a BYE got lost, the
call process would live forever. So when no command arrives for
:idle_timeout (default 30 min), ExKamailio.CallHandler.handle_idle/2
runs. Its default returns {:stop, state}, which runs handle_delete/2 and
stops the process; return {:ok, state} to keep the call.
This only frees the process. The SIP dialog goes on, and both peers may keep sending and expecting media.
Kamailio config
priv/kamailio/kamailio.cfg is a minimal config that wires the rtpengine
and lwsc modules to ex_kamailio: a registrar with no authentication and a
proxy that puts every call to a registered user through the handler.
Copy it and extend it when it stops being enough; its header lists what a
real deployment would add.
It reads two environment variables:
RTPENGINE_SOCK: whereex_kamailiolistens, as a WebSocket URL.ws://127.0.0.1:4003when Kamailio shares the host's network,ws://<name>:4003when it reaches the app by DNS, e.g. a Docker service name.ADVERTISE_IP: the address peers reach Kamailio at. Used only with-A PUBLIC_MODE.
By default Kamailio binds 0.0.0.0:5060 and advertises that, which breaks
in-dialog requests (BYE, ACK) as soon as the peers are on other hosts.
Start it with -A PUBLIC_MODE to advertise ADVERTISE_IP instead.
Status
Implements the offer, answer, delete and ping rtpengine commands.
update and query are not covered yet.
License
MIT. See LICENSE. Portions derive from the ex_media project by Javier Gallart / BTS, used with permission.
Authors
ex_kamailio is created by Software Mansion.
Since 2012 Software Mansion is a software agency with experience in building web and mobile apps as well as complex multimedia solutions. We are Core React Native Contributors and experts in live streaming and broadcasting technologies. We can help you build your next dream product – Hire us.
ex_kamailio was originally extracted from a Kamailio integration written by
Javier Gallart (BTS), who graciously permitted relicensing the relevant
parts as open source.