На протяжении нескольких лет в нашу интернет-телефонию по кусочкам добавлялась разная функциональность. В итоге мы оказались в точке, где единственным плюсом сложившейся VoIP-архитектуры была её надёжность. При этом внесение любых изменений вызывало головную боль, а успешный результат их внедрения в прод никогда не был гарантирован. Мы решили это исправить и за год написали новое решение. 

Сервис телефонии Quadcode

Мы делаем интернет-телефонию для наших B2B-клиентов. Компоненты VoIP, которые мы используем:

  • FreeSWITCH в качестве телефонных станций. 

  • Провайдеры VoIP-телефонии от наших клиентов. 

  • Более 200 телефонных номеров со всего мира.

  • SIP-телефоны и GSM-шлюзы. 

  • WebRTC-клиенты в CRM и наш WebRTC Webphone.

  • Интеграция с CRM. 

  • Мониторинг через Telegraf, Influx и Prometheus.

  • Homer для хранения SIP-трейсов.

  • Ceph и AWS S3 для хранения аудиозаписей разговоров.

  • Защита от DDoS и защита UDP-протокола.

VoIP-телефония только с первого взгляда кажется простой и понятной, но по факту оказывается, что только VoIP-инженеры понимают, как работает SIP и WebRTC со всеми нюансами этих протоколов. Поэтому начнём с небольшого объяснения, чтобы можно было дальше рассказать про архитектурные изменения. Если вы уже знакомы с этими протоколом и стандартом, можно сразу перейти к сути по якорной ссылке. 

Что такое SIP-протокол

Session Initiation Protocol — это клиент-серверный протокол прикладного уровня. Он обеспечивает организацию, модификацию и завершение сеансов связи: мультимедийных конференций, телефонных соединений и распределения мультимедийной информации. Взаимодействие клиентов в рамках SIP чаще всего осуществляется в виде диалога — это последовательность SIP-сообщений. 

SIP можно разделить на две взаимосвязанные части: 

  1. Сигнализация.

  2. Передача медиа. 

В сигнализации передаются диалоговые и другие SIP-сообщения. Внутри SIP-протокола встроен SDP-протокол, который отвечает за установление медиасоединения. В SDP передаётся информация об аудио- и видеопотоке. 

Само SIP-сообщение выглядит примерно так:

INVITE sip:1234567890@192.168.1.1:5080;transport=tcp;gw=123 SIP/2.0
Record-Route: <sip:192.168.1.2;lr=on;ftag=as1f5e1177;vsf=AAAAAAAAAAAAAAAAAAAAAAAACAwAAAcBAAAEAAYCODo1MDgw;vst=AAAAAA8HAAYAdAEPDBtxAAAbGAIfABkxMzQuMjUy>
Via: SIP/2.0/UDP 192.168.1.2;branch=z9hG4bK413d.eb4e995427afcd56704ae5c59c0e9fd9.0
Max-Forwards: 69
From: <sip:0987654321@192.168.1.2>;tag=as1f5e1177
To: 1234567890 <sip:1234567890@192.168.1.1>
Contact: <sip:0987654321@192.168.2.3:5080>
Call-ID: 6366d9bb789ea6f1235f83297564388d@192.168.2.3:5080
CSeq: 102 INVITE
Content-Type: application/sdp
Content-Length: 348
User-Agent: Provider SBC
 
v=0
o=provider 1719357914 1719357914 IN IP4 192.168.2.3
s=Provider MGW
c=IN IP4 192.168.2.3
t=0 0
m=audio 12554 RTP/AVP 8 0 9 18 3 101
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:9 G722/8000
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=rtpmap:3 GSM/8000
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-16
a=ptime:20
a=sendrecv

Подобно HTTP, SIP представляет собой текстовый протокол. Коды ответов у них похожи — в SIP тоже есть 200-е, 400-е, 500-е, но сам по себе протокол немного другой. В HTTP мы отправили запрос, получили ответ и успокоились. А в SIP всё сложно: одно сообщение, ответ на него, ещё одно сообщение, ещё один ответ на него. Это как раз диалоги, о которых мы говорили чуть выше. Но диалоги тоже есть не всегда: в каких-то моментах они используются, в каких-то — нет. Также есть разница в обработке кодов ответов, например в HTTP 500 — это явная ошибка, а в SIP — как повезёт. 

Ещё одна сложность — это обработка SDP-информации. SDP передаётся в body SIP, где перечисляются кодеки, порты, IP-адреса и т. д. Вся эта информация согласуется между сервером и клиентом, всё происходит довольно-таки сложно и с большим количеством нюансов. Например, если клиент находится за NAT, то ему нужны дополнительные средства для того, чтобы определить, какой внешний IP-адрес нужно указать в SDP, ведь клиент знает только свой IP-адрес внутри сети. Для этого обычно используют STUN-протокол.

Не случайно в wiki разработчиков FreeSWITCH написано: „SIP is a crazy protocol and it will make you crazy too if you aren’t careful“. 

Тем не менее, на SIP-протоколе основывается почти вся современная VoIP-телефония. В целом VoIP SIP можно представить как набор различных протоколов: STUN, TURN, SIP, TLS, SDP, (S)RTP и т. д.

Что такое WebRTC

Web Real Time Communications — это стандарт, который описывает передачу сигнальной информации, потоковых аудиоданных, видеоданных и другого контента. Он применим не только к телефонии: WebRTC может передавать как SIP, так и XMPP и другие протоколы. Он нужен для обмена данными в режиме реального времени между браузерами напрямую, либо между сервером и браузером. 

Семиуровневая модель OSI применительно к WebRTC

Удобство WebRTC в том, что для его работы не нужно устанавливать дополнительные плагины или расширения — это встроенный функционал. А со стороны сервера есть различные программные решения для взаимодействия с использованием WebRTC:

  • FreeSWITCH.

  • Asterisk.

  • Flashphoner.

  • Kurento. 

  • И т. д.

WebRTC обеспечивает абсолютную кроссплатформенность для приложений и полную независимость как от аппаратных средств, так и от средств операционной системы. Для разработчиков технология доступна в виде разных библиотек JavaScript API. Можно взять эти библиотеки, импортировать их к себе и писать полноценный фронт на их основе. Он будет без проблем работать и поддерживаться как Google Chrome, так Safari, Firefox и другими браузерами. Мы, например, используем библиотеку sip.js

Но есть у использования WebRTC один большой минус — необходимы большие ресурсы на транскодинг медиа, а также передачу медиапотоков. Но этот минус компенсируется тем, что нет необходимости вести разработку программного обеспечения и каких-то своих протоколов для ядра телефонии, что потребовало бы большого количества человеко-часов.

В нашем случае WebRTC используется именно для телефонных вызовов. Для них на верхнем уровне сигнализации остаётся только SIP, а для медиа — SRTP плюс контрольный протокол медиа:

Старая архитектура VoIP 

В 2020 году архитектура нашей телефонии выглядела следующим образом:

Слева на схеме — подключённые через интернет VoIP-провайдеры. 

Клиенты подключаются к телефонии по внутренней сети. В качестве клиентов у нас есть: 

  • CRM-юзеры — это подключения из браузеров службы поддержки и клиентских менеджеров. 

  • Юзеры, которые используют Zoiper в качестве SIP-телефона со своих смартфонов или ноутбуков. 

  • Внутренняя офисная телефония с Wi-Fi-трубками и GSM-шлюзами.

Телефония работает в двух окружениях: Production и Integration. Integration или Int — это мини-копия прода, в этой среде мы проводим интеграционное тестирование и верификацию перед публикацией релиза.  

Плюсы старой архитектуры. Плюс в старой схеме был только один — надёжность. Во-первых, это проверенные опытом провайдеры, которые всегда готовы помочь в решении вопросов взаимодействия. Во-вторых — всё достойно работало, но с оговоркой. При необходимости что-либо менять мы неизбежно сталкивались с трудностями. 

Минусы старой архитектуры. Минусов было гораздо больше. Во-первых, в старой схеме не было CI/CD, и внедрять CI/CD под текущую архитектуру очень не хотелось, поскольку архитектура была далека от совершенства и имела много проблем. Как раз поэтому менять что-либо было больно: это какие-то ручные манипуляции и занесение в Git. Во-вторых, архитектура не масштабировалась: если нужно было что-то новое, мы были вынуждены изобретать всё заново. 

Бонусом у всего этого дела была плохая документация, которая осталась нам по наследству. К сожалению, когда компания ещё была стартапом, до документации руки не доходили, а потом стало слишком поздно. Насколько смогли, мы привели её в соответствие с тем, как всё работает, но всех подводных камней не знал никто. 

В целом это был классический вариант внутренней телефонии, как в любой компании, которая использует open-source решения VoIP-телефонии.

Вишенка: Int-решение не соответствовало Prod-решению. Это было огромным минусом потому, что все тесты телефонии на Int не гарантировали, что всё будет работать в продакшене. 

Разработку и функциональное тестирование мы проводим в третьей среде — Sandbox. И в старой архитектуре эта среда работала костыльно: Sandbox был интегрирован с Int, это позволяло что-то делать, но снова не давало гарантий, что на продакшене всё будет работать так же. 

Если рассмотреть каждый FreeSWITCH внутри, то архитектура старой телефонии и её интеграция с CRM выглядели примерно так: 

Красная зона — ответственность разработчиков бэк-офиса, а серым отмечена зона ответственности отдела VoIP-телефонии. В какой-то момент эти зоны ответственности перемешались

Это был тот ещё ужас, в котором было очень сложно разобраться. Всё строилось по мере необходимости: функциональность добавлялась кусочек за кусочком, что и привело к гремучей смеси зон ответственности. Стало невозможно понимать, какие изменения в какой момент на что могут повлиять. 

Интеграция происходила путём использования диал-планов FreeSWITCH и внесения изменений в SQL-кэш FreeSWITCH, а также использовался fs_curl с некоторыми кастомными изменениями, который уже 6 лет не развивается. То есть со стороны разработчиков CRM было необходимо знание того, как конфигурировать FreeSWITCH, как он работает, и как работает вся его обвязка. 

И больше того: интеграция происходила через Go-applications, которые писались бэк-офисом, но при этом поддерживались и были запущены на сервере телефонии. А поверх всего этого работали в большом количестве Lua-скрипты в зоне ответственности телефонии, запуск которых даже отследить в логике интеграции стало невозможно. Если возникала необходимость внести любое изменение в существующей схеме, то это давалось очень сложно, и каждое новое изменение могло приносить ещё большее количество новых проблем. 

В какой-то момент у нас встал вопрос о расширении возможностей B2B-телефонии для наших клиентов. Мы поняли, что продолжать жить со старой архитектурой нельзя, и приступили к её улучшению.

Новая архитектура VoIP 

Создание нового решения заняло год. В итоге новая архитектура выглядит так:

Мы реализовали разделение FreeSWITCH по задачам: есть SBC, которые отвечают только за телефонию и подключение операторов связи, и есть PBX, которые отвечают за подключение клиентов. На схеме есть SBC0X и PBX0X, это значит, что мы можем увеличивать количество и SBC и PBX по мере необходимости — SBC01, SBC02, PBX01, PBX02 и т. д.

Если будет необходимость на SBC вместо FreeSWITCH использовать Kamailio или любую другую систему для маршрутизации SIP — без проблем.

Плюсы новой архитектуры. Надёжность VoIP осталась на том же уровне: провайдеры и FreeSWITCH не изменились. Зато теперь у нас есть CI/CD, и всё контролируется через GitLab. В новой архитектуре также добавилась высокая безопасность, потому что мы внедрили и сделали интеграцию с сервисом DDoS protection, сделали защиту UDP-протокола и внедрили защиту WebSocket-подключения с использованием токенов.

Как я уже говорил, появилась масштабируемость. Теперь мы можем увеличивать количество серверов в зависимости от задач и от целей. Каждый шаг, каждое изменение в телефонии документируется, мы стараемся вести документацию по-максимуму.  

Изменилась и архитектура для Int-окружения. Теперь она практически идентична продовому решению: 

Единственная разница — в подключённых провайдерах, вместо них подключены заглушки. Теперь у нас нет необходимости покупать на Int отдельные номера — мы можем сэмулировать продовые номера на провайдере и работать с ними так же, как на Prod-окружении. Для Sandbox-окружения мы Int-схему завернули в Docker. 

Теперь одна и та же конфигурация FreeSWITCH работает и на Prod, и на Int, и в Sandbox. Для каждого разработчика в Sandbox может быть поднято своё окружение, один в один соответствующее Int-окружению. Это обеспечивает и тестирование, и разработку, и уверенность в том, что всё будет работать везде одинаково.

Новая архитектура FreeSWITCH и её интеграция с CRM выглядит так: 

Зоны ответственности теперь понятно разделены

Точек взаимодействия стало намного меньше. Сейчас главная точка взаимодействия FreeSWITCH и CRM — это HTTP-запросы от FreeSWITCH к CRM API (через PHP-конвертер JSON в XML), и обмен происходит путём получения JSON. То есть на запрос от FreeSWITCH, PHP-конвертер запрашивает заранее стандартизированный JSON у API и генерирует на основании JSON-ответа XML для FreeSWITCH с конфигурацией или диалпланом. В новой архитектуре ушла необходимость знать принципы конфигурирования и работы FreeSWITCH для того, чтобы с ним взаимодействовать. 

Вторая точка соприкосновения — это event socket у FreeSWITCH, через который API может получать данные от FreeSWITCH, создать колбэк через originate или попросить FreeSWITCH обновить свою конфигурацию. 

Программные средства для работы с телефонией

Для разработки и работы с телефонией мы сделали два программных продукта. 

Первый — это Web_fs_cli. У FreeSWITCH есть fs_cli, это клиент командной строки FreeSWITCH. Клиент работает через event socket и позволяет делать запросы во FreeSWITCH и получать ответы. Через fs_cli можно посмотреть количество регистраций или список пользователей FreeSWITCH, выполнить другие команды. 

Изначально этот интерфейс работает только через консоль, то есть мы подключаемся в fs_cli к FreeSWITCH event socket, авторизуемся и работаем. Но это не очень удобно, потому что fs_cli нужен и тестировщикам, и разработчикам, и иногда нужно давать SSH-доступ к серверу, а работать с fs_cli удалённо временами просто невозможно. Ну и тестировщики очень часто не любят делать запросы в консоли. Поэтому мы разработали интерфейс fs_cli в вебе. Веб-интерфейс позволяет выполнять запросы на FreeSWITCH event socket через ввод команды, либо используя предустановленные команды в Action. Ответы выводятся напрямую в браузер.

Для Int-окружения в интерфейсе Web fs_cli доступен мод Test originate. Это генерация вызовов с эмуляторов провайдеров на интовую SBC. Там мы указываем, кто куда звонит и через какое время вызов завершается, и нажимаем «сгенерировать вызов». Через event socket происходит originate-запрос на эмуляторы провайдеров, и в результате генерируется входящий вызов на SBC так же, как он пришёл бы на Prod. К вызову подключается автоинформатор, который говорит откуда и с какими номерами был сделан вызов. 

Этот интерфейс был запущен в конце 2021 года, и отлично выполняет свои задачи.

Второй программный продукт — это Webphone. Это WebRTC-телефон, который работает в браузере. Он использует библиотеку sip.js, которую у нас также используют чаты и CRM. Телефон поддерживает работу с websocket-токенами и очень часто помогает в отладке. 

Почему понадобилось наше решение

Зачем разбираться с легаси в своём решении, если B2B-клиенты могут использовать любое из уже имеющихся на рынке? Приведу пример. 

Один из наших B2B-клиентов — банковский сервис. У него была своя инфраструктура и своё решение для телефонии — Twilio. Это поставщик облачного ПО для бизнес-коммуникаций. Twilio не является самостоятельной звонилкой, и его можно было интегрировать в узкое количество других платформ. У клиента была настроена интеграция с Zendesk.

Обслуживание Twilio дешёвое, и с самой системой проблем у клиента не было. Однако Zendesk был очень дорогим, поэтому было принято решение отказаться от обоих продуктов. С этого момента у бизнеса начались поиски идеальной телефонии. 

bOnline. Первым вариантом стал bOnline. Он удовлетворял потребности и стоил разумных денег. Но спустя пару месяцев эксплуатации начались регулярные технические проблемы со связью, которые система телефонии не могла решить. Даже если сложности получалось устранить, это занимало много времени. Пришлось искать другую платформу.  

Acefone. Следующим решением стал Acephone, но с ним количество технических проблем меньше не становилось. Однажды банкинг получил от платформы не очень приятный комментарий, что возможность слышать клиентов блокируется на их стороне, и решать эту задачу должны администраторы самой компании. 

Quadcode уже работал с самим банковским сервисом, поэтому клиент обратился к нам за VoIP-решением. Команда телефонии собрала бизнес- и технические требования к сервису и смогла предложить удовлетворяющее решение. Наш Webphone тоже ушёл в продакшн для этого клиента как телефон в браузере. Он выглядит просто, но подходит под текущие потребности. 

Полный срок развёртывания от получения задачи от заказчика до продового решения составил одну неделю. При старой архитектуре мы не смогли бы ничего сделать. Поэтому для нас решение о поддержке и обновлении своей VoIP-телефонии было верным. 

Вот обратная связь, которую мы получили от клиента:

«О возможных технических проблемах сообщается незамедлительно, и решается они очень быстро. Также мы можем спокойно расширять функциональность телефонии под новые требования. Например, сравнительно недавно добавили возможность трекинга, кто на данный момент находится онлайн в системе. Переход на телефонию Quadcode стал лучшим решением.» 

Результаты доработки архитектуры

Надёжность новой архитектуры телефонии осталась прежней. При этом мы увеличили безопасность, добавили CI/CD, масштабируемость и регулярно ведём документацию, Теперь VoIP соответствует стандартам архитектуры Quadcode и поддерживает все три окружения: Sandbox, Int и Prod. 

Интеграция с внешними сервисами тоже стандартизирована — это JSON, где чётко прописано, что, как и когда использовать. И, наконец, ушла необходимость знать FreeSWITCH для того, чтобы производить интеграцию с телефонией. Разработка и внедрение на прод новых VoIP-продуктов в новой архитектуре занимает одну неделю, а наши клиенты получили возможность расширять функциональность под новые бизнес-требования.   

Старая архитектура

Новая архитектура

Надёжность

Подключение к любым VoIP-провайдерам

Безопасность

Низкая

Высокая

CI/CD

Масштабируемость

Документация

Отсутствует

Подробная

Работа в Sandbox

Разработка и внедрение в прод новых VoIP-продуктов

1 неделя

Стандартизированная интеграция с внешними сервисами

Особенности интеграции

Freeswitch XML dialplan, Freeswitch database, fs_cli

JSON, fs_cli

Готовность для B2B