Настройка динамической маршрутизации вызовов (LCR) в диалплане Asterisk
- Неэффективные расходы на исходящую связь из-за отправки вызовов через дорогих транзитных операторов.
- Ошибки дозвона при падении основного SIP-транка (отсутствие резервного переключения).
- Сложности при маршрутизации международных вызовов по географическим префиксам и кодам мобильных операторов (MNP).
1. Принцип организации LCR (Least Cost Routing)
LCR в Asterisk анализирует префикс вызываемого номера (E.164), статус доступности провайдеров (SIP/PJSIP qualify) и себестоимость минуты разговора, после чего направляет вызов в самый экономичный маршрут с автоматическим переходом на резервные транки (Failover) при отказе.
2. Настройка транков в pjsip.conf
[trunk-provider-a]
type=endpoint
context=from-trunk
disallow=all
allow=alaw
allow=ulaw
aors=trunk-provider-a-aor
[trunk-provider-b]
type=endpoint
context=from-trunk
disallow=all
allow=alaw
allow=ulaw
aors=trunk-provider-b-aor3. Конфигурация диалплана с отказоустойчивым LCR
Отредактируйте файл /etc/asterisk/extensions.conf:
[outbound-lcr]
; Маршрутизация мобильных номеров (Дешевле через Provider A)
exten => _89XXXXXXXXX,1,NoOp(=== LCR: Mobile Route for ${EXTEN} ===)
same => n,Set(TARGET_NUM=${EXTEN})
same => n,Gosub(sub-dial-lcr,s,1(trunk-provider-a,trunk-provider-b,${TARGET_NUM}))
; Маршрутизация городских номеров (Дешевле через Provider B)
exten => _8[348]XXXXXXXX,1,NoOp(=== LCR: PSTN Route for ${EXTEN} ===)
same => n,Set(TARGET_NUM=${EXTEN})
same => n,Gosub(sub-dial-lcr,s,1(trunk-provider-b,trunk-provider-a,${TARGET_NUM}))
; Подпрограмма дозвона с Failover
[sub-dial-lcr]
exten => s,1,NoOp(Dialing via Primary: ${ARG1})
same => n,Dial(PJSIP/${ARG3}@${ARG1},30,T)
same => n,NoOp(Primary Trunk Status: ${DIALSTATUS} / HangupCause: ${HANGUPCAUSE})
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?success)
same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
; Если транк недоступен, звоним через резервный транк ARG2
same => n,NoOp(Failing over to Secondary: ${ARG2})
same => n,Dial(PJSIP/${ARG3}@${ARG2},30,T)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?success)
same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
same => n,Playtones(congestion)
same => n,Congestion(10)
same => n(busy),Playtones(busy)
same => n,Busy(5)
same => n(success),Hangup()4. Интеграция LCR с базами данных через func_odbc
Для динамического извлечения стоимости направлений настройте /etc/asterisk/func_odbc.conf:
[LCR_ROUTE]
dsn=asterisk-db
readsql=SELECT trunk_name FROM lcr_routes WHERE '${ARG1}' LIKE CONCAT(prefix, '%') ORDER BY length(prefix) DESC, cost ASC LIMIT 1Вызов функции в диалплане:
exten => _X.,1,Set(BEST_TRUNK=${ODBC_LCR_ROUTE(${EXTEN})})
same => n,Dial(PJSIP/${EXTEN}@${BEST_TRUNK},30) Частые вопросы (FAQ)
Как отличить реальную занятость абонента (BUSY) от сбоя сети у оператора (CHANUNAVAIL)?
Переменная ${DIALSTATUS} принимает значения CHANUNAVAIL, CONGESTION, BUSY, NOANSWER. В подпрограмме LCR переключение на резервный транк должно происходить только при CHANUNAVAIL и CONGESTION, чтобы не беспокоить занятого клиента повторным дозвоном.
Как LCR обрабатывает перенесенные мобильные номера (MNP)?
Для точного учета MNP перед маршрутизацией выполняется HTTP/REST или SQL-запрос к локальной/внешней базе перенесенных номеров для определения фактического префикса оператора (Routing Number/RN).
Можно ли балансировать трафик между одинаковыми транками по весам?
Да, с помощью функции RAND() в диалплане (например, GotoIf($[${RAND(1,100)} <= 70]?routeA:routeB)) или через модуль распределения очередей и групп каналов.
Как избежать зацикливания звонка при отказе всех провайдеров?
Всегда ограничивайте глубину переходов жестким списком аргументов в подпрограмме Gosub и завершайте обработку отправкой приложения Congestion() или Hangup().