Аннотация.
Современному сотруднику важно оставаться на связи даже если он находится вне офиса и не может ответить по настольному телефону или с рабочего компьютера. Для этого все чаще используются приложения корпоративной телефонии на смартфонах и ноутбуках. Чтобы такие мобильные клиенты работали стабильно, может потребоваться важный элемент — сервис push-уведомлений.
В этой статье я поделюсь опытом построения архитектуры и запуска тестовой системы с сервисом push-уведомлений для приложения телефонии на базе Android. Обращаю внимание, что материал представляет собой архитектурный обзор и описание реального опыта автора, а не пошаговую инструкцию по настройке.
Push уведомления.
В какой-то момент производители мобильных операционных систем и платформ (в основном смартфонов) начали внедрять механизмы приостановки неактивных приложений. Это было сделано с целью увеличения срока службы аккумуляторных батарей. При переходе в такой спящий режим постоянные TCP-подключения между приложением и сервером принудительно разрываются. Более того, само приостановленное приложение больше не может самостоятельно возобновить работу, используя внутренние таймеры или ожидая входящий сетевой трафик от сервера. Такой сценарий использования приложения, конечно же, не предполагался изначальным стандартом RFC 3261 (SIP), который был выпущен еще в 2002 году. На практике это выражается в том, что заблокированный в текущее время смартфон не сможет принять входящий вызов, т.к. приложение не сможет принять входящий INVITE от сервера. На сегодняшний день в такой ситуации для пробуждения приложения используется сервис push-уведомлений (Push Notification Service, сокращенно PNS). PNS для SIP приложений был описан в RFC 8599 (SIP PUSH) и опубликован в мае 2019 г. Согласно этому RFC PNS определяется как служба, которая отсылает сообщения к пользовательскому приложению от другого приложения для того, чтобы перевести приложение из приостановленного состояния в активное, а сами такие сообщения называются push-уведомлениями. В зависимости от приложения push-уведомления при этом могут содержать полезные данные. PNS бывают открытыми, основанными на стандартном механизме, определённом в RFC 8030, и закрытыми. Например, PNS для iOS и Android устройств являются закрытыми – это, соответственно, сегодня Apple Push Notification service (APNs) и Firebase Cloud Messaging (FCM) service.
Как видно, использование механизма пробуждения приложения через push-уведомления требует внедрения дополнительных backend-сервисов. Их придется разрабатывать, разворачивать, интегрировать в клиентские приложения и постоянно поддерживать. При этом такие сервисы не относятся напрямую к протоколам классической телефонии. Более того, их доступность и особенности реализации зависят от конкретных производителей мобильных платформ и их сервисов.
SIP клиент.
Было бы интересно и полезно получить решение с мобильным клиентом, которое работало бы на основе RFC 8599. При этом с RFC 8030 гораздо сложнее, т.к. два основных производителя распространённых мобильных платформ – это Google и Apple, чьи PNS являются закрытыми.
Поиск мобильного sip клиента с поддержкой RFC 8599 привёл к приложению Linphone, как самому упоминаемому в нужном контексте. Это программный продукт с открытым исходным кодом, распространяемый по условиям лицензии GNU GPLv3, и имеющий набор API под несколько языков программирования, и под распространённые платформы.
Само приложение, которое можно скачать в магазинах приложений соответствующих платформ, имеет возможность включить push уведомления, но, очевидно, эти уведомления будут работать только с backend сервисами разработчиков. Например, если зарегистрировать sip account на их доступном sip сервере, push уведомления в Linphone с этим профилем будут работать. В SDK приложения присутствует несколько собственных сервисов разработчиков (например, flexisip account manager), заранее настроенных на их инфраструктуру, и сервисы, которые конфигурируются под инфраструктуру пользователя. Используя SDK, возможно сделать своё приложение, привязав его к своим backend сервисам push уведомлений, и также при желании удалить модули, которые не требуются в конкретном случае, включая модули сервисов разработчиков.
SIP и PUSH backend.
Что касается SIP серверов, поддерживающих RFC 8599, то находятся, например, c заявленной поддержкой такие как OpenSIPS начиная с версии 3.1 и Flexisip от разработчиков Linphone, который также распространяется по условиям лицензии GNU GPLv3 . Однако, в случае sip backend-а, стоит иметь в виду, что нет препятствий принимать от клиента sip сообщения с заголовками необходимыми по RFC 8599, важна возможность взаимодействия с push backend-м и способность отрабатывать взаимодействие по RFC 8599. Поэтому, например, на sip proxy Kamailio также можно реализовать RFC 8599 с использованием модулей tsilo и http_async_client, о чём можно найти работы на конференциях по Kamailio. Если идти по этому пути, то придётся самостоятельно разработать с нуля службу взаимодействия с PNS производителей мобильных платформ, и схему работы sip сервера. Очевидно, такое решение более сложное и ресурсоёмкое. В данной статье рассматривается первый вариант – использовать готовое свободно распространяемое п.о., пусть, возможно, и увеличив количество узлов. Учитывая, что клиентская часть уже определена, выбор падает на использование sip proxy Flexisip в качестве push gateway для некой тестовой sip backend инфраструктуры.
Задача и схема тестовой среды.
Таким образом, в первом приближении декомпозиция задачи - получить sip клиентское приложение с работающими push уведомлениями, выглядит следующим образом:
Развернуть и сконфигурировать SIP backend инфраструктуру
Развернуть и сконфигурировать SIP push gateway с поддержкой RFC 8599
Настроить push сервис на стороне PNS провайдера (в данном случае Google)
Разработать клиентское SIP приложение с поддержкой RFC 8599
Сконфигурировать клиентское SIP приложение
Не останавливаясь подробно на каждой подзадаче, далее будут описаны их основные особенности.
В качестве backend инфраструктуры будут использоваться sip proxy - Kamailio (v. 6.1.1) и rtp proxy – Rtpengine (v.13.5.1.7).
Flexisip последней, стабильной на момент написания версии 2.4.3 развёрнут в конфигурации pushgateway. Используется только proxy сервис, остальные его службы отключены. Разворот сервера и конфигурация подробно описаны на сайте разработчиков, там же можно посмотреть описание распространённой схемы использования с расположением шлюза и backend инфраструктуры за NAT, которая и будет задействована в тестовой среде. Первый важный момент - не забыть отключить media relay (по умолчанию включён), который для режима pushgateway не требуется, т.к. в нём Flexisip работает только с сигнализацией:

В собираемой схеме flexisip будет использоваться только как pushgateway, поэтому можно не включать настройки, связанные с режимом outbound proxy. Таким образом, сервер будет обрабатывать только сессии, связанные с Register и входящими от Kamailio Invite. В итоге получается схема соединений, показанная на рисунке:

Здесь поток трафика 1 от Android устройства с номером 7003 соответствует маршруту SIP регистрации и входящих вызовов. Поток трафика 3 представляет маршрут push уведомлений. В настройках sip клиента на Android (адаптированный Linphone) будут указаны:
сервер регистрации – pxxxxxxx.pro (доменное имя Flexisip)
outbound proxy – sxxxxxxx.pro (доменное имя Kamailio)
Клиент будет регистрироваться в sip домене – sxxxxxxx.pro на Kamailio.
Конфигурация проекта в Google.
На странице firebase console нужно создать новый проект для пользовательского приложения (адаптированный Linphone), которое будет использовать сервис FCM. Отсюда для пользовательского экземпляра Linphone понадобятся Package name и файл google-service.json. Для сервера Flexisip нужен будет ключ сервисной учётной записи.

После того, как проект сконфигурирован в Firebase, и на Flexisip установлен ключ сервисной учётной записи, можно запустить утилиту flexisip_pusher для проверки работоспособности сервиса уведомлений:


Как видно, push уведомление с flexisip успешно отправлено на FCM.
Разработка приложения.
Для разработки приложения использовалось актуальное Linphone Android SDK версии 6.0.23.
В SDK включено много возможностей, которые в рассматриваемой среде не используются, тогда они либо отключаются при помощи конфигурационных параметров при компиляции apk, либо отключаются в настройках приложения, либо вырезаются на уровне кода (в данном случае kotlin). Минимально на уровне кода желательно сделать, например, уникальное имя, и уникальный User-Agent, чтобы в дальнейшем его можно было бы применить против попыток назойливого сканирования в публичной сети.

Из коробки приложение с минимальными правками будет иметь недостатки в виде некритичных ошибок, поэтому для их устранения придётся разобраться с заложенными особенностями, провести необходимые изменения в коде и выполнить отладку. В readme к SDK описано, что минимально необходимо сделать для компиляции своего экземпляра приложения со своим push и sip backend-м. Потребуется заменить файл app/google-service.json на скаченный из свойств проекта в firebase, и заменить в файле app/build.gradle.kts значение packageName на актуальное из проекта в firebase. Если компилируется signed APK, то нужно экспортировать fingerprint-ы sha-1 и sha-256 из локального репозитория ключей приложения, чтобы затем добавить их в проект на Google Firebase. Выше на рисунке с данными проекта видно начало раздела «SHA certificate fingerprints», куда добавляются ключи.
Настройка приложения.
В настройках приложения для проверки схемы push уведомлений важно отключить сервис фоновой работы, который включен по умолчанию. Этот сервис на Android делает приложение постоянно включенным, а в этом случае push уведомления не требуются, так как приложение не засыпает и не экономит батарейку. На iOS устройствах возможности сделать приложение фоновым нет, поэтому там работают только push уведомления:


Разрешения и режим батареи приложения в Android, и установка адресов прокси (flexisip) и исходящего прокси серверов (kamailio) в приложении:



Тестирование.
Для тестирования работы push уведомлений нужно сделать входящий вызов на номер 7003, когда телефон находится в заблокированном состоянии. На рисунке с sip обменом c pushgateway во время регистрации (согласно схеме) пользователя 7003 в поле Contact присутствуют необходимые по RFC значения pn-provider, pn-param, pn-prid:

pn-provider – идентификатор PNS поставщика, для android устройств – это fcm
pn-param – идентификатор приложения для PNS
pn-prid – уникальный идентификатор устройства
После того, как пользователь 7003 зарегистрировался, телефон блокируется, приложение автоматически приостанавливается. Пользователь 7002 совершает вызов пользователю 7003. Как видно, на первый INVITE от pashgateway в 18:01:08:591417 пользователь 7003 не ответил в течении установленного интервала, так как приложение находится в приостановленном состоянии, поэтому pushgateway отправил в 18:01:09:184 push запрос на FCM и ещё два INVITE:

Ниже лог отправки push уведомления на FCM:


После получения push уведомления приложение пользователя 7003 просыпается и отправляет сообщение Register на pushgateway, которое он получает в 18:01:09:465.

После успешной регистрации приложение в 18:01:10:199237 отсылает на pushgateway Trying в ответ на первый INVITE и диалог продолжается как обычно. SIP обмен этого же вызова снятый на Kamailio:

Регистрация пользователя 7003 и приём входящего вызова на приостановленное приложение прошли согласно схеме. Вывод – собранная тестовая среда c Flexisip в качестве pushgateway для sip клиентов на Android работоспособна и поддерживает работу с параметрами, определёнными в RFC 8599.
