FreePBX и динамическая генерация исходящих маршрутов по регионам с использованием базы DEF кодов.

Сегодня у нас под скальпелем оказался asterisk 11.16.0 обёрнутый во FreePBX Distro 12.0.38. У заказчика имеется несколько линий от одного из SIP провайдеров, распределённые по регионам. Логично было бы для снижения затрат на телефонию осуществлять звонки с линии, принадлежащей тому же региону, что и вызываемый абонент. Способ, кстати, подойдёт и в том случае, если вы используете свои физические линии (например, несколько GSM линий с разными ценами за разные направления или для звонков на номера разных сотовых операторов своего региона). И в случае с городскими линиями всё просто — телефонные коды регионов России довольно стабильны и меняются(добавляются) редко. Но что делать с номерами, принадлежащими операторам сотовых сетей? Там один и тот же DEF код может принадлежать разным операторам и делиться по диапазонам номеров. Скажу сразу, в этой статье я не буду рассматривать настройку asterisk для работы с GSM модемом, если интересно — напишу позже.

Поиск решения привёл меня на Хабр, где добрый человек поделился своими соображениями на эту тему и скриптом, эту идею осуществляющим. За что ему большое человеческое спасибо. В двух словах — Федеральное Агентство Связи на своём официальном сайте публикует обновляемый файл, где указана принадлежность DEF кодов и номеров к операторам по регионам. Оригинальный скрипт брал определённый регион и правил маршрут в соответствии с полученными данными. Но у меня несколько направлений и, соответственно, был выбор — запускать несколько скриптов (для каждого из регионов по скрипту) или заняться допилингом. Конечно, я выбрал второй вариант. Был взят за основу скрипт из статьи и несколько переписан для соответствия моим требованиям и большей универсальности. И теперь мне хочется поделиться результатом.

Отличия от оригинального скрипта, доступного по ссылке (ну, кроме исправления опечаток в именах файлов и огрех с использованием статичного имени файла с середины скрипта, при том, что до этого он вызывался через переменную 🙂 ) :

1. Из конфига FreePBX мы вытаскиваем имя и пароль пользователя базы данных (стр. 4..7). Оригинальный скрипт рассчитан на полностью дефолтную установку FreePBX, в которой рутовый пароль к MySQL пустой. Меня, например, не устраивает пустой пароль root, даже когда подключение ограничено localhost-ом и ssh нет ни у кого, кроме меня. Соответственно, в моём случае, оригинальный скрипт не смог бы писать/читать базу. А на тот случай, если мы поменяем пароль для БД — лучше его забирать из конфига, где он однозначно актуален, а не менять его вручную во многих местах.

2. Доп «конфиг. файл». В нём через запятую мы перечисляем имя маршрута во FreePBX и имя региона, ему соответствующего (точные имена регионов смотрим на сайте Россвязи, в файле). Соответственно, за одно выполнение скрипта мы можем править сколько угодно маршрутов. Пример файла:

out-SPb-cell,Ленинградская область
out-Msk-cell,Московская область
_

Ограничения, налагаемые на конфиг — отсутствие пробелов вокруг запятой и обязательное наличие пустой строки в конце.

3. Переменная LOCCODE (строка 13). В моём случае сотрудники заказчика привыкли набирать номера как придётся — через 7 или через 8. Я прекрасно знаю, что выход на «межгород» у нас осуществляется через восьмёрку или через +, а 7 — код России, и набор номера «79211234567» — жуть и абсурд, но… Но клиент всегда прав :)) . Поэтому появилась эта переменная, формат её — стандартный для asterisk. В моём случае — [78], что означает 7 или 8.

4. Теперь нам не надо знать ID маршрута во FreePBX. В строках 31 и 32 мы выдёргиваем его из базы данных в соответствии с нашим конфигом.

5. Изменён способ применения изменений. В моём случае метод автора с curl-ами не сработал, но прекрасно работает способ из строки 141.

Теперь нам надо добавить запуск скрипта в cron (лучше через полчасика после создания бэкапа) и наслаждаться жизнью. В случае добавления нового направления звонков у заказчика мне достаточно дописать создать и настроить маршрут и дописать одну строчку в конфиг, всё остальное произойдёт само.

Сам скрипт:

#!/bin/bash

#Данные для подключения к БД, вытаскиваем из конфига FreePBX
tmp_res=$(cat /etc/amportal.conf | grep 'AMPDBUSER')
BD_USER=${tmp_res#*=}
tmp_res=$(cat /etc/amportal.conf | grep 'AMPDBPASS')
BD_PASS=${tmp_res#*=}
#Конфиг-файл
CONFFILE="./def_trunks.conf"
#Временный файл
TMPFILE="destinations"
#Выход на межгород
LOCCODE="[78]"
#файл DEF-кодов
DOWNFILE='http://www.rossvyaz.ru/docs/num/DEF-9x.html';
#рабочая папка
TMPDIR='/tmp/'
#файл, где сохраним csv формат кодов
FILENAME='codes'
#файл, где сохраним паттерны
PATTFILE='patterns'

#качаем и преобразуем в формат csv
/usr/bin/wget -c -q -O - $DOWNFILE | grep "^<tr>" | sed -e 's/<\/td>//g' -e 's/<tr>//g' -e 's/<\/tr>//g' -e 's/[\t]//g' -e 's/^<td>//g' -e 's/<td>/;/g' | iconv -c -f WINDOWS-1251 -t UTF8 > $TMPDIR/$TMPFILE
#перебираем конфиг
while read -r STR_CNF; do
ROUTE_NAME=${STR_CNF%,*}
REGION=${STR_CNF#*,}

#Выдираем id маршрута из БД FreePBX по имени
sql="SELECT route_id FROM outbound_routes WHERE name='$ROUTE_NAME'"
ROUTE_ID=$(mysql -u$BD_USER -p$BD_PASS -Dasterisk -sse "$sql")

#Выдираем всё, относящееся к текущему маршруту из файла Россвязи
/bin/cat $TMPDIR/$TMPFILE | /bin/grep "$REGION" > $TMPDIR/$FILENAME

#проверяем не скачали ли пустышку
check=/bin/cat $TMPDIR/$FILENAME
#если не пусто - работем
if [ "$check" != "" ]; then

#скрипт на awk генерации Dial Patterns
awk_code='
#функция определения диапазона
function ret_diap(from,to)
{
if ((to-from)==0) return from;
else if ((to-from)==9) return "X";
else return "["from"-"to"]";
}
#основная функция
{
DEF=$1;
razm=1;
delete out_str;
for (i=1; i <= length($3);i++)
{
if ((substr($3,i,1)-substr($2,i,1))==0)
{
for (r=1; r <= razm;r++)
{
out_str[r]=out_str[r] substr($3,i,1);
}
}
else
{
if ((substr($3,i,1)-substr($2,i,1))==9)
{
for (r=1; r <= razm;r++)
{
out_str[r]=out_str[r]"X";
}
}
else
{
if (substr($3,i,1)-substr($2,i,1)>=1 && substr($3,(i+1),1)-substr($2,(i+1),1)!=9)
{
count=1;
init_str=out_str[1];
for (j=substr($2,(i),1); j < substr($3,(i),1);j++)
{
if (count==1)
{
out_str[count]=init_str j ret_diap(substr($2,(i+1),1),9);
}
else
{
out_str[count]=init_str ret_diap(j,(substr($3,(i),1)-1)) "X";
j=(substr($3,(i),1)-1);
}
count++;
if (razm<count) razm=count;
}
out_str[count]=init_str j ret_diap(0,substr($3,(i+1),1));
i++;
}
else
{
for (r=1; r <= razm;r++)
{
out_str[r]=out_str[r]"["substr($2,i,1)"-"substr($3,i,1)"]";
}
}
}
}
}
for (r in out_str)
{
print DEF out_str[r];
}
}'

#исполняем код awk, на выходе - Dial Patterns
/bin/cat $TMPDIR/$FILENAME | /bin/awk -F ';' "$awk_code" > $TMPDIR/$PATTFILE

#удаляем старые паттерны
sql="DELETE FROM outbound_route_patterns WHERE route_id=$ROUTE_ID"
/usr/bin/mysql -u$BD_USER -p$BD_PASS -Dasterisk -e "$sql"

#формируем новые паттерны
sql="INSERT INTO outbound_route_patterns (route_id,match_pattern_pass,match_pattern_prefix) VALUES "

n=1
for i in /bin/cat $TMPDIR/$PATTFILE; do
if [ $n -eq 1 ]; then sql="$sql ($ROUTE_ID,'$LOCCODE$i','')"
else sql="$sql, ($ROUTE_ID,'$LOCCODE$i','')"
fi
let n=n+1
done
/usr/bin/mysql -u$BD_USER -p$BD_PASS -Dasterisk -e "$sql"

fi
done < $CONFFILE

#Подчищаем за собой
/bin/rm -f $TMPDIR/$PATTFILE
/bin/rm -f $TMPDIR/$TMPFILE
/bin/rm -f $TMPDIR/$FILENAME

#Применяем маршруты
/var/lib/asterisk/bin/module_admin reload

Здесь его можно скачать.

Помогла заметка - поделись с другом:
  1. Комментов пока нет

  1. Трэкбэков пока нет.

Оставьте эти два поля как есть:

 
Яндекс.Метрика
Array ( [path] => /var/sites/homeless.su/www/wp-content/uploads/2023/06 [url] => https://www.homeless.su/wp-content/uploads/2023/06 [subdir] => /2023/06 [basedir] => /var/sites/homeless.su/www/wp-content/uploads [baseurl] => https://www.homeless.su/wp-content/uploads [error] => )