SIP на смартфоне через свой Asterisk: TLS, фон и «телефон уснул»
Москва, Щёлковское шоссе, д. 92, корп. 7 · Пн–Пт 9:00–19:00 · +7 903 729-62-41
АйТи Фреш
Серверы и инфраструктура

Рабочий номер на личном смартфоне через свой Asterisk: почему входящие теряются, когда телефон засыпает, и как это настроить правильно

Автор: , директор ООО «АйТи-Фреш» · · ~18 мин чтения
Уснувший смартфон соединён нитью с замком-щитом с серверной стойкой Asterisk, входящий звонок застрял в сетке
Регистрация жива, а телефон спит: звонок застревает по дороге.

Входящие на смартфоне теряются, потому что уснувшее приложение или NAT оператора не слышат АТС, а регистрация в Asterisk числится живой. Лечится TLS 5061 с SRTP, NAT-опциями PJSIP, qualify 60 с, push-клиентом или резервом на мобильный номер.

Почему входящие на смартфоне теряются, когда телефон засыпает

Типичная картина: у выездного менеджера в приложении горит зелёная точка «Зарегистрирован», а клиент уже третий раз попадает в голосовую почту. Менеджер стоит с телефоном в руке на показе, экран погас пять минут назад, и вся эта зелёная точка — красивое враньё. Я видел такое десятки раз и каждый раз слышу одну и ту же версию: «АТС глючит». АТС как раз в порядке. Глючит наша вера в то, что смартфон ведёт себя как настольный аппарат.

Настольный телефон держит регистрацию годами, потому что ему нечего делать, кроме как ждать звонка. Смартфон устроен наоборот: операционная система считает каждую фоновую программу подозреваемой в расхищении батареи. iOS фактически не даёт обычному приложению без push-уведомлений держать SIP-регистрацию в фоне. Android со своими режимами энергосбережения и Doze усыпляет сеть и закрывает сокеты, а вместе с ними и соединение TLS. Приложение «живо», но его уши заткнуты, а собственный Asterisk и внедрение открытых решений об этом не знают.

Знать об этом Asterisk не может по простой причине: регистрация в нём числится живой до истечения expiration. Телефон зарегистрировался на 3600 секунд (это значение по умолчанию в default_expiration), уснул через пять минут, и ещё 55 минут АТС честно отправляет вызовы в мёртвый контакт. Именно так половина входящих и уходит в голосовую почту: звонок ушёл адресату, который физически не слышит. К этому добавляется NAT мобильного оператора, который закрывает «дырку» для входящих пакетов, если по ней давно ничего не ходило, и смена IP при переходе с Wi-Fi на LTE.

Чтобы вы не искали виноватого в неправильном месте, держите в голове три независимые причины. Первая: приложение усыплено системой. Вторая: NAT закрыл путь для входящих. Третья: IP поменялся, а клиент об этом не узнал. Лечатся они разными средствами, и ни одно из них не называется «поставить приложение позеленее». Дальше в статье по порядку: как защитить канал, какие опции поставить в PJSIP, какие бывают клиенты и что делать с теми, кого уснувший телефон всё равно подведёт.

Памятка: Почему входящие на смартфоне теряются, когда телефон засыпает — схема
Памятка: Почему входящие на смартфоне теряются, когда телефон засыпает. Открыть схему в полном размере

TLS 5061 и SRTP: что закрыть до того, как настраивать фон

Сначала про вещи, которые не про батарею, а про здравый смысл. Бесплатное приложение «по инструкции из интернета» чаще всего подключается по UDP на 5060, и тогда логин, пароль и разговоры идут через мобильную сеть открытым текстом. В кафе, на вокзале, в гостиничном Wi-Fi это читается любым любопытным сотрудником соседнего столика. Меня веселит, что компании, которые шифруют почту, спокойно разрешают менеджерам звонить клиентам по цене квартиры без единого шифра. Для работы с корпоративной АТС снаружи открывается только 5061 по TLS, а 5060/UDP наружу не торчит вообще.

Транспорт в pjsip.conf задаётся отдельной секцией. Протокол tls, привязка bind=0.0.0.0:5061, сертификат и ключ, метод tlsv1_2 или tlsv1_3 (я ставлю не ниже tlsv1_2). Если АТС стоит за NAT, добавляются external_signaling_address и external_media_address, а локальные сети перечисляются в local_net. Один неочевидный параметр: allow_reload по умолчанию no, то есть изменения транспорта подхватываются только после перезапуска Asterisk, и половина «у меня не применилось» оттуда. Сертификат нужен доверенный, например от Let's Encrypt, иначе мобильные клиенты начнут либо ругаться, либо молча отказываться. Если после включения TLS звонки начали рваться, у нас есть отдельный разбор, почему TLS ломает звонки и как это исправить.

Вот рабочий каркас транспорта. Пути, адреса и домен условные, подставьте свои; значения шифров и перечень методов проверьте в своей версии Asterisk:

; /etc/asterisk/pjsip.conf — транспорт для мобильных клиентов
[transport-tls]
type=transport
protocol=tls
bind=0.0.0.0:5061
cert_file=/etc/asterisk/keys/fullchain.pem
priv_key_file=/etc/asterisk/keys/privkey.pem
method=tlsv1_2
external_signaling_address=192.0.2.10
external_media_address=192.0.2.10
local_net=10.0.0.0/24
allow_reload=yes

[global]
type=global
keep_alive_interval=30

Параметр keep_alive_interval в секции global отвечает за отправку keep-alive по соединениям TCP и TLS и по умолчанию равен 90 секундам. Для мобильных клиентов его часто ставят в 30, и это та самая цифра, которую я не меняю без причины.

Шифрование голоса включается на endpoint параметром media_encryption=sdes, то есть SRTP с ключами в SDP. Раз сигнализация идёт по TLS, ключи не утекают. Если оставить media_encryption=no по умолчанию, то TLS защитит пароль, а разговор продолжит ходить открытым RTP. Это половина защиты, которую легко принять за целую. Параметр media_encryption_optimistic оставьте выключенным: хотите шифр, так шифруйте, а не «если получится».

Теперь периметр, ради которого к нам и приходят после первой же ночи «звонков из Бразилии». Я рекомендую: длинные пароли на каждый endpoint, max_contacts не больше одного-двух, fail2ban или nftables на неудачные регистрации (разбор здесь: fail2ban для защиты Asterisk от SIP-атак), отдельный контекст для мобильных без международных направлений. Системный подход к тому, что открыто наружу, мы описываем в услуге информационная безопасность, но начните просто: закройте 5060 снаружи и не публикуйте административные интерфейсы.

SIP над мобильным интернетом без TLS — это логин, пароль и голос в открытом виде на любой точке Wi-Fi. Шифрование — не дополнительная опция, а вход в игру.
Рабочий номер на личном смартфоне через свой Asterisk: почему входящие теряются, когда телефон засыпает, и как это настроить правильно — схема
Схема к статье. Открыть схему в полном размере

NAT, qualify и expiration: настройки endpoint и AoR для мобильного абонента

Допустим, TLS поднят, а звук идёт в одну сторону или звонок рвётся через пару десятков секунд. Знакомая ситуация: администратор три раза пересобирает конфиг, а причина — три галочки, которые по умолчанию стоят в положении «для офиса». Мобильный абонент всегда за чужим NAT, и его адрес в заголовке Contact не имеет отношения к реальному. Пакеты с ним надо отправлять туда, откуда они пришли, а не туда, куда клиент написал в SIP.

Для этого на endpoint включаются rtp_symmetric=yes и rewrite_contact=yes (по умолчанию оба no), force_rport оставляется yes (так по умолчанию), а direct_media обязательно выключается: по умолчанию он yes, и Asterisk пытается свести медиа напрямую между устройствами, которые сидят за разными NAT и не видят друг друга. Подробнее про ICE и STUN в таких схемах мы писали в статье настройка PJSIP с ICE и STUN за NAT, а в документации Asterisk есть отдельная страница про работу PJSIP через NAT. Параметр rtp_keepalive по умолчанию 0; попробуйте поставить 10–30 секунд, чтобы NAT не закрывал порт во время длинной паузы в разговоре.

Теперь AoR, он же адрес регистрации. max_contacts по умолчанию 0, и это не «без ограничений», а «регистрироваться нельзя вообще»: без явного значения телефон получит отказ. Ставим 1, а при включённом remove_existing новая регистрация вытесняет старую. Для мобильного это правильно: сменился IP — телефон зарегистрировался снова, а мёртвый контакт ушёл. Дальше маленькое, но решающее: qualify_frequency=60 заставляет Asterisk раз в минуту посылать OPTIONS на контакт и видеть, жив ли он, qualify_timeout по умолчанию 3 секунды. Уснувший телефон отвечать перестаёт, контакт получает статус Unavail, и Asterisk не ждёт зря. А maximum_expiration (по умолчанию 7200 секунд) для мобильных разумно ограничить 600–900 секундами, чтобы «мёртвая» регистрация не висела часами.

Полный пример для одного мобильного абонента. Пароль, домен и адреса условные:

; /etc/asterisk/pjsip.conf — мобильный абонент agent01
[agent01]
type=endpoint
transport=transport-tls
context=from-mobile
disallow=all
allow=ulaw,alaw
auth=agent01
aors=agent01
media_encryption=sdes
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
direct_media=no
rtp_keepalive=15

[agent01]
type=auth
auth_type=userpass
username=agent01
password=ДлинныйСлучайныйПароль_заменить

[agent01]
type=aor
max_contacts=1
remove_existing=yes
qualify_frequency=60
qualify_timeout=3.0
default_expiration=600
minimum_expiration=60
maximum_expiration=600

Секции с одним именем и разным type здесь допустимы: так в PJSIP принято. Значение rtp_keepalive=15 — отправная точка для эксперимента, а не догма. Проверьте поведение в своей версии и на своих телефонах.

Есть и обратная сторона. Слишком частые qualify и keep-alive на клиенте без push заставляют телефон просыпаться и держать сокет, и батарея садится быстрее. Значения 60 секунд для qualify и 30 для keep-alive я выбираю как компромисс: этого хватает, чтобы NAT не закрывал порт и Asterisk быстро узнавал о мёртвом контакте. Если вы хотите ещё реже, получите потерянные звонки; ещё чаще — получите гневное сообщение «ваше приложение съело 40 % батареи к обеду».

Цифры и версии: NAT, qualify и expiration: настройки endpoint и AoR для мобильного абонента — схема
Цифры и версии: NAT, qualify и expiration: настройки endpoint и AoR для мобильного абонента. Открыть схему в полном размере

Три стратегии: push-клиент, короткая регистрация с переадресацией или только переадресация

Дальше самое неприятное: настройка PJSIP сама по себе не разбудит уснувший телефон. Что бы вы ни выставили на сервере, iOS не даст приложению без push работать в фоне, а Android без исключения из энергосбережения прибьёт сокет. Поэтому у вас реально три стратегии, и каждая платит своей монетой. Я люблю этот момент в проектах: директор спрашивает «а нельзя просто настроить, чтобы работало», и приходится объяснять, что нельзя, а можно выбрать, чем платить.

Первая стратегия: клиент с push-уведомлениями. Приложение не держит сокет, а просыпается по push, когда звонит АТС. Так работают, например, Linphone с push-сервисом и платный Groundwire от Acrobits, оба поддерживают TLS и SRTP. Тут надо трезво оценить один факт: сервер уведомлений принадлежит разработчику клиента, и ваши события вызова проходят через чужую инфраструктуру. Если «без облака» для вас принципиально, это важная деталь; если нет, то это самый удобный вариант для постоянных звонков. Подробности и условия смотрите на сайтах linphone.org и acrobits.net, я их здесь не пересказываю, потому что условия меняются.

Вторая стратегия: клиент без push, короткие expiration, qualify и переадресация на мобильный номер при неответе. Контакт вызывается 20 секунд; если не ответил, Asterisk через ваш транк звонит на сотовый номер агента. Это та схема, которую мы ставим чаще всего: она не зависит от чужих серверов и работает на любом клиенте. Минус честный: исходящий звонок на сотовый идёт через транк, и минуты оплачиваются по тарифу вашего оператора. Для 6 агентов это копейки, для 60 уже стоит посчитать.

Третья стратегия: отказаться от SIP на смартфоне и оставить только переадресацию на мобильный номер. Это самое надёжное, потому что сотовый звонок не уснёт никогда, и заодно самое скучное. Цена: разговор идёт мимо приложения, и если ваш транк не записывает разговоры, то АТС записи не получит. Для дежурных и курьеров это нередко оптимальный вариант, и красота SIP-клиента здесь вторична. Отдельно упомяну исключение из энергосбережения на Android: оно делается вручную на каждом телефоне, и вы не заставите это работать централизованно, зато вы заставите сотрудников хотя бы один раз пройти по меню.

Исключение приложения из энергосбережения на Android делается руками на каждом телефоне и после крупных обновлений системы нередко слетает. Не стройте на нём единственную линию обороны.
Если у вас входящие на смартфонах уже уходят в голосовую почту, мы разберём ваш Asterisk, включим TLS и SRTP, настроим NAT-опции и qualify и поставим резерв на мобильный номер. Обсудить задачу

Кейс агентства «Ясный Двор»: 6 агентов на показах и половина входящих в голосовой почте

Агентство недвижимости «Ясный Двор»: 18 рабочих мест, Asterisk 20 на Debian 12, SIP-транк оператора с городским номером. Шестеро агентов практически не бывают в офисе: показы, встречи, дороги. Раньше они ставили бесплатное приложение по инструкции из интернета. Приложение подключалось по UDP, звонки на смартфон приходили через раз, и половина входящих уходила в голосовую почту. Офис-менеджер ежедневно перезванивала руками: слушала голосовую почту, искала агента, перезванивала клиенту. Клиент к этому моменту уже звонил в другое агентство.

Мы в АйТи-Фреш разобрали картину за полчаса. В pjsip show contacts регистрации числились живыми, а qualify вообще не был включён, поэтому Asterisk не узнавал о мёртвых контактах. Транспорт был UDP без шифрования, rewrite_contact и rtp_symmetric не включены, direct_media оставался по умолчанию. Для начала мы перевели агентов на TLS 5061 и SRTP, поставили таймаут регистрации 600 секунд, qualify 60 секунд и keep-alive 30 секунд, как в примерах выше. Затем добавили резерв: если смартфон не ответил за 20 секунд, Asterisk через транк звонит на мобильный номер агента.

Внедрение заняло один рабочий день, потом два дня мы наблюдали за журналом и за теми самыми 6 агентами. Доля «потерянных» входящих упала кратно (точных процентов не называю: на таком объёме это был бы подгон под красивую цифру). Остался один агент со старым Android, у которого батарея всё равно садилась до вечера: мы перевели его на вариант с переадресацией, без SIP на телефоне. Он остался доволен, а разговоров, которые он принимает, не стало меньше. Диалплан для входящего, который мы использовали, схематично выглядит так (номера условные, проверьте имя транка и формат номеров у своего оператора):

; /etc/asterisk/extensions.conf — входящий на агента с резервом
[from-trunk]
exten => 4950100,1,NoOp(Входящий на агента)
 same => n,Dial(PJSIP/agent01,20)
 ; если агент ответил, после разговора сюда не вернёмся (у Dial нет опции g);
 ; сюда попадаем только при неответе, занятости или недоступности контакта.
 ; Резерв: переадресация через транк на мобильный номер.
 ; Минуты оплачиваются по тарифу оператора, учитывайте это.
 same => n,Dial(PJSIP/+79990000001@trunk-operator,40)
 same => n,Hangup()

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

Цифры и версии: Кейс агентства «Ясный Двор»: 6 агентов на показах и половина входящих в голосовой почте — схема
Цифры и версии: Кейс агентства «Ясный Двор»: 6 агентов на показах и половина входящих в голосовой почте. Открыть схему в полном размере

Диагностика: симптом, причина, команда проверки

Когда звонки перестают доходить, первым делом открывайте консоль Asterisk, а не чат поддержки приложения. Основной инструмент — pjsip show contacts: он показывает контакты, их адрес и статус по qualify; срок регистрации конкретного контакта видно в pjsip show contact с его именем или в pjsip show aors. Если статус Avail, а звонок не доходит, смотрите дальше (NAT, кодеки). Если Unavail — контакт мёртв, и вопрос уже к телефону. Если контакта нет вообще, телефон не зарегистрировался; тогда самое время открыть разбор почему не проходит регистрация PJSIP.

Минимальный набор команд, которым я пользуюсь:

# живые регистрации и статус qualify (Avail/Unavail)
asterisk -rx "pjsip show contacts"
asterisk -rx "pjsip show aors"
# настройки endpoint и транспорты
asterisk -rx "pjsip show endpoints"
asterisk -rx "pjsip show endpoint agent01"
asterisk -rx "pjsip show transports"
# трассировка SIP-сообщений (выключить: pjsip set logger off)
asterisk -rx "pjsip set logger on"
# проверка сертификата снаружи
openssl s_client -connect pbx.example.com:5061 -servername pbx.example.com </dev/null

Теперь типичные симптомы. «Регистрация OK, входящие в голосовую почту» — приложение уснуло или NAT закрыл порт; смотрите статус контакта по qualify. «Нет звука в одну сторону на LTE» — чаще всего direct_media=yes, либо rtp_symmetric и rewrite_contact не включены, либо NAT оператора; проверьте три параметра на endpoint. «Отваливается при переходе Wi-Fi на LTE» — сменился IP, клиент должен перерегистрироваться, и короткий expiration помогает. «Звонок рвётся через 30 секунд» — ACK не дошёл из-за NAT, проверяйте rewrite_contact и force_rport; про похожую историю у нас есть разбор звонков, падающих через 30 секунд. «Батарея садится» — qualify и keep-alive слишком частые, либо клиент без push держит сокет постоянно.

Последнее, что хочется проверить в любой схеме: что будет, когда кто-то перебирает пароли. Загляните в журнал и убедитесь, что fail2ban или nftables реально баннят неудачные регистрации, а контекст для мобильных не позволяет звонить за границу. И заведите календарную задачу на продление сертификата: Let's Encrypt выдаёт сертификаты на короткий срок, и «внезапно умерший TLS» в понедельник утром у меня в практике случался чаще, чем хотелось бы. После продления перезагрузите транспорт; с allow_reload=yes это делается без полной остановки, но проверьте в своей версии, что именно подхватывается.

Частые вопросы

Почему регистрация в Asterisk есть, а входящие не доходят?

Регистрация числится живой до истечения expiration, даже если приложение уснуло или NAT закрыл порт. Включите qualify_frequency=60 и сократите maximum_expiration до 600–900 с; статус контакта смотрите командой pjsip show contacts.

Можно ли обойтись без push-уведомлений?

Можно: короткие expiration, qualify и переадресация на мобильный номер через 20 секунд без ответа. На iOS клиент без push в фоне надёжно не работает, поэтому резерв на сотовый обязателен.

Какой порт и какое шифрование открывать наружу?

Только 5061 по TLS, а 5060/UDP закрыть. Для голоса на endpoint включить media_encryption=sdes (SRTP), сертификат взять доверенный, например Let's Encrypt.

Что ставить для мобильных абонентов за NAT?

direct_media=no, rtp_symmetric=yes, rewrite_contact=yes, force_rport=yes, на AoR max_contacts=1 и remove_existing=yes. Значения проверьте в своей версии Asterisk.

Сколько стоит минута переадресации на мобильный номер?

Звонок идёт через ваш SIP-транк, поэтому минуты тарифицирует оператор транка по своему прайсу. Для небольшой команды суммы обычно небольшие, но посчитать их заранее стоит.

Столкнулись с похожей задачей? Обращайтесь — решим

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

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи
АйТи-Фреш (ООО «АЙТИ-ФРЕШ») · г. Москва, Щёлковское шоссе, д. 92, корп. 7 · +7 903 729-62-41 · info@itfresh.ru · Пн–Пт 9:00–19:00