Я всегда был активным сторонником самохостинга, но ни разу не пробовал ничего, связанного с VoIP. Недавно я приобрёл несколько IP-телефонов и создал личную домашнюю телефонную сеть на основе Asterisk. Это руководство поможет вам настроить собственную телефонную систему при помощи опенсорсных инструментов.

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

Краткое введение в SIP

Прежде, чем браться за конфигурации телефонов и язык скриптинга Asterisk, стоит потратить немного времени на знакомство с сетевыми протоколами. Это оправдает себя позже, когда вам неизбежно придётся отлаживать проблемы связи голосовых вызовов (поверьте мне).

Session Initialization Protocol (SIP) — это сигнальный протокол, применяющийся практически во всех цифровых телефонных устройствах, произведённых за последние два десятка лет. VoIP-телефоны, программные телефоны (softphone), офисные устройства для конференц-связи — все они используют SIP. Соединение SIP может осуществляться по TCP или UDP и обычно слушает порт 5060.

О SIP вам нужно знать два очень важных аспекта.

  • SIP не передаёт голосовые данные. Это просто сигнальный протокол, который используется только для выбора IP-адреса, порта и формата аудио, которые должны использоваться для передачи аудиопотока.

  • SIP появился в 1999 году, он проектировался, исходя из предположения о том, что у каждого устройства есть свой публичный IP-адрес с возможностью глобальной маршрутизации. Казалось бы, стандарт IPv6 был выпущен в 1995 году, и NAT скоро должны будут остаться в прошлом... или нет? К сожалению, этого не произошло.

Например, допустим, у нас есть стандартная домашняя сеть с двумя VoIP-телефонами в локальной подсети 192.168.1.0/24. Что произойдёт, когда этим двум телефонам нужно пообщаться друг с другом? Если говорить простыми словами, установка связи (handshake) SIP будет происходить примерно так:

Привет, 192.168.1.6.
Я бы хотел совершить аудиовызов при помощи кодека G.711.
Отправляй мне свои аудиокадры по адресу 192.168.1.5, порт 20000.

Привет, 192.168.1.5!
Отлично, я поддерживаю кодек G.711.
Отправляй мне свои аудиокадры по адресу 192.168.1.6, порт 30000.

Каждый телефон случайным образом выбирает неиспользуемый порт UDP, по которому он будет получать аудиопоток другой стороны. После согласования по SIP они обе будут отправлять друг другу голосовые данные по Real-Time Transport Protocol (RTP). Так как обе они находятся в одной локальной сети, всё это без проблем заработает.

Проблемы с NAT

А теперь рассмотрим, что произойдёт, когда мы будем звонить кому-то за пределами локальной сети. Допустим, у нашего маршрутизатора есть публичный IP-адрес 203.0.113.8, а у нашей подруги Алисы есть адрес 198.51.100.7.

Привет, 198.51.100.7. Алиса рядом?
Я бы хотел совершить аудиовызов при помощи кодека G.711.
Отправляй мне свои аудиокадры по адресу 192.168.1.5, порт 20000.

Привет, 192.168.1.5. Вижу, что твой исходящий IP-адрес 203.0.113.8…и это странно!
Да, я поддерживаю кодек G.711.
Отправляй мне свои аудиокадры по адресу 198.51.100.7, порт 30000.

Благодаря преобразованию сетевых адресов (network address translation, NAT) мы сможем установить SIP-соединение с Алисой. К сожалению, скорее всего, выяснится, что аудио работает только в одном направлении!

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

Но, во-вторых, наш маршрутизатор всё равно будет блокировать весь её аудиотрафик! Когда он получает UDP-пакет из аудиопотока Алисы, NAT-соединение с хранением состояния, соответствующее внутреннему клиенту, отсутствует, поэтому пакет отклоняется. Увы!

Решения для NAT

Решить эту проблему можно тремя способами.

  • Выдать каждому из наших SIP-устройств собственный публичный IP (скорее всего, это малореально).

  • Использовать Application Layer Gateway SIP. Это ужасная функция, имеющаяся у некоторых маршрутизаторов. По сути, она выполняет глубокий анализ пакетов трафика SIP, переписывает заголовки и создаёт на лету перенаправления портов, чтобы входящий аудиопоток точно добрался до вашего устройства.

    ALG протокола SIP — это хак, печально известный своей забагованностью. Кроме того, если вы решите шифровать свой трафик SIP при помощи TLS, всё перестанет работать.

  • Сконфигурировать фиксированный диапазон портов RTP на каждом из SIP-устройств, затем создать в маршрутизаторе статичные правила перенаправления портов для каждого устройства. Это единственное приличное решение.

    Стоит отметить, что поскольку мы используем Asterisk, есть также вариант прокси-передачи всех аудиопотоков через Asterisk box; тогда останется лишь настроить перенаправление портов (мы вернёмся к этому позже).

Что такое Asterisk?

Asterisk — это опенсорсная реализация офисной АТС (private branch exchange, PBX). Если вы новичок в VoIP, то, вероятно, для вас это совершенно ничего не значит.

АТС — центральный мозг любой частной телефонной сети. Общий принцип таков: с одной стороны АТС мы подключаем кучу «глупых» телефонов, а с другой — один или несколько аплинков к телефонной линии общего пользования (PSTN). АТС позволяет внутренним телефонам соединяться друг с другом через частные внутренние добавочные номера, в то же время мультиплексируя вызовы к PSTN. Также она обрабатывает такие расширенные функции, как голосовую почту, очереди вызовов, направление вызовов на группы телефонов (hunt group) и интерактивные меню.

Когда-то давно АТС представляла собой огромный шкаф в серверной с хаотичным переплетением торчащих телефонных кабелей RJ-11, соединённых с каждым телефоном в офисе. Сегодня этих монстров заменили программные АТС с голосовым SIP через стандартные Ethernet-сети. Стандартом де-факто среди таких АТС стала Asterisk.

Глоссарий

Прежде чем мы приступим, нужно освоиться с терминологией. Asterisk — это очень старый проект с неинтуитивной конфигурацией. Надеюсь, этот раздел поможет вам в расшифровке документации и руководств по настройке.

Добавочные номера (extension)

В терминологии Asterisk любой набираемый номер называется extension (добавочным номером). Однако на практике под extension обычно понимается локальный телефонный номер (например, 6001) и связанный с ним аккаунт SIP. Добавочные номера могут быть алфавитно-цифровыми, но этим практически никто не пользуется — большинство указывает номера из трёх или четырёх цифр.

Очереди (queue)

Очереди Asterisk — это механизм для управления входящими звонками. Можно настроить, какие локальные добавочные номера могут звонить в каждую из очередей, и даже воспроизводить выбранную музыку, пока звонящие ждут ответа.

Магистраль SIP (SIP Trunk)

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

DID

DID (Direct Inward Dial) — это техническое название публичного телефонного номера.

Контексты (context)

В Asterisk каждому вызову присваивается контекст. Контекст — это просто выбранное вами имя, которое указывается при конфигурировании каждого добавочного номера или магистрали SIP. Например, я присваиваю всем своим внутренним телефонам контекст from-home, а магистрали SIP — контекст from-pstn.

Dialplan

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

Кодеки (codec)

Кодек — это тип цифрового кодирования, используемого для передачи аудиопотока. Каждое VoIP-устройство поддерживает один или несколько кодеков, а для того, чтобы совершать вызовы, сторонам нужен хотя бы один общий кодек. Если нет ни одного общего поддерживаемого кодека, Asterisk может побыть man-in-the-middle и выполнять транскодирование. G.711 — это, по сути, единственный кодек с всеобщей поддержкой. У него есть две версии: ulaw (Северная Америка/Япония) и alaw (во всех других регионах). Для внутренних вызовов я использую высококачественный кодек (G.722), а когда звоню на PSTN, то позволяю Asterisk выполнять транскодирование в G.711.

BLF / Presence

BLF (busy lamp field) — это небольшой светодиод на VoIP-телефоне, загорающийся, когда один из ваших контактов пользуется линией. В Asterisk эта функциональность включается при помощи так называемой presence subscription.

SIP / PJSIP

В некоторых онлайн-руководствах можно встретить упоминания модулей chan_sip и chan_pjsipchan_sip — это легаси-реализация Asterisk SIP. Сегодня следует использовать PJSIP.

Шаг 1: приобретение IP-телефона

Для начала вам понадобится один или несколько VoIP-телефонов. С Asterisk должно уметь работать любое устройство, поддерживающее вызовы по SIP, так что здесь всё сводится к личным предпочтениям.

Если вы новичок, то, вероятно, стоит сделать выбор в пользу простоты конфигурирования. У большинства современных VoIP-телефонов есть дружественный веб-GUI, в котором можно настраивать все аккаунты SIP, параметры звонков и так далее. Также я бы рекомендовал не покупать устройства, использующие проприетарные инструменты настройки.

Лично у меня никогда не возникает проблем с устройствами Yealink. В качестве настольного телефона я рекомендую модель T54W или W73P, если вы предпочитаете беспроводную модель.

Если вы не хотите приобретать физическое устройство, то можно использовать программное телефонное приложение наподобие Linphone. Я пользуюсь им на своём Android-телефоне с GrapheneOS, и при правильной настройке оно работает без проблем. Чуть ниже я расскажу о Linphone подробнее.

Шаг 2: подключение услуг VoIP

Далее вам нужно приобрести услуги VoIP-сервиса для совершения и получения звонков через телефонные сети общего пользования (PSTN). Часто поставщиков таких услуг называют SIP Trunk.

По сути, вы платите VoIP-компании небольшую ежемесячную сумму, порядка $3-$5 в месяц за каждый DID. Она предоставляет вам публичный телефонный номер, а также учётные данные её SIP-сервера. Настроив в Asterisk подключение к этому SIP-серверу, вы сможете совершать и принимать вызовы через PSTN по этому номеру.

Лично я рекомендую двух поставщиков.

  • VOIP.ms — это недорогой поставщик с серверами в США, Канаде и Западной Европе. Примерно за $5 можно получить неограниченные входящие вызовы с одним DID, или меньше, если вы предпочитаете платить поминутно.

    Также VOIP.ms поддерживает шифрование вызовов через TLS/SRTP.

  • JMP.chat (только США/Канада) — это сервис на основе XMPP, позволяющий совершать и принимать голосовые вызовы и сообщения SMS/MMS через имеющийся аккаунт XMPP. Лично мне нравится JMP, потому что уже существует куча качественных нативных приложений XMPP для десктопных и мобильных устройств. Кроме того, весь его стек опенсорсный.

    Хотя JMP в основном делает упор на вызовы по XMPP, он также предоставляет аккаунт SIP, который можно использовать с Asterisk.

Ещё есть куча других поставщиков VoIP, так что выбирайте.

Шаг 3: конфигурирование Asterisk

Теперь мы готовы к настройке Asterisk. Эти команды написаны для дистрибутивов на основе RHEL, но должны быть применимы и для других Unix-подобых систем.

Установка

Сначала установим Asterisk:

dnf install asterisk asterisk-pjsip asterisk-voicemail-plain

Также нам понадобятся звуковые файлы. В некоторых дистрибутивах они включены в пакет Asterisk (но не в EPEL).

for codec in g722 g729 gsm sln16 ulaw wav; do
  curl -sL "https://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-${codec}-current.tar.gz" \
  | tar xvz -C /usr/share/asterisk/sounds
done

Сетевая конфигурация

Можно приступать к редактированию файлов конфигурации. Если вы находитесь за NAT (а это скорее всего), то можно для начала рассказать Asterisk о вашей локальной сети. Отредактируем /etc/asterisk/pjsip.conf и добавим шаблоны транспорта.

; /etc/asterisk/pjsip.conf

; Этот шаблон содержит стандартные настройки для всех транспортов. 
[transport-defaults](!)
type = transport
bind = 0.0.0.0

; Для общения с любым адресом внутри local_net, Asterisk не применяет
; обход NAT.
local_net = 127.0.0.0/8
local_net = 10.0.0.0/8
local_net = 172.16.0.0/12
local_net = 192.168.0.0/16

; Если у вас есть публичный статический IP для сервера Asterisk, укажите его здесь.
external_media_address     = 203.0.113.8
external_signaling_address = 203.0.113.8


; Следующие транспорты UDP и TCP наследуют от значений по умолчанию.
[transport-udp](transport-defaults)
protocol = udp

[transport-tcp](transport-defaults)
protocol = tcp

Помните, выше мы говорили о NAT и аудиопотоках RTP? Нам также понадобится настроить статический диапазон портов для входящего аудиотрафика UDP. В rtp.conf укажите подходящий для вашего сервера Asterisk диапазон. Затем настройте в маршрутизаторе/файрволле перенаправление портов для перенаправления всего входящего UDP-трафика в этом диапазоне на внутренний IP сервера Asterisk.

Если ваш сервер Asterisk находится за NAT, и вы забудете это, то в телефонных вызовах звук будет идти только в одном направлении.

; /etc/asterisk/rtp.conf

[general]
rtpstart=20000
rtpend=20999

SIP Trunk

Далее нам нужно настроить SIP trunk. Я буду использовать pjsip_wizard.conf, потому что стиль конфигурирования PJSIP Wizard позволяет избавиться от кучи бойлерплейта. Для этого этапа вам понадобится имя хоста и порт сервера, а также имя пользователя и пароль, предоставленные апстрим-поставщиком услуг SIP.

; /etc/asterisk/pjsip_wizard.conf

; Этот шаблон содержит стандартные настройки для всех SIP trunk.
[trunk-defaults](!)
type = wizard

; Требуется SIP-аутентификация.
sends_auth = yes

; Требуется SIP-регистрация.
sends_registrations = yes

; Отправляем медиа на адрес и порт во входящем пакете, вне зависимости от того,
; что указано в заголовках SIP (обход NAT).
endpoint/rtp_symmetric = yes

; Перезаписываем контакт SIP адресом и портом запроса (обход NAT).
endpoint/rewrite_contact = yes

; Отправляем заголовок SIP Remote-Party-ID, некоторым поставщикам он требуется.
endpoint/send_rpid = yes

; Принудительно включаем кодек ulaw, который должен работать в любом устройстве Северной Америки.
endpoint/allow = !all,ulaw

; Шифрование вызова не входит в рамки обсуждаемого в этом руководстве. 
endpoint/media_encryption = no

; При сбое регистрации пытаемся повторить его приблизительно бесконечно.
registration/max_retries = 4294967295

; Не предполагаем, что сбой аутентификации действует постоянно.
registration/auth_rejection_permanent = no

; Выполняем проверку связи каждые 30 секунд.
aor/qualify_frequency = 30


; Здесь указываются SIP trunk. Для этого примера я использовал VOIP.ms.
; Можно выбрать для раздела любое уникальное имя.
[voipms](trunk-defaults)
; С большой долей вероятности вы будете использовать TCP.
transport = transport-tcp

; Имя хоста и порт поставщика SIP.
remote_hosts = atlanta2.voip.ms:5060

; Выбираем имя контекста для входящих вызовов этого аккаунта.
; Это имя будет использоваться в вашем dialplan.
endpoint/context = from-pstn

; Эти учётные данные предоставляет поставщик услуг SIP.
outbound_auth/username = 555555
outbound_auth/password = s3cret

Локальные добавочные номера SIP

В том же файле настраиваются и локальные добавочные номера. Добавочный номер требуется каждому VoIP-телефону или программному телефону внутри вашей сети. Я буду использовать для локальных добавочных номеров префикс 6XXX, а вы можете выбрать любой удобный вам формат.

В показанном ниже примере есть три добавочных номера: два для VoIP-телефонов и один для программного телефона на Android. Стоит отметить, что если вы хотите использовать программный телефон вне своей локальной сети, то вам или понадобится открыть свой инстанс Asterisk миру (если вы не знаете, что делаете, то это не рекомендуется), или настроить какой-то VPN.

; /etc/asterisk/pjsip_wizard.conf

; Этот шаблон содержит настройки по умолчанию для всех локальных добавочных номеров.
[extension-defaults](!)
type = wizard

; Требуем регистрации клиентов.
accepts_registrations = yes

; Требуем аутентификации клиентов.
accepts_auth = yes

; Когда количество одновременных логинов с одного аккаунта превышает max_contacts,
; разъединяем самую старую сессию.
aor/remove_existing = yes

; Для внутренних телефонов в дополнение к ulaw разрешаем более качественный кодек g722.
endpoint/allow = !all,g722,ulaw

; Имя контекста для BLF/presence. Это может быть любая строка; я выбрал
; "subscribe". Позже мы используем её в dialplan.
endpoint/subscribe_context = subscribe


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Добавочный номер 6001 - VoIP-телефон
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6001](extension-defaults)
; Имя контекста dialplan для вызовов, исходящих из этого аккаунта.
endpoint/context = from-home

; Адрес голосовой почты (примечание: я использую одинаковый адрес голосовой почты для всех добавочных номеров)
endpoint/mailboxes = 6000@default

; Внутренняя строка Caller ID для этого устройства.
endpoint/callerid = Living Room <6001>

; Имя пользователя аккаунта SIP. Традиционно в качестве него применяется добавочный номер.
inbound_auth/username = 6001

; Пароль аккаунта SIP (можете выбрать любой пароль).
inbound_auth/password = s3cret

; Максимальное количество одновременных логинов для этого аккаунта.
aor/max_contacts = 1

; Проверяем подключение каждые 30 секунд.
aor/qualify_frequency = 30

; Таймаут проверки подключения - 3 секунды.
aor/qualify_timeout = 3.0

; ВАЖНО! Этот параметр определяет, будет ли аудиопоток проксироваться
; через сервер Asterisk.
;
; Если к этому устройству можно напрямую можно получить доступ через (или через публично
; маршрутизируемый IP, или через отображение статических портов маршрутизатора), то выберите YES.
;
; В противном случае, если это устройство скрыто за NAT, выберите NO.
endpoint/direct_media = yes


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Добавочный номер 6002 - VoIP-телефон
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (Те же настройки, что и выше, отличаются лишь добавочный номер и пароль.)
[6002](extension-defaults)
endpoint/context      = from-home
endpoint/mailboxes    = 6000@default
endpoint/callerid     = Kitchen <6002>
inbound_auth/username = 6002
inbound_auth/password = s3cret2
aor/max_contacts      = 1
aor/qualify_frequency = 30
aor/qualify_timeout   = 3.0
endpoint/direct_media = yes


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Добавочный номер 6003 - приложение Linphone в Android-устройстве
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6003](extension-defaults)
endpoint/context      = from-home
endpoint/mailboxes    = 6000@default
endpoint/callerid     = Smartphone <6003>
inbound_auth/username = 6003
inbound_auth/password = s3cret3
aor/max_contacts      = 1
endpoint/direct_media = no

; Для мобильных устройств проверки подключения должны быть отключены.
; На то есть две причины:
;   1. Плохой сигнал сотовых сетей часто приводит к разрыву соединения по таймауту,
;      даже если на самом деле всё в порядке.
;   2. Частый трафик в TCP-сессии приводит к постоянным пробуждениям
;      и быстро разряжает аккумулятор.
aor/qualify_frequency = 0

Голосовая почта

Записи голосовой почты хранятся в локальной файловой системе сервера Asterisk. Получить доступ к ним можно, набрав с локального телефона специальный добавочный номер голосовой почты (сконфигурированный в dialplan). Кроме того, если у вас запущен локальный MTA, при получении новой голосовой почты Asterisk может отправлять электронное письмо на выбранный вами адрес.

В предыдущем разделе мы указали для внутренних добавочных номеров адрес голосовой почты 6000@default. Сам ящик голосовой почты мы настроим в voicemail.conf.

; /etc/asterisk/voicemail.conf

; В этом разделе содержатся глобальные опции голосовой почты.
[general]
; Аудиоформаты для хранения файлов голосовой почты.
format=wav49|gsm|wav

; "From:" адрес, используемый для отправки писем голосовой почты.
serveremail=asterisk-noreply@example.com

; Нужно ли прикреплять аудиофайл голосовой почты к почтовым уведомлениям.
attach=yes

; Максимальное количество сообщений на один почтовый ящик.
maxmsg=100

; Максимальная длина сообщения голосовой почты в секундах.
maxsecs=300

; Максимальная длина приветствия голосовой почты в секундах.
maxgreet=60

; Количество миллисекунд для перемотки вперёд/назад при воспроизведении сообщения.
skipms=3000

; Количество секунд тишины, после которых запись голосовой почты завершается.
maxsilence=10

; Пороговое значение тишины (что мы считаем тишиной: чем ниже значение, тем выше чувствительность).
silencethreshold=128

; Максимальное количество ошибочных попыток логина.
maxlogins=3

; Шаблон электронного письма для уведомлений о голосовой почте.
emailsubject=New voicemail ${VM_MSGNUM} in mailbox ${VM_MAILBOX}
emailbody=Hi ${VM_NAME},\n\nYou have a new voicemail in mailbox ${VM_MAILBOX}.\n\nFrom: ${VM_CALLERID}\nDate: ${VM_DATE}\nDuration: ${VM_DUR}\nMessage Number: ${VM_MSGNUM}
emaildateformat=%A, %B %d, %Y at %r

; Часовой пояс по умолчанию для почтовых ящиков (определённый в разделе zonemessages ниже).
tz=myzone

; Локаль для генерации строк даты/времени.
locale=en_US.UTF-8

; Минимальная длина пароля голосовой почты.
minpassword=4


; В этом разделе указываются дополнительные определения часовых поясов.
[zonemessages]
myzone=America/New_York|'vm-received' Q 'digits/at' IMp


;;;;;;;;;;;;;;;;;
; Ящики голосовой почты
;;;;;;;;;;;;;;;;;

; Каждый из представленных ниже разделов описывает "контекст" голосовой почты, то есть
; набор ящиков голосовой почты. Если вам не нужно несколько контекстов, можно указать
; "default".
;
; Используется следующий формат:
;   VOICEMAIL_NUMBER => INITIAL_PASSWORD,REAL_NAME,EMAIL_ADDRESS,,,
;
; Последние три поля для простых конфигураций неактуальны.

[default]
6000 => 1234,John Doe,johndoe@example.com,,,

Очереди

Когда на телефонный номер звонят, нам нужно, чтобы все телефоны звонили одновременно (как в обычной телефонной сети). Это поведение можно имитировать в Asterisk при помощи очереди ringall.

Очереди настраиваются в queues.conf.

; /etc/asterisk/queues.conf

; В этом разделе находятся глобальные настройки очередей.
[general]
; Сохраняем динамические списки участников в astdb.
persistentmembers = yes

; Опции для более интуитивно понятного поведения очередей.
autofill                = yes
monitor-type            = MixMonitor
shared_lastcall         = yes
log_membername_as_agent = yes

;;;;;;;;;;;;;;;;;;;
; Определения очередей
;;;;;;;;;;;;;;;;;;;

; Очередь "home-phones" будет использоваться для входящих вызовов по домашней телефонной линии.
[home-phones]
; При каждом входящем вызове звоним на всех участников очереди.
strategy = ringall

; Максимальное количество секунд для ожидания звонящего в очереди.
timeout = 30

; Не сообщать об ожидаемом времени удержания и т.п.
announce-frequency          = 0
announce-holdtime           = no
announce-position           = no
periodic-announce-frequency = 0

; Разрешать звонить, даже если отсутствуют участники очереди.
joinempty      = yes
leavewhenempty = no

; Звонить на телефоны-участники, даже если они уже находятся в другом вызове.
ringinuse = yes

; Участники очереди
;
; Каждый участник задаётся в следующем формате:
;  member => INTERFACE,PENALTY,FRIENDLY_NAME,PRESENCE_INTERFACE
;
; Значение "penalty" в нашем сценарии неактуально.
; В случае PJSIP интерфейс BLF/Presence идентичен стандартному интерфейсу.
member => PJSIP/6001,0,Living Room,PJSIP/6001
member => PJSIP/6002,0,Kitchen,PJSIP/6002
member => PJSIP/6003,0,Smartphone,PJSIP/6003

Dialplan

Итак, мы настроили апстрим-магистрали SIP (trunk), создали аккаунты SIP для локальных добавочных номеров и даже определили очередь для входящих вызовов. Осталось лишь объединить всё это в dialplan!

Dialplan конфигурируется в extensions.conf. Это самый малопонятный аспект Asterisk. Скорее всего, поначалу его синтаксис будет казаться вам непонятным.

Следует помнить, что в dialplan всё считается приложением. Завершение вызова выполняется приложением Hangup(), а подсказка голосовой почты — приложением Voicemail(). Набор телефонного номера выполняется приложением Dial(). Каждое приложение может принимать один или несколько аргументов, разделённых запятыми.

Теперь перейдём к синтаксису. Каждый контекст dialplan помечается квадратными скобками. Каждая строка внутри контекста называется extension (что сбивает с толку) и имеет следующий формат:

[context-name]
exten => NAME,PRIORITY,APPLICATION()
exten => NAME,PRIORITY,APPLICATION()
; и т.д.

name — это номер (или паттерн) добавочного номера.

priority определяет порядок, в котором должен выполняться этап.

application выполняет какое-то действие с вызовом.

Простое определение контекста может выглядеть примерно так:

[from-home]
; ${EXTEN} - это макрос, разворачиваемый в текущий набранный номер.
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
exten => _6XXX,2,Hangup()

Этот раздел dialplan позволяет внутренним телефонам звонить друг на друга. _6XXX — это паттерн добавочного номера. Когда устройство в контексте from-home набирает четырёхзначный добавочный номер, начинающийся с 6, Asterisk начинает звонить на соответствующий аккаунт PJSIP.

Так как повторять один и тот же добавочный номер утомительно, Asterisk предоставляет синтаксический сахар. Точно такой же dialplan можно написать и следующим образом:

[from-home]
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
 same => n,Hangup()

Ниже показана полный dialplan Asterisk для нашего примера VoIP-сети. Он поддерживает следующие функции:

  • Внутренние телефоны могут звонить друг на друга по четырёхзначному добавочному номеру.

  • Внутренние телефоны могут звонить на PSTN по стандартным десятизначным номерам.

  • Внутренние телефоны могут получать доступ к голосовой почте при наборе *99.

  • Внутренние телефоны могут показывать статус BLF/line всех остальных телефонов.

  • Звонок при входящих вызовах из PSTN будет звучать на всех внутренних телефонах. Если никто не ответит в течение 25 секунд, будет выполняться перенаправление на голосовую почту.

  • Если телефоны поддерживают заголовок автоматического ответа, можно инициировать интерком всего дома, набрав 6000.

Для объяснения этого запутанного синтаксиса я снабжу код большим количеством комментариев. Надеюсь, этого будет достаточно для того, чтобы вы могли создавать собственные dialplan!

; /etc/asterisk/extensions.conf

; Помните, что имена контекстов для каждого аккаунта SIP задаются в pjsip_wizard.conf.

; Для начала добавим защиту от неправильного использования встроенных контекстов.
[public]
exten => _X.,1,Hangup(3)
[default]
exten => _X.,1,Hangup(3)


; Далее идут глобальные переменные. Некоторые вам придётся изменить.
[globals]
; Ваш локальный телефонный код.
MY_AREA_CODE = 555

; Ваше реальное имя и публичный телефонный номер. Будут использоваться для исходящих вызовов
; в PSTN.
MY_CALLER_ID = John Doe <+5555555555>

; Этот номер при наборе с локального номера позволяет получить доступ к меню голосовой почты.
VOICEMAIL_NUMBER = *99

; Адрес голосовой почты (конфигурируемый в voicemail.conf)
VOICEMAIL_BOX = 6000@default

; Длительность вызова в секундах, после которой выполняется перенаправление в голосовую почту.
VOICEMAIL_RING_TIMEOUT = 25

; Имя очереди 'ringall' для локальных телефонов.
HOME_QUEUE = home-phones

; Номер для набора локального интеркома.
INTERCOM   = 6000

; Паттерн набора для локальных добавочных номеров.
LOCAL_EXTS = _6XXX


; Бойлерплейт для включения подписок BLF/presence. Имейте в виду, что имя контекста соответствует
; значению "endpoint/subscribe_context" в pjsip_wizard.conf.
[subscribe]
exten => _XXXX,hint,PJSIP/${EXTEN}


; Этот контекст обрабатывает входящие вызовы от поставщика SIP trunk. Каждый вызов
; помещается в очередь ringall. Если ответа нет, вызывающий перенаправляется
; в голосовую почту.
[from-pstn]
exten => _X.,1,Queue(${HOME_QUEUE},nr,,,${VOICEMAIL_RING_TIMEOUT})
 same => n,Answer(500)
 same => n,Voicemail(${VOICEMAIL_BOX},su)
 same => n,Hangup()


; Эта функция (или "gosub" в терминологии Asterisk) задаёт заголовок SIP "auto answer"
; для исходящего вызова.
[gosub-intercom]
exten => s,1,Set(PJSIP_HEADER(add,Alert-Info)=auto answer)
 same => n,Return()


; Этот контекст обрабатывает входящие вызовы с локальных телефонов.
[from-home]
; При наборе номера INTERCOM все участники очереди "home-phones"
; вызываются в конференц-вызов.
exten => ${INTERCOM},1,Set(CALLERID(all)=Intercom <${EXTEN}>
 same => n,Page(${STRREPLACE(QUEUE_MEMBER_LIST(${HOME_QUEUE}),",","&")},db(gosub-intercom^s^1),10)
 same => n,Hangup()

; В случае вызова с локального на локальный телефон звонок будет звучать бесконечно.
exten => ${LOCAL_EXTS},1,Dial(PJSIP/${EXTEN})
 same => n,Hangup()

; При наборе номера голосовой почты перенаправляем вызывающего в меню голосовой почты.
exten => ${VOICEMAIL_NUMBER},1,Answer(500)
 same => n,VoiceMailMain(${VOICEMAIL_BOX},s)
 same => n,Hangup()

; Указанные ниже добавочные номера используются для вызовов в PSTN через SIP trunk
; с использованием персонализированной строки caller ID.
;
; Помните, что в pjsip_wizard.conf мы назвали наш SIP trunk "voipms".
;
; N соответствует любой цифре 2-9
; X соответствует любой цифре 1-9

; Номера в формате +1xxxxxxxxxx набираются без изменений.
exten => _+1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/${EXTEN}@voipms)
 same => n,Hangup()

; В начало номеров вида 1xxxxxxxxxx добавляется "+".
exten => _1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+${EXTEN}@voipms)
 same => n,Hangup()

; В номера без кода страны добавляется "+1". 
exten => _NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+1${EXTEN}@voipms)
 same => n,Hangup()

; В семизначные номера добавляется локальный телефонный код.
exten => _NXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+1${MY_AREA_CODE}${EXTEN}@voipms)
 same => n,Hangup()

; Трёхзначные номера наподобие 311, 411, 911 (НЕ ПРОТЕСТИРОВАНО!!!) набираются без изменений.
exten => _N11,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/${EXTEN}@voipms)
 same => n,Hangup()

Устранение неполадок

Настроив все конфигурации, попробуем запустить Asterisk:

systemctl restart asterisk

В дистрибутивах на основе RedHat наличие ошибок можно проверить в /var/log/asterisk/messages. Также можно заглянуть в журнал systemd:

journalctl -u asterisk

В реальном времени информацию можно получать из консоли Asterisk. Для проверки можете выполнить под рутом следующую команду:

asterisk -rvvvvv

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

pjsip set logger on

Шаг 4: конфигурирование IP-телефонов

Наконец, настроим VoIP-телефоны, чтобы они подключались к серверу Asterisk, и совершим пару тестовых вызовов! Команды актуальны для телефонов Yealink, но при адаптации их под другие марки телефонов проблем возникнуть не должно.

Аккаунт SIP Account

В браузере перейдите по IP-адресу VoIP-телефона и выполните вход с именем пользователя и паролем по умолчанию (обычно это admin/admin). Здесь обычно есть возможность добавления аккаунта SIP. В интерфейсе Yealink он находится в разделе Account→Register.

Для созданного выше добавочного номера Living Room необходимо указать следующее:

  • Register Name: 6001.

  • Username: 6001.

  • Password: s3cret.

  • Server Host: IP/имя хоста сервера Asterisk.

  • Server Port: 5060.

  • Transport: UDP.

Кодеки

Также следует убедиться, что в устройстве включены соответствующие кодеки. В веб-интерфейсе Yealink их можно найти в разделе Account→Codec. Я рекомендую включать эти кодеки в следующем приоритете:

  • G722 для высококачественных локальных вызовов

  • PCMU (ulaw) для вызовов в PSTN

Если другая сторона не поддерживает G722, Asterisk будет автоматически транскодировать в ulaw. Можно поэкспериментировать с ещё более высококачественными кодеками наподобие Opus, но по моему опыту, они плохо поддерживаются и их сложно транскодировать.

Диапазон портов RTP

Для снижения нагрузки на сервер Asterisk можно сделать так, чтобы VoIP-телефон отправлял и получал аудиопотоки непосредственно от другой стороны. Для этого нужно сконфигурировать маршрутизатор/файрволл для перенаправления всего входящего трафика RTP устройства.

Сначала присвойте устройству статический IP в локальной сети. Затем сконфигурируйте в маршрутизаторе перенаправление портов всего трафика UDP на выбранный для этого IP диапазон портов RTP.

В веб-интерфейсе Yealink диапазон статических портов RTP можно настроить в разделе Network→Advanced→Local RTP Port.

Если вы не будете всего этого делать, то убедитесь, что в pjsip_wizard.conf для аккаунта SIP endpoint/direct_media имеет значение no!

Номер голосовой почты

Также стоит сконфигурировать номер, который должен вызывать телефон при нажатии на кнопку голосовой почты. В веб-интерфейсе Yealink необходимо указать *99 в разделе Account→Advanced→Voice Mail (или другой номер, который вы выбрали выше для VOICEMAIL_NUMBER).

Busy Lamp Field (BLF)

Можно настроить включение светодиода телефона, когда на текущей линии выполняется вызов. В веб-интерфейсе Yealink это можно настроить в разделе Dsskey→Line Key.

Для каждой нужной вам линии установите для Type значение BLF, а для Value — добавочный номер другой стороны.

Интерком

Для работы описанной в представленном выше dialplan функции интеркома нужно сделать так, чтобы в телефоне была включена возможность автоматического ответа. В веб-интерфейсе Yealink она включается в разделе Features→Intercom→Intercom Allow выбором значения on.

VoIP на Android: Linphone

Раньше в Android был встроенный клиент SIP. К сожалению, в Android 12 его удалили.

Попробовав все SIP-приложения из F-Droid, я могу порекомендовать только Linphone. Оно работает с Bluetooth-устройствами, имеет интуитивно понятный интерфейс и стабильно доставляет вызовы.

Чтобы Android не закрывал это приложение, необходимо отключить оптимизации аккумулятора. Зайдите в раздел Settings→Apps→See All Apps→Linphone→App Battery Usage и выберите Unrestricted.

При конфигурировании аккаунта SIP в Linphone используйте следующие настройки:

  • Username: 6003 (или другой выбранный для мобильного устройства добавочный номер).

  • Password: выбранный вами пароль аккаунта SIP.

  • Domain: IP/имя хоста сервера Asterisk.

  • Transport: TCP.

  • Expire: 3600.

  • Apply prefix for outgoing calls: включено.

Для мобильного устройства определённо стоит использовать транспорт TCP. Радиопередатчики смартфонов крайне эффективно справляются с поддержанием открытого TCP-соединения, а TCP будет стабильно доставлять пакеты SIP даже при плохом сигнале сотовой сети.

При значении Expire, равном 3600, телефон будет просыпаться каждый час для повторной установки SIP-соединения. У меня никогда не было никаких проблем с соединением при использовании транспорта TCP, но если вы параноик, можете выбрать значение пониже.

Также полезными мне показались следующие параметры Linphone:

  • Settings→Audio→Echo cancellation: отключено (у большинства телефонов есть аппаратная поддержка компенсации эха).

  • Settings→Audio→Adaptive rate control: включено.

  • Settings→Audio→Codec bitrate limit: 128 kbits/s.

  • Settings→Audio→Codecs: включены PCMU и G722.

  • Settings→Call→Improve interactions with bluetooth devices: включено.

  • Settings→Call→Voice mail URI: *99.

  • Settings→Advanced→Start at boot time: включено.

Возможно, вам захочется отключить постоянно висящий значок уведомления, когда запущено Linphone. Не делайте этого! Когда я пытался скрывать этот значок, многие вызовы оказывались пропущенными, а Asterisk могла сообщать, что устройство находится офлайн в течение нескольких минут. Если я оставлял значок в области уведомлений видимым, никаких проблем не возникало.

В заключение

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

Если вы решите организовать самохостинг инфраструктуры VoIP, то определённо стоит настроить в маршрутизаторе или файрволле QoS так, чтобы приоритет отдавался голосовому трафику. В противном случае, при высокой сетевой нагрузке будут возникать задержки и снижаться качество аудио.

Также можно попробовать изучить вопрос шифрования вызовов при помощи STRP и SIP TLS. Лично я этого не делаю, потому что после подключения к PSTN голосовые вызовы всё равно становятся абсолютно прозрачны.

Комментарии (0)


  1. mlnw
    15.09.2025 13:10

    Вот бы мне такую статью в 2007м.