Рабочий номер на личном смартфоне через свой 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, какие бывают клиенты и что делать с теми, кого уснувший телефон всё равно подведёт.
- Приложение усыплено системой: сокет закрыт, регистрация числится живой до конца срока в 600–3600 с
- NAT оператора закрыл порт: исходящие работают, входящие не доходят
- Смена Wi-Fi на LTE: сменился IP, а клиент не перерегистрировался
- Обычная ошибка настройки: нет push, нет qualify, нет резерва на мобильный номер
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 снаружи и не публикуйте административные интерфейсы.
- Наружу открыт только 5061/TCP (TLS), 5060/UDP закрыт
- Сертификат доверенный, срок продления в календаре и в мониторинге
- media_encryption=sdes, пароли от 16 символов, свой пароль на каждый endpoint
- Мобильный контекст без международных направлений, лимит параллельных вызовов
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 % батареи к обеду».
- direct_media=no — медиа идёт через Asterisk, не напрямую между устройствами за NAT
- rtp_symmetric=yes, rewrite_contact=yes, force_rport=yes — ответы идут туда, откуда пришёл пакет
- max_contacts=1 и remove_existing=yes — одна живая регистрация на абонента
- qualify_frequency=60, maximum_expiration=600 — быстро узнаём о мёртвом контакте
Три стратегии: 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: оно делается вручную на каждом телефоне, и вы не заставите это работать централизованно, зато вы заставите сотрудников хотя бы один раз пройти по меню.
- Push-клиент: входящие доходят, батарея в порядке, но зависимость от сервера разработчика клиента
- Без push + переадресация: сами управляем всем, резерв на сотовый, платите минуты по тарифу
- Только переадресация: надёжнее всего, но без записи разговоров в АТС и без SIP на телефоне
Кейс агентства «Ясный Двор»: 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-клиент, другому — переадресация.
- Было: UDP 5060, без qualify, половина входящих в голосовой почте, ручные перезвоны офис-менеджера
- Стало: TLS 5061, SRTP, регистрация 600 с, qualify 60 с, keep-alive 30 с, резерв на мобильный через 20 с
- Срок: 1 рабочий день внедрения и 2 дня наблюдения
- Остаток: 1 агент со старым Android переведён на переадресацию из-за батареи
Диагностика: симптом, причина, команда проверки
Когда звонки перестают доходить, первым делом открывайте консоль 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 это делается без полной остановки, но проверьте в своей версии, что именно подхватывается.
- Статус контакта: Avail или Unavail по qualify — смотрим в pjsip show contacts
- Звук в одну сторону: direct_media, rtp_symmetric, rewrite_contact
- Разрыв через 30 с: ACK не дошёл, проверяем force_rport и rewrite_contact
- Батарея: ослабляем qualify и keep-alive или переходим на push-клиент
Частые вопросы
Почему регистрация в 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-транк, поэтому минуты тарифицирует оператор транка по своему прайсу. Для небольшой команды суммы обычно небольшие, но посчитать их заранее стоит.
Источники
- Asterisk 20 — res_pjsip: transport — Проверены protocol, bind, cert_file, priv_key_file, method, external_signaling_address, external_media_address, local_net, allow_reload, tcp_keepalive_*: https://docs.asterisk.org/Latest_API/API_Documentation/Module_Configuration/res_pjsip/
- Asterisk 20 — res_pjsip: endpoint и aor — Проверены media_encryption, rtp_symmetric, rewrite_contact, direct_media, force_rport, rtp_keepalive, max_contacts, remove_existing, qualify_frequency, qualify_timeout, default/minimum/maximum_expiration: https://docs.asterisk.org/Latest_API/API_Documentation/Module_Configuration/res_pjsip/
- Asterisk 20 — res_pjsip: global — Проверены keep_alive_interval и default_realm: https://docs.asterisk.org/Latest_API/API_Documentation/Module_Configuration/res_pjsip/
- Asterisk — Configuring res_pjsip to work through NAT — Описание работы PJSIP за NAT: https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/Configuring-res_pjsip-to-work-through-NAT/
- Let's Encrypt — Доверенные сертификаты для транспорта TLS: https://letsencrypt.org/
- Linphone и Acrobits — Сайты разработчиков SIP-клиентов с поддержкой push, TLS и SRTP (условия смотрите у производителей): https://www.linphone.org/ и https://www.acrobits.net/
