[*nix, Asterisk] Как мы переводили MIKOPBX с chan_sip на PJSIP
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ПредысторияМатериал изначально готовился как доклад для asterconf 2020. Теперь постараюсь описать все более подробно в этой статье. MIKOPBX - это бесплатная АТС с открытым исходным кодом на базе Asterisk 16. Год назад мы взялись за переход на PJSIP. Основные причины:
- PJSIP поддерживает "множественную регистрацию". На одном аккаунте можно без проблем регистрировать несколько конечных UAC
- Корректная работа входящей маршрутизации при настройке регистрации нескольких учетных записей провайдера на одном адресе (IP+PORT)
- PJSIP более гибок в настройке
- chan_sip не развивается и объявлен deprecated в Asterisk 17
Далее опишу с какими сложностями мы столкнулись и какие выгоды получили.Основная причина - необходимость в поддержке "множественной регистрации". Крайне удобно подключить к аккаунту несколько софтфонов / телефонов и не беспокоится, входящий вызов поступит где бы ты не находился. Лично у меня подключены следующие устройства:
- Аппаратный телефон на рабочем столе в офисе
- Софтфон на ноутбуке
- Софтфон на смартфоне
При поступлении входящего звонка на добавочный, все устройства звонят одновременно. С чего начать?В нашем случае был готовый файл конфигурации sip.conf. Стало интересно, возможно ли как то конвертировать старый конфиг в новый формат (структура pjsip.conf отличается значительно). Готовый скрипт был найден в исходниках asterisk. Найти можно по пути:contrib/scripts/sip_to_pjsip/sip_to_pjsip.pyИз встроенной справки:
Usage: sip_to_pjsip.py [options] [input-file [output-file]]
Converts the chan_sip configuration input-file to the chan_pjsip output-file.
The input-file defaults to 'sip.conf'.
The output-file defaults to 'pjsip.conf'.
Скрипт позволяет получить рабочий конфиг и начать его тестировать и дорабатывать напильником. Настройка множественной регистрацииПосле конвертации конфигурационного фала потребовалось увеличить количество контактов, которые могут подключаться к учетной записи (далее endpoint).Каждую входящую регистрацию Asterisk рассматривает как contact. Параметр "max_contacts" позволяет ограничить количество устройств, которые могут подключиться к endpoint.
;pjsip.conf
[226]
type = aor
max_contacts = 5
Количество подключенных контактов можно посмотреть в CLI консоли Asterisk:
mikopbx*CLI> pjsip show contacts
Contact: <Aor/ContactUri..............................> <Hash....> <Status> <RTT(ms)..>
==========================================================================================
Contact: 201/sip:201@172.16.156.1:60616;ob 418d36496b Avail 3.793
Contact: 201/sip:201@172.16.156.1:60616;ob ba56853d54 Avail 2.189
Contact: 203/sip:203@172.16.156.1:60616;ob 2cd641799f Avail 0.988
Objects found: 3
Для того, чтобы при входящем звонили сразу все контакты, потребовалось доработать dialplan. Пример c комментариями:
;extensions.conf
[internal-users]
; контекст для набора 3х значных внутренних номеров
; PJSIP_DIAL_CONTACTS - функция возвращает Dial-совместимую строку с контактами
; Контакты разделены символом &
; В качестве параметра функции необходимо передать ID endpoint
exten => _XXX,1,Set(dialContacts=${PJSIP_DIAL_CONTACTS(${EXTEN})})
; Перед Dial обязательно необходимо проверить
; заполнена ли переменная "dialContacts"
; если нет, то на endpoint никто не зарегистрировался
same => n,ExecIf($["${dialContacts}x" != "x"]?Dial(${DC},,Tt))
После правки dialplan началось интересное поведение системы. Наши ожидания не оправдались. Мы предполагали, что при таком звонке, asterisk будет оперировать двумя каналами "Кто звонит" и "Кому звонит". На практике, все оказалось иначе.О природе каналов и их происхожденииКаждый канал SIP и PJSIP непосредственно связан с SIP диалогом "PBX - UAC". Проще говоря один INVITE = один канал вида SIP/104-0000XX. Если к endpoint подключено несколько контактов, то при звонке на внутренний номер INVITE будет отправлен каждому контакту, будет создано несколько каналов. Зная это, можно сделать следующие выводы:
- Чем больше каналов, тем больше событий в AMI
- Каждый канал пройдет определенный для него dialplan
- Каждый канал повлияет на CDR записи
Если кратко подвести итог, то, после включения множественной регистрации, мы видим влияние на все основные модули наших продуктов:
- История звонков на АТС
- Функция записи разговоров
- Работа CTI приложений, завязанных на AMI
Автоподъем. Paging. IntercomЭто крайне интересные функции. Все они завязаны на функцию "Автоответ". Может работать как с настольными телефонами, так и с многими софтфонами. Принцип работы многих UAC схож. Чтобы "поднять трубку" достаточно в INVITE передать дополнительный заголовок. Пример:
Call-Info:\;answer-after=0
В случае с аппаратным телефоном будет включена либо громкая связь, либо произойдет ответ в гарнитуре. При работе с chan_sip при originate достаточно было установить переменную SIPADDHEADER:
Action: Originate
Channel: SIP/104
Context: from-internal
Exten: 74952293042
Priority: 1
Callerid: 104
Variable: SIPADDHEADER="Call-Info:\;answer-after=0"
Работа с этой переменной была описана в chan_sip.с и при звонке заголовок добавлялся автоматически в INVITE. В случае с PJSIP подход отличается. Упрощенный пример extensions.conf:
[internal-users]
exten => 204,1,Dial(${PJSIP_DIAL_CONTACTS(204)},,Ttb(dial_create_chan,s,1)))
[dial_create_chan]
exten => s,1,Set(PJSIP_HEADER(add,Call-Info)=\;answer-after=0)
same => n,return
Опция "b" в команде "Dial" позволяет созданный канал назначения с помощью Gosub направить в дополнительный контекст "dial_create_chan". Только в этом месте есть возможность управлять SIP заголовками ДО отправки INVITE. Интересный вывод: "dial_create_chan" - место в dialplan, где канал еще существует, но НЕ связан с SIP диалогом. Теперь более правильный пример установки заголовка:
[internal-users]
; Получаем контактны:
exten => _XXX,1,Set(dС=${PJSIP_DIAL_CONTACTS(${EXTEN})})
; Считаем количество контактов:
same => n,ExecIf($["${FIELDQTY(dС,&)}"!="1"]?Set(__SIPADDHEADER=${EMPTY}))
same => n,ExecIf($["${dС}x" != "x"]?Dial(${DC},,Ttb(dial_create_chan,s,1)))
[dial_create_chan]
exten => s,1,ExecIf($["${SIPADDHEADER}x" == "x"]?return)
same => n,Set(header=${CUT(SIPADDHEADER,:,1)})
same => n,Set(value=${CUT(SIPADDHEADER,:,2)})
same => n,Set(PJSIP_HEADER(add,${header})=${value})
same => n,Set(__SIPADDHEADER=${EMPTY})
same => n,return
С помощью функции "FIELDQTY" мы анализируем количество контактов, подключенных к endpoint. Если контактов несколько, то функцию лучше отключить, ведь сложно предугадать, на каком из телефонов сработает ответ на вызов. С помощью функции "CUT" происходит разбор строки "SIPADDHEADER", выделяем имя заголовка и его значение. Обязательно, после PJSIP_HEADER очищаем значение переменной SIPADDHEADER. Это страховка от случайного срабатывания "ответа" на вызов при переадресациях. Получение значения UserAgentДля выборка корректного SIP заголовка необходимо понимать какое конечное устройство подключено к endpoint. В случае с pjsip ситуация несколько изменилась. Пример:
[get-user-agent]
exten => 300,1,NoOp(--- Incoming call ---)
same => n,Set(vContact=${PJSIP_AOR(300,contact)})
same => n,Set(vUserAgent=${PJSIP_CONTACT(${vContact},user_agent)})
same => n,NoOp(--- ${vContact} & ${vUserAgent} ---)
... ... ...
same => n,Hangup()
Пример в одну строчку для AOR с ID 300. Для упрощения ID endpoint = ID AOR и = EXTEN:
; ${PJSIP_CONTACT(${PJSIP_AOR(${EXTEN},contact)},user_agent)}
В функцию "PJSIP_AOR" передаем ID AOR, и в качестве опции указываем, что вернуть нам следует поле "contact". В функцию "PJSIP_CONTACT" передаем полученный контакт, и в качестве опции указываем, что вернуть следует поле "user_agent".Обратите внимание, PJSIP_AOR(300,contact) вернет ID контакта, но это не тоже самое, что можно увидеть в CLI.Пример результата PJSIP_AOR:
201;@e758f5661420b391e239386a94edbefe
Пример вывода в CLI:
pjsip show contacts 201/sip:201@172.16.156.1:57130;ob
Contact: 201/sip:201@172.16.156.1:57130;ob
Исходящая регистрацияСогласно документации Asterisk, разработчики выделяют два основных вида проблем регистрации:Временные (temporary) проблемы
- No Response
- 408 Request Timeout
- 500 Internal Server Error
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Server Timeout
- Некоторые 6xx ответы
Постоянные (Permanent) проблемы
- 401 Unauthorized
- 403 Forbidden
- 407 Proxy Authentication Required
- Прочие 4xx, 5xx, 6xx ошибки
В pjsip.conf при настройке исходящей регистрации обязательно необходимо описать опции для повторной попытки регистрации:
[74952293042]
type = registration
; Временные неудачи
; Интервал для повторных попыток регистрации
retry_interval = 30
; Максимальное количество попыток
max_retries = 100
; "Постоянные" неудачи
; Интервал используется при получении 403 Forbidden ответа.
forbidden_retry_interval = 300
; Интервал используется при получении Fatal ответов (non-temporary 4xx, 5xx, 6xx)
fatal_retry_interval = 300
Если sip_to_pjsip.py для конвертации конфигурации, то эти опции придется описать вручную. Идентификация провайдераДля рада провайдеров телефонии может наблюдаться следующая картина:
- Успешно проходит регистрация по адресу sip.test.ru
- Допустип sip.test.ru резолвится в 10.10.10.10
- Входящие вызовы поступают с 11.11.11.11
- Входящие могут поступать и с 10.10.10.10
Вызовы могут не пройти авторизацию и будут завершены. В PJSIP есть возможность идентификации по IP адресу:
[74952293042]
type = identify
; ... ... ...
match=sip.test.ru,185.45.152.0/24,185.45.155.0/24;
; ... ... ...
В параметре "match", через запятую, можно описать все IP адреса провайдера. В этом случае входящий будет корректно сопоставлен с нужным endpoint.Кроме того, следует обратить внимание на опцию "endpoint_identifier_order". Значение по умолчанию:
endpoint_identifier_order=ip,username,anonymous
Если у вас есть несколько учетных записей одного провайдера, которые регистрируются на одном и том же адресе IP:PORT, то имеет смысл поменять порядок идентификации:
endpoint_identifier_order=username,ip,anonymous
Пример, есть три транка:
- 99999 - подключается к 10.10.10.10:5060
- 88888 - подключается к 10.10.10.10:5060
- 77777 - подключается к 10.10.10.10:5060
Если не настроить "endpoint_identifier_order", то:
- все входящие будут направлены в контекст произвольного endpoint (идентификация пройдет по адресу IP:PORT), к примеру в контекст endpoint "99999" .
- канал, созданный при входящем будет всегда ассоциироваться с одним и тем же endpoint, к примеру PJSIP/99999-0000XXX, на какой внешний номер бы ни звонил клиент
Входящие без регистрации SIP URIДля ряда случаев удобно направлять входящие на АТС без регистрации.Обязательно следует подгрузить модуль "res_pjsip_endpoint_identifier_anonymous.so". Пример настройки pjsip.conf
[anonymous]
type = endpoint
allow = alaw
timers = no
context = public-direct-dial
Пример extensions.conf
[public-direct-dial]
exten => 74952293042,NoOp(--- Incoming call to ${EXTEN} ---)
same => n,Dial(PJSIP/204,,TKg));
same => n,Hangup()
Контекст public-direct-dial должен быть изолирован от исходящих dialplan. В качестве exten описываются все DID номера и логика маршрутизации. Подведу итоги
- Переход на PJSIP состоялся. С chan_pjsip АТС работает стабильно, надежно
- Нами был получен огромный опыт работы с PJSIP
- PJSIP более гибок в настройке, предоставляет больше возможностей
- Функция множественной регистрации крайне удобна и порой незаменима
- chan_pjsip живой, активно развивается и поддерживается сообществом
Из минусов перехода на chan_pjsip стоит отметить:
- Требуется модернизация dialplan
- Изменение поведения AMI, что отражается на CTI клиентах
- Меняется поведение CDR, требуется доработка легирования истории звонков
- chan_pjsip активно развивается, в свежих релизах asterisk встречаются грубые ошибки. не стоит гнаться за новыми версиями, лучше выждать появления "certified" версий
Полезные ссылки
- Ссылка на проект MIKOPBX
- wiki.asterisk.org
- Identifying an endpoint in PJSIP
- SIP Resource using PJProject
- Dialing PJSIP Channels
===========
Источник:
habr.com
===========
Похожие новости:
- [*nix, Open source] FOSS News №35 – дайджест новостей и других материалов о свободном и открытом ПО за 21-27 сентября 2020 года
- [Python, Asterisk, Социальные сети и сообщества, Flask] Реализация аудиоконференций в Telegram + Asterisk
- [Asterisk, IT-инфраструктура, Сетевое оборудование, Сетевые технологии] I want to break free. Обзор беспроводной DECT гарнитуры Snom A170
- [*nix, Open source] FOSS News №34 – дайджест новостей свободного и открытого ПО за 14-20 сентября 2020 года
- [*nix, IT-инфраструктура, Виртуализация, Облачные вычисления] Opennebula. Короткие записки
- [*nix, DevOps, Kubernetes] Краткое руководство по разработке чартов в Helm (перевод)
- [CRM-системы, Сотовая связь] Переезд производственной компании с «железок» на виртуальную АТС
- [*nix, Open source] FOSS News №33 – дайджест новостей свободного и открытого ПО за 7-13 сентября 2020 года
- [*nix, C, Разработка под Linux] поддерживаю драйвер tp-link t4u для linux
- [*nix, C] Создаём приложение на gtk
Теги для поиска: #_*nix, #_asterisk, #_pbx, #_asterisk, #_*nix, #_asterisk
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:15
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ПредысторияМатериал изначально готовился как доклад для asterconf 2020. Теперь постараюсь описать все более подробно в этой статье. MIKOPBX - это бесплатная АТС с открытым исходным кодом на базе Asterisk 16. Год назад мы взялись за переход на PJSIP. Основные причины:
Usage: sip_to_pjsip.py [options] [input-file [output-file]]
Converts the chan_sip configuration input-file to the chan_pjsip output-file. The input-file defaults to 'sip.conf'. The output-file defaults to 'pjsip.conf'. ;pjsip.conf
[226] type = aor max_contacts = 5 mikopbx*CLI> pjsip show contacts
Contact: <Aor/ContactUri..............................> <Hash....> <Status> <RTT(ms)..> ========================================================================================== Contact: 201/sip:201@172.16.156.1:60616;ob 418d36496b Avail 3.793 Contact: 201/sip:201@172.16.156.1:60616;ob ba56853d54 Avail 2.189 Contact: 203/sip:203@172.16.156.1:60616;ob 2cd641799f Avail 0.988 Objects found: 3 ;extensions.conf
[internal-users] ; контекст для набора 3х значных внутренних номеров ; PJSIP_DIAL_CONTACTS - функция возвращает Dial-совместимую строку с контактами ; Контакты разделены символом & ; В качестве параметра функции необходимо передать ID endpoint exten => _XXX,1,Set(dialContacts=${PJSIP_DIAL_CONTACTS(${EXTEN})}) ; Перед Dial обязательно необходимо проверить ; заполнена ли переменная "dialContacts" ; если нет, то на endpoint никто не зарегистрировался same => n,ExecIf($["${dialContacts}x" != "x"]?Dial(${DC},,Tt))
Call-Info:\;answer-after=0
Action: Originate
Channel: SIP/104 Context: from-internal Exten: 74952293042 Priority: 1 Callerid: 104 Variable: SIPADDHEADER="Call-Info:\;answer-after=0" [internal-users]
exten => 204,1,Dial(${PJSIP_DIAL_CONTACTS(204)},,Ttb(dial_create_chan,s,1))) [dial_create_chan] exten => s,1,Set(PJSIP_HEADER(add,Call-Info)=\;answer-after=0) same => n,return [internal-users]
; Получаем контактны: exten => _XXX,1,Set(dС=${PJSIP_DIAL_CONTACTS(${EXTEN})}) ; Считаем количество контактов: same => n,ExecIf($["${FIELDQTY(dС,&)}"!="1"]?Set(__SIPADDHEADER=${EMPTY})) same => n,ExecIf($["${dС}x" != "x"]?Dial(${DC},,Ttb(dial_create_chan,s,1))) [dial_create_chan] exten => s,1,ExecIf($["${SIPADDHEADER}x" == "x"]?return) same => n,Set(header=${CUT(SIPADDHEADER,:,1)}) same => n,Set(value=${CUT(SIPADDHEADER,:,2)}) same => n,Set(PJSIP_HEADER(add,${header})=${value}) same => n,Set(__SIPADDHEADER=${EMPTY}) same => n,return [get-user-agent]
exten => 300,1,NoOp(--- Incoming call ---) same => n,Set(vContact=${PJSIP_AOR(300,contact)}) same => n,Set(vUserAgent=${PJSIP_CONTACT(${vContact},user_agent)}) same => n,NoOp(--- ${vContact} & ${vUserAgent} ---) ... ... ... same => n,Hangup() ; ${PJSIP_CONTACT(${PJSIP_AOR(${EXTEN},contact)},user_agent)}
201;@e758f5661420b391e239386a94edbefe
pjsip show contacts 201/sip:201@172.16.156.1:57130;ob
Contact: 201/sip:201@172.16.156.1:57130;ob
[74952293042]
type = registration ; Временные неудачи ; Интервал для повторных попыток регистрации retry_interval = 30 ; Максимальное количество попыток max_retries = 100 ; "Постоянные" неудачи ; Интервал используется при получении 403 Forbidden ответа. forbidden_retry_interval = 300 ; Интервал используется при получении Fatal ответов (non-temporary 4xx, 5xx, 6xx) fatal_retry_interval = 300
[74952293042]
type = identify ; ... ... ... match=sip.test.ru,185.45.152.0/24,185.45.155.0/24; ; ... ... ... endpoint_identifier_order=ip,username,anonymous
endpoint_identifier_order=username,ip,anonymous
[anonymous]
type = endpoint allow = alaw timers = no context = public-direct-dial [public-direct-dial]
exten => 74952293042,NoOp(--- Incoming call to ${EXTEN} ---) same => n,Dial(PJSIP/204,,TKg)); same => n,Hangup()
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:15
Часовой пояс: UTC + 5