sip2go: Альтернатива SIP клиентам для Android

Однажды заказчик, которому я разворачивал офисную телефонию, спросил меня: «А можно нашим сотрудникам поставить на телефоны SIP клиенты, чтобы их разговоры с клиентами проходили через офис, попадали в CRM и записывались?»
Мысль заказчика понятна: Почему бы просто не поставить софтофон на мобильник и не настроить его на свой сервер?
Но есть несколько причин, почему это работает откровенно плохо:
Софтофону придётся разрешить постоянную фоновую работу, иначе нет входящих звонков. Но это очень быстро «сушит» батарею. Конечно же, я знаю про схему linphone + Flexisip, но если честно, я тупо не смог нормально собрать Flexisip.;)
Проблемы с удержанием сессии. SIP протокол не любит клиентов со внезапно меняющимися IP адресами. А мобильный телефон делает это довольно часто.
SIP за NAT это отдельный вид мазохизма. Особенно весело, когда звук динамически пропадает на любой из сторон при каждом отдельном вызове.;)
Светить SIP сервер в мир без SBC — не хорошо. Жирный пароль не спасёт от постоянных сканов и брутфорса ботами, а fail2ban вместо защитника порой выполняет функцию вредителя.
В последний раз при необходимости экстренно обеспечить связь с офисом я второпях пустил SIP через OpenVPN. Но туннель регулярно отваливался, а в некоторых случаях вообще отказывался работать через мобильный интернет.
В какой‑то момент я подумал: Почему, чёрт возьми, мобильный софтофон не работает так же просто, как это делают мессенджеры?
Так родился открытый проект Sip2Go.

Идея
Суть задумки была в том, чтобы избавиться сразу от всех проблем SIPа на смартфоне, заставив голосовую связь работать точно так же, как она работает у мессенджеров.
В Sip2Go телефон вообще не является SIP‑клиентом.
Схема выглядит иначе:
┌──────────────┐
│ Android │
│ SIP2GO App │
└──────┬───────┘
│
│ WSS / TLS
│
▼
┌──────────────────┐
│ SIP2GO Gateway │
│ │
│ WebSocket │
│ ↕ │
│ SIP / RTP │
└────────┬─────────┘
│
│ SIP
▼
┌──────────────────┐
│ PBX / Asterisk │
└──────────────────┘
Получилось довольно изящно:
SIP остаётся там, где он гарантированно работает, а мобильное устройство разговаривает с сервером обычным защищённым WebSocket‑соединением. Шлюз может подключаться к АТС как обычный SIP клиент, не требуя никаких специфических настроек на её стороне, что кардинально упрощает интеграцию этого софта в уже рабочую систему.

Почему именно WebSocket
Мне нужен был транспорт, который:
нормально работает сквозь NAT;
обходит кривые хелперы, типа SIP ALG, которые частенько вредят.
выглядит как обычный https;
позволяет стабильно удерживать двустороннее соединение;
одинаково хорошо подходит для управляющих сообщений и передачи аудио.
Работает сквозь любой веб сервер в режиме реверс прокси (хоть через cloudflare).
WebSocket здесь оказался практически безальтернативным выбором.
Телефон устанавливает WSS‑соединение с Sip2Go Gateway, после чего через него идут управляющие сообщения и аудио. При этом серверная часть продолжает работать с обычной SIP‑инфраструктурой. А это залог того, что для шлюза нет принципиальной разницы, к какой именно АТС его подключат: будь то условный FreePBX, проприетарная коробочка с лампочками или вовсе SIP провайдер с неизвестно чем под капотом.
Что происходит при звонке
Допустим, пользователь хочет позвонить с мобильного приложения.
Android отправляет запрос через WebSocket на gateway.
Gateway уже сам взаимодействует с SIP‑сервером:
Android
│
│ "позвонить на 123"
▼
SIP2GO Gateway
│
│ SIP INVITE
▼
PBX
│
▼
SIP абонент
В обратную сторону всё работает почти так же, но с небольшим нюансом.
Когда на соответствующий номер приходит входящий звонок, PBX передаёт его gateway (как своему абоненту), а дальше немного магии: шлюз отправляет серверу прогресс звонка, а сам в это время отправляет мобильному клиенту FCM (push пробуждение) и ждёт, когда подключится к шлюзу.
При этом мобильный клиент не обязан постоянно висеть на сессии и ждать звонка. Шлюз будет «искать» его и держать статус RINGING до тех пор, пока клиент не примет звонок или не наступит таймаут. Тут практически как у мобильных операторов.:)
Что там с RTP?
На стороне PBX мы остаёмся в привычном мире SIP, работая на кодеках PCMU/PCMA (ulaw/alaw).
На стороне мобильного устройства аудиопоток кодируется в Opus и бегает через WebSocket.
Gateway выступает посредником и транскодером между этими двумя мирами:
SIP / RTP
│
PCMU/PCMA
|
▼
┌─────────────────┐
│ Gateway │
│ │
│ audio bridge │
│ │
└────────┬────────┘
│
Opus
│
▼
Android
В результате мобильному приложению не нужно реализовывать весь зоопарк SIP/RTP/NAT traversal, который обычно сопровождает VoIP‑клиент. Ему достаточно поддерживать собственный небольшой протокол общения с gateway.
Удерживаем связь до последнего
Разрыв соединения может случиться по самым разным причинам, начиная со смены IP адреса, при переключении между сетями и заканчивая полным закрытием приложения.
Например, если соединение было потеряно в момент, когда звонок уже поступил, приложение не должно считать, что ничего не происходило.
Даже в сценарии рестарта приложение, как только оно снова подключится к шлюзу, тот сообщит о наличии активной сессии и попытается её возобновить, не роняя вызов.
Для этого gateway и клиент умеют синхронизировать состояние активного вызова после повторного подключения. Время, отведённое на попытки восстановления соединения настраиваются на стороне шлюза.
Если же восстановить связь вовремя не удалось — шлюз отправляет АТС грустный Bye.;)
А что происходит с SIP?
PBX продолжает заниматься своей работой. Для неё Sip2Go Gateway выглядит как ещё один SIP endpoint. Можно использовать существующую инфраструктуру телефонии, а мобильный доступ добавляется отдельным слоем. Это позволяет использовать Sip2Go вместе с существующей телефонией, не превращая PBX в экспериментальную лабораторию.
Правда, тут имеется маленький нюанс: я не стал реализовывать множественную регистрацию. То есть, если работать через регистрацию — получится прокинуть только один endpoint. А вот если нужно запихать сразу много, то нужно подключить шлюз в режиме транка (авторизация по IP) и маршрутизировать в его сторону вызовы на номера всех его подопечных. Наиболее удобный вариант в данном случае — выделить мобильным клиентам свой внутренний пул (например 7XXX)

Лёгкое подключени
Понимая, кто будет пользоваться этим приложением я решил, что нужно максимально упростить его настройку.
В голову мысль: а почему бы не сделать настройку как у eSIM? Отсканил одноразовый QR, либо кликнул на одноразовую ссылку и ты в сети!
Реализация такого подхода моментально избавила меня от необходимости лишнего сопровождения пользователей.
Боевые испытания
Одним из первых пользователей Sip2Go в реальных условиях стал руководитель предприятия, которое я обслуживаю.
До этого мы с ним использовали схему с SIP‑клиентом поверх OpenVPN, и периодическая диагностика глюков этой связки меня однажды прилично достала.
Когда появилась первая рабочая версия Sip2Go, я предложил ему стать добровольным испытателем.
Тестирование получилось особенно значимым, поскольку в данном случае удалось испытать работу софта за пределами страны, где был расположен сервер.
Честно говоря, я даже не знаю, кто из нас был больше впечатлён результатом: Шеф, который внезапно получил простое решение наболевшей проблемы или я, который офигел от положительных отзывов весьма требовательного пользователя.:)
Open source
Sip2Go состоит из двух основных частей:
Исходный код открыт, Шлюз можно развернуть на собственной инфраструктуре и использовать собственные Firebase credentials для push‑уведомлений. Также, придётся собрать и приложение, добавив в него свои ключи от firebase. Инструкция по установке имеется тут и на репозитории проекта.

Заранее поясню, что это за настройка "Push relay URL" и "Push relay API key".
Данная настройка предназначена для коммерческих клиентов, которые пользуются официальной сборкой приложения с Google Play с привязкой к моему аккаунту Firebase. Она используется исключительно для того, чтобы не хранить приватный токен на серверах клиентов и не является обязательной, поскольку при добавлении своего токена в .env шлюз сможет отправлять FCM без всяких дополнительных релеев. Более подробная информация об этом находится тут.
Абсолютно весь функционал программы, без каких либо ограничений доступен бесплатно, при условии самостоятельной сборки приложения с привязкой к своему аккаунту Firebase.
А где клиент под iOS?
Увы, у меня нет аккаунта разработчика для apple. Собственно, как и нет устройств, на которых можно тестировать софт.
Впрочем, я был бы искренне рад, если бы за это взялся кто‑то другой.:)
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.