В этой статье мы рассмотрим взаимодействие и настройку Kamailio + WebRTC. Рассмотрим несколько клиентов, а также на практике увидим взаимодействие нескольких модулей для терминации SSL и работы с самими WebSocket’ами в Kamailio.
Теория работы SIP протокола через браузер — WebRTC
Раньше для взаимодействия с SIP протоколом, необходимы были SIP клиенты. SIP клиенты делятся на два вида:
- Программные клиенты, их часто называют софтфонами (softphone). Что это такое? Прежде всего это клиентское приложение. Пользователь, устанавливает это приложение в свою ОС и благодаря этому получает возможность взаимодействовать с SIP сервером через SIP протокол (Обработка SIP регистрации в Kamailio, SIP INVITE обработка в Kamailio, получать медиа услуги от сервера и.т.д). Популярными программными клиентами являются, как пример, Zoiper, MicroSIP и тысячи других приложений на различных платформах (ПК, приложения для смартфонов…)
- Аппаратные клиенты. Это всеми привычные стационарные SIP телефоны, которые также как и программные клиенты используются для взаимодействия с SIP сервером. Исторически появились раньше, чем программные решения, придя из аналоговой телефонии. Современные аппаратные клиенты могут включать как беспроводные решения, так и всеми привычные проводные. Обычно у каждого телефона имеется Web интерфейс (или один общий Web интерфейс) с помощью которого выполняется настройка.
Сейчас активно используются как программные клиенты, так и аппаратные (физические) клиенты.
А теперь давайте представим, что мы хотим предоставить некий облачный сервис, который позволяет пользователям не устанавливая никакое дополнительное ПО, не закупая физические трубки, использовать все возможности SIP телефонии, предоставляемые нашим облачным сервисом. Как это можно сделать?
Встречайте — WebRTC
WebRTC — это технология (проект) с открытым исходным кодом, который позволяет организовать передачу данных в реальном времени между браузерами. Т.е WebRTC позволяет самому браузеру (без установки доп.плагинов и каких-либо модификаций) передавать медиа потоки.
Прежде всего важно понять, что WebRTC — это API в браузере, который позволяет передавать голос (+видео) через этот самый браузер. Все что делает WebRTC — это предоставляет API в браузере клиента с помощью которого можно захватить голос/видео и преобразовать голос/видео в медиа поток. Ничего нового, очень подробно работу с кодеками (а WebRTC преобразует голос/видео в медиа поток или же цифровые данные с помощью тех же кодеков, как и в классической SIP телефонии) рассматривалось в предыдущей статье (Обработка RTP в Kamailio).
Т.к WebRTC это скорее про peer-to-peer соединение, то нам необходим транспорт, который будет взаимодействовать с нашим SIP сервером. Здесь и появляется еще один столп звонков в браузере — WebSocket. Замечу, что в большинстве случае связка WebRTC использует под собой как минимум транспортный протокол для связи с серверов, т.е peer-to-peer по факту не получиться т.к нужно будет обвязка для всего этого (для взаимодействия с сервером).
WebSocket как транспорт для WebRTC
WebRTC не выполняет SIP сигнализацию напрямую с SIP серовером, поэтому для взаимодействия с сервером по протоколу SIP, нам необходимо использовать некий транспорт, коим и является WebSocket’ы (также можно использовать HTTP и другие, но в подавляющем большинстве используется WebSocket’s). Т.е мы SIP сигнализацию завернем в WebSocket’ы для передачи.
WebSocket протокол очень похож на HTTP (но не является им, есть отличия). Если проводить аналогию, HTTP — это WebSocket (ws), а HTTPS — это WebSocket Secure (wss). Далее по тексту, под WebSocket нужно понимать, что речь идет о защищенной версии WebSocket’ов (WSS), если другое не оговаривается.
WebSocket’ы работают через TCP протокол (в то время как классическая телефония в подавляющем большинстве случаев использует UDP). Также сразу отмечу, что почти всегда, WebSocket’ы в браузере работают только через WSS (WebSocket Secure) т.е с использованием TLS сертификатов.
WebSocket передает внутри себя SIP сигнализацию, а WebRTC используется для уже непосредственной доставки звука/видео в браузер (захватывает звук с микрофона/камеры, использует кодеки для кодирования звука/видео и передача его через API браузера)
Саму SIP сигнализацию мы рассматривать не будем, т.к протокол разбирается на протяжении всего цикла статей (Kamailio с нуля), также мы не будет углубляться в работу TLS.
Необходимо только уточнение, что для работы WebRTC необходимо использование протокола ICE (рассматривалось в статье Kamailio часть 2. Прохождение NAT в SIP/Kamailio)
TLS как транспорт для WebSocket
WebSocket (как и HTTP) протокол является прикладным протоколом, в сетевой модели OSI они занимают 7 уровень, в сетевой модели TCP/IP (которая сейчас используется) — 4 уровень. Важно отметить, что WebRTC использует WebSocket’ы в качестве транспорта именно на прикладном уровне, это очень важно понимать. Сам же прикладной протокол WebSocket использует уже в качестве своего транспорта 3 уровень модели TCP/IP, а именно TCP(+ TLS который работает поверх TCP, для наглядности объединен в один уровень с TCP, но отмечу, что сам протокол TLS работает на прикладном уровне). Схема взаимодействия получается следующей:
Отправка пакетов к удаленной стороне происходит сверху вниз (WebRTC -> WebSocket -> TLS -> TCP -> IPv4 -> Ethernet, протоколы межсетевого уровня и канального уровня могут отличаться), а прием на удаленной стороне происходит наоборот, снизу вверх.
Как я упомянул ранее, сейчас по умолчанию используется защищенные соединения, TLS — это протокол прикладного уровня, который работает поверх протокола TCP. Для установки защищенного соединения используются сертификаты безопасности. Это может быть PEM формат, JKS хранилище (используется в Java приложениях) или же PKCS12 хранилище. Сам сертификат разделен на две взаимосвязанных части:
- Цепочка сертификатов (сам сертификат, промежуточный и сертификат удостоверяющего центра)
- Ключ, который состоит из двух частей, отрытого и закрытого. Открытая часть ключа используется для подписания, закрытая часть для расшифровки. Закрытая часть, он же приватный ключ, который никто не должен знать, кроме самого сервера
Первым делом, Клиент (напр. браузер) устанавливает TCP сессию с удаленным сервером (Отправляет запрос SYN, сервер отвечает SYN-ACK, клиент подтверждает получение через ответ ACK), после чего, клиент отправляет запрос на установление защищенной TLS сессии, для этого он отправляет запрос ClientHello, указывает версию TLS протокола (напр. TLSv1.2), указывает поддерживаемые алгоритмы шифрования.
Сервер, получив запрос ClientHello отвечает ServerHello, в котором содержится серверный публичный сертификат + открытая часть ключа.
Клиент верифицирует полученный серверный сертификат (с помощью удаленного УЦ или с помощью корневых сертификатов, установленных в самой операционной системе)
Клиент генерирует случайную последовательность цифр, которую подписывает полученным открытым ключом от сервера и отправляет зашифрованные пакеты обратно на сервер.
Сервер расшифровывает полученные пакеты от клиента с помощью закрытого ключа.
Практическая часть. Реализация WebRTC в Kamailio
Как мы уже поняли ранее, WebRTC для работы с SIP сервером использует несколько протоколов. Далее, мы по очереди настроим каждый протокол в конфигурационном файле Kamailio.
Kamailio TLS
Сразу уточню, что здесь я не буду зацикливаться на разжевывании действий, которые мы неоднократно уже делали (такие как загрузка модуля, параметры модуля, все это описывается в первой статье цикла)
Для возможности установления защищенных TLS сессий необходимо использовать модуль «tls.so», для этого предварительно его установим через менеджер пакетов,
Deb пакет (если используется Debian, Ubuntu или совместимые с deb пакетами ОС):
apt install kamailio-tls-modules -y
rpm пакет (если используется CentOS, RHEL, Fedora или любые другие, совместимые с rpm пакетами ОС):
yum install kamailio-tls -y
Также загрузим сам модуль в конфигурационном файле kamailio.cfg. К остальным модулям добавляем строку:
loadmodule "tls.so"
Изменим порты, которые слушаются Kamailio. Помимо UDP порта 5060, будем слушать также 7777 порт для работы с TLS сессиями:
listen=udp:**.**.**.**:5060
listen=udp:10.0.0.4:5060
listen=tls:**.**.**.**:7777
listen=tls:10.0.0.4:7777
alias=tls:sip.ipcall24.com:7777
alias=udp:sip.ipcall24.com:5060
И рядом с настройкой mhomed, включим параметр отвечающий за TLS, добавив строку enable_tls=yes
mhomed=1
enable_tls=yes
И к остальным параметрам модулей добавим строку:
modparam("tls", "config", "/etc/kamailio/tls.cfg")
Отмечу, что параметры можно указать как в отдельном файле, например в tls.cfg (или любом другом), также можно отдельный файл не создавать и прописать все параметры в самом файле kamailio.cfg
Т.к мы вынесем настройки TLS модуля в отдельный файл tls.cfg, создадим этот файл (по умолчанию, при установке модуля, файл tls.cfg уже будет создан, можете переименовать его в tls.cfg.example и создать новый файл, или удалить существующий и создать чистый файл). Содержимое этого файла будет следующим:
[server:default]
method = TLSv1.2+
private_key = /etc/ssl/ipcall24/privkey.pem
certificate = /etc/ssl/ipcall24/fullchain.pem
Это тот минимум, с которым все заработает.
method может принимать следующие значения:
- TLSv1.2+ — наш сервер будет работать с протоколом версии TLSv1.2 и новее (TLSv1.3), в большинстве банковских организаций, будет использоваться именно эта настройка
- TLSv1.2 — разрешает клиентами устанавливат TLS сессии только с версией протокола TLSv1.2
- TLSv1.1+ — установить TLS сессию можно начиная с версии протокола TLSv1.1 и новее (TLSv1.2, TLSv1.3)
- TLSv1.1 — сервер будет работать только с версией TLSv1.1
- TLSv1+ — сервер будет разрешать устанавливать сессии с протоколами TLSv1 и новее (TLSv1.1, TLSv1.2, TLSv1.3)
- TLSv1 — сервер поддерживает только TLSv1
- SSLv3 — сервер принимает соединения только с версией протокола SSLv3
- SSLv2 — сервер принимает соединения только с версией протокола SSLv2
- SSLv23 — сервер разрешает устанавливать TLS сессии со всеми версиями протокола (SSLv2, SSLv3, TLSv1, TLSv1.1…)
В некоторых организациях (в основном финансовых) единственный вариант, который пропустит служба информационной безопасности — TLSv1.2+ или TLSv1.2. В любом случае, я бы не советовал использовать версии ниже чем TLSv1.2 т.к версии TLSv1.1 и более ранние (в т.ч SSL) являются устаревшими, deprecated.
private_key — это путь к самому ключу, который содержит открытую и закрытую часть. Ключ генерируется при создании CSR (запроса на выпуск сертификата)
certificate — это путь к сертификату, у меня в данном случае используется полная цепочка сертификатов
Ваша организация может приобрести как платный сертификат безопасности, выпущенный одним из удостоверяющих центров, так и бесплатный с помощью Lets Encrypt. Также есть вариант использования самоподписанного сертификата (если клиенты добавят его в доверенные).
Выпуск сертификатов выходит за рамки этой статьи, но упомяну, что для выпуска сертификата в Lets Encrypt я использую certbot, где есть детальная инструкция как выпустить сертификат.
Перезагрузим демон Kamailio, после чего проверяем, что Kamailio начал слушать указанный порт (в нашем случае 7777), выполнив команду в терминале Linux:
kamailio -c
Получим следующий результат
Listening on
udp: **.**.**.**:5060
udp: 10.0.0.4:5060
tcp: 10.0.0.4:8080
tls: **.**.**.**:7777
tls: 10.0.0.4:7777
Aliases:
tls: *****.com:7777
udp: *****.com:5060
tls: sip.ipcall24.com:7777
На этом этапе настройка TLS в Kamailio окончена.
Kamailio WebSocket
Как было описано ранее, WebRTC для передачи сигнализации в качестве транспорта использует WebSocket’ы. В Kamailio доступен модуль с одноименным названием, который позволяет работать с websocket без каких-либо шлюзов/балансировщиков/веб-серверов.
Для этого необходимо установить пакет, через пакетный менеджер вашей ОС
Deb совместимые ОС:
apt install kamailio-websocket-modules -y
RPM совместимые ОС:
yum install kamailio-websocket -y
К другим модулям добавим загрузку модуля websocket.so и модуля xhttp.so (в качестве веб сервера)
loadmodule "xhttp.so"
loadmodule "websocket.so"
В глобальных параметрах укажем, что заголовок Content-Length является не обязательным, чтобы модуль xHTTP не выдавал ошибку, если такой заголовок не задан
mhomed=1
tcp_accept_no_cl=yes
enable_tls=yes
Сам модуль xHTTP в конфигурационном файле работает через event_route, поэтому в самый конец файла kamailio.cfg добавим:
event_route[xhttp:request] {
set_reply_close();
set_reply_no_connect();
if ($Rp != 7777) {
xlog("L_WARN", "HTTP request received on $Rp\n");
xhttp_reply("403", "Forbidden", "", "");
exit;
}
xlog("L_DBG", "HTTP Request Received\n");
if ($hdr(Upgrade)=~"websocket"
&& $hdr(Connection)=~"Upgrade"
&& $rm=~"GET") {
if (ws_handle_handshake())
{
exit;
}
}
xhttp_reply("404", "Not found", "", "");
}
event_route — это маршрут который будет вызван только при определенном событии. В квадратных скобках указывается модуль (xhttp) и через двоеточие указывается запрос (request), в данном случае — получение http (+websocket) запроса
Функция set_reply_close указывает на то, что сообщение будет передано только если уже существует TCP соединение с удаленной стороной. После чего соединение закрывается.
Функция set_reply_no_connect — такая же как и set_reply_close, но используется только для локальных (внутренних сообщений)
Проверкой псевдопеременной $Rp мы проверяем, пришел ли запрос на порт TLS и в случае, если запрос пришел на другой порт — отвечаем ошибкой.
После чего выполняем проверку на websocket, если полученные запросы работают через websocket’ы — вызываем функцию ws_handle_handshake, которая выполняет рукопожатие
В случае, если ни одно условие не выполненно, но запрос пришел на корректный TLS порт — отвечаем 404 ошибкой, т.е мы не нашли маршрутов обработки этого запроса.
На этом настройка WebSocket модуля завершена.
Адаптация RTPengine для работы с WebRTC клиентами, ошибка 488 not acceptable here
Первым делом подключим модуль для работы с регулярными выражениями, regex.so:
loadmodule "regex.so"
Теперь вновь обратимся к целевой схеме из нулевой статьи (Архитектура VoIP с использованием Kamailio):
Как видно из схемы, у нас есть два типа внутренних клиентов, это обычные аппаратные или программные телефоны, реализующие самостоятельно SIP протокол и WebRTC клиенты, реализующие SIP протокол в браузере через другие протоколы.
Реализация SIP в классических вариантах и WebRTC различается, в первую очередь настройками SDP (что такое SDP мы очень подробно разбирали в статье RTPEngine в Kamailio).
Для WebRTC мы должны принудительно работать с ICE протколом, выключим работу SDES (т.к протокол deprecated и соответственно большинство браузеров не поддерживает работу этого протокола), и явно задать протоколы RTP. Без всех этих дополнительных настроек, WebRTC работать не будет и в большинстве случаев мы получим ошибку 488 not acceptable here.
Важно разобрать каким образом технически мы будет обрабатывать следующие возможные варианты взаимодействия:
- SIP<—Kamailio—>SIP
- SIP<—Kamailio—>WebRTC
- WebRTC<—Kamailio—>SIP
- WebRTC<—Kamailio—>WebRTC
Чтобы решить эту проблему, нам нет необходимости разбирать все эти варианты, т.к внутри нашей сети, взаимодействие между Kamailio и FreeSWITCH (медиа сервером) происходит через протокол UDP. Поэтому нам не важно с какого клиентского устройства пришел вызов, он все равно уйдет к медиа серверам через классический UDP/RTP, нам важно понимать, какому клиенту мы звоним и уже в зависимости от этого изменять SDP.
Очень подробно сам процесс звонка описывался в предыдущей статье (Обработка вызова, SIP диалог, SIP транзакции в Kamailio), здесь опишем только лишь отличия классического звонка из телефона(софтфона) на другой телефон(софтфон).
Рассмотрим какой контакт сохраняется в памяти Kamailio при регистрации из браузера (вывод команды kamcmd ul.dump из командной строки Linux):
Address: sip:[email protected];transport=ws;alias=**.47.**.1**~55370~6
Т.е нам известно через какой транспорт был зарегистрирован этот абонент, в данном случае транспортом указан WS протокол (может быть указан WSS).
Зная, что мы храним эту информацию, мы можем проверить (перед формированием SDP заголовков с помощью rtpengine_manage) к какому абоненту будет отправлен вызов, к классическому SIP клиенту или в браузер. Для этого мы изменим маршрут RELAY на следующий вид (приведен только фрагмент, который мы изменяем):
if (has_body("application/sdp") && isflagset(tswitch)) {
rtpengine_manage("ICE=remove replace-origin UDP/RTP external internal");
} else {
if (pcre_match("$dP", "(?i)wss|ws")) {
rtpengine_manage("replace-origin ICE=force SDES=off UDP/TLS/RTP/SAVPF codec-mask=all codec-transcode=PCMA,PCMU internal external");
} else {
rtpengine_manage("ICE=remove replace-origin internal external");
}
}
Из этого конфига видно, что все вызовы, которые идут с флагом tswitch, т.е от балансировщика в сторону медиа серверов, обрабатываются без изменений, т.е отправляются через UDP + RTP
Если INVITE идет от медиа серверов в сторону балансировщика, то мы делаем несколько дополнительных проверок, а именно:
С помощью регулярного выражения (из ранее подключенного модуля regex) проверяем псевдопеременную $dP — которая возвращает тот самый transport из контакта, зарегистрированного на Kamailio, на соответствие протоколам WS/WSS (с помощью (?i) это проверка является регистронезависимой, т.е в кач-ве транспорта могут быть значения ws/wss/WS/WSS) и если обнаруживаем, что клиент является WebRTC клиентом, модифицируем SDP как было сказано чуть ранее, также отмечу, что транскодинг можно делать не только на Kamailio, но и на медиа серверах, но в данном случае это не принципиально.
Если же в качестве транспорта не используется websocket, то как и ранее мы подменяем внутренний IP на внешний и удаляем ICE заголовки (если они имеются)
JSSIP библиотека, интеграция JSSIP с Kamailio
В качестве примера WebRTC клиента я буду использовать демо библиотеки JSSIP, для этого необходимо перейти по ссылке demo-jssip, после чего отобразиться следующая страница:
После чего необходимо ввести имя и нажать на шестеренку:
И нажимаем OK, после чего на главном экране нажимаем стрелку под полем ввода имени и увидим следующую страницу:
В консоли разработчика, в Firefox это ПКМ -> Исследовать -> Консоль, мы увидим сами SIP сообщения, которые мы отправляем из браузера и ответы на них от нашего Kamailio.
Проверим, что исходящий из классического SIP клиента доходит до JSSIP клиента:
И проверим, что звонок из WebRTC доходит до софтфона:
Видно, что все также хорошо и звонок доходит
Сразу отмечу, что сигнальный трафик идет шифрованным с помощью ранее настроенных сертификатов (в модуле tls.so) и трафик приходит не в чистом SIP виде, а через websocket’ы, поэтому через sngrep этот трафик будет не виден, для дебага нужно/можно использовать консоль разработчика из браузера (где отображается взаимодействие между WebRTC клиентом и Kamailio) и sngrep для отслеживания трафика с балансировщика к медиа серверам, сопоставляя две трассировки (из браузера и классическую через sngrep), можно выполнять дебаг каких-либо проблем. Также можно использовать специальное ПО, которое умеет захватывать и расшифровывать трафик, который идет через вебсокеты, например проект Homer.
Заключение
В этой статье мы разобрали теорию работы WebRTC, какие протоколы он под собой несет, разобрали сами протоколы. Также мы реализовали на практике взаимодействие с WebRTC клиентами нашего SIP сервера, настроили корректное прохождение SDP в зависимости от используемого клиента. А также наглядно посмотрели, как это работает с помощью библиотеки JSSIP (самостоятельно можете попробовать использовать клиенты SIPML5, ctxSip).
Итоговый конфигурационный файл можно посмотреть по ссылке
Если есть какие-либо вопросы, можете оставлять их в комментариях, обязательно отвечу.
Если есть желание сделать пожертвование, то это можно сделать кликнув по ссылке или же нажав на кнопку. Спасибо за поддержку !)
6 ответов к “Kamailio часть 10. Реализация WebRTC в Kamailio с рассмотрением примеров”
Всё что тут написано это бесценно
Материал настолько хорош что и не грех отблагодарить , автор оставь контакты для донатов
Благодарю за отзыв.
Ссылку на донат добавил в статью 🙂
Доброго дня, можете опубликовать итоговый файл конфига?
Доброго дня!
Выложил на гитхаб
Благодарю за отличные статьи, очень помогли, ловите на яндекс небольшую благодарность)
Единственный момент, когда посылаю запрос через jssip то у меня идет запрос авторизации от астериска, после этого jssip завершает работу, а вот через sipML5 все отрабатывает отлично (высылает повторно запрос на регистрацию на астериске)
Ну и пришлось чуток доработать место с Relay, т.к. не проходил голос, но это уже специфика астериска я думаю