...

среда, 14 февраля 2018 г.

Управление IAX каналами при большом количестве Asterisk

Добрый день.

Каждый, кто мало-мальски администрирует Asterisk, сталкивается с такой задачей как объединить несколько серверов между собой. Тут уже не важно какой протокол выбран IAX или SIP, так как не зависимо от протокола будет приблизительно одинаковый набор действий. В этом нет никакой проблемы до тех пор пока у вас сервера можно пересчитать по пальцам одной руки. Если же вам не хватило одной руки, а пальцы на второй уже заканчиваются тогда милости прошу под кат, дабы посмотреть один из способов решения данной проблемы.
Я работаю в одной относительно небольшой организации, но эта организация очень сильно разбросана географически, у нас много мелких офисов и всем нужна телефония, соответственно в каждом офисе установлен Asterisk и все сервера объединены для осуществления звонков внутри компании. Когда открывается новый офис, приходится все сервера настраивать и сообщать им о дополнительной нумерации и новом сервере. В один момент, настраивая очередной сервер, я подумал что это можно автоматизировать и избавиться от большинства рутинных действий. По итогу пришел к выводу что должно быть автоматизировано:

  1. Добавление нового IAX канала.
  2. Изменение параметров одного IAX канала
  3. Изменение нумерации в каком-либо офисе
  4. Не требовало изменений на остальных серверах

У Asterisk для реализации задуманного уже всё есть, нам надо только настроить и связать все компоненты между собой. Итак, что же нам понадобится для реализации задуманного:
  1. Asterisk — Я использую Asterisk 13 LTS Certified
  2. Realtime — Asterisk надо собрать с модулем pbx_realtime
  3. Lua — Я использую только его в Asterisk (pbx_lua)
  4. Perl — Куда без Perl в автоматизации, но можно заменить любым другим языком
  5. PostgreSql — Я использую 9.6 версию. Может быть любая другая БД
  6. Bucardo — Я его использую для репликации БД, тут может быть и штатное средство БД

Алгоритм


Для тех кто использует другой набор ПО, я опишу просто алгоритм и уже потом перейду к его реализации на вышеперечисленном наборе программного обеспечения.

Ничего выдающегося тут нет, алгоритм прост как палка. Asterisk настроен на использование realtime конфигурации. Добавление информации о новом IAX канале происходит путем добавление строки в соответствующую таблицу БД. На сервере с определенной периодичностью запускается скрипт, который проверяет в БД наличие новых IAX каналов и при их обнаружении выполняет регистрацию всех каналов. При совершении звонка на внутренний номер идет проверка к какому серверу принадлежит данный внутренний номер и отправляет звонок на соответствующий сервер. Вот и весь алгоритм, более подробно смотрите ниже.

Подготовка


Asterisk. Описывать полноценную и подробную установку нет никакого смысла, в сети можно найти не один десяток статей, как это сделать правильно и быстро. Я опишу лишь какие модули должны быть установлены для решения нашей задачи:
  • pbx_realtime
  • pbx_lua
  • res_config_odbc
  • res_odbc
  • res_odbc_transaction

Этих модулей должно хватить для решения нашей задачи, естественно не забываем подключить все необходимые лично вам модули. Так же установим пакеты, которые понадобятся для сборки Asterisk:
yum install -y dmidecode ncurses-devel libxml2-devel openssl-devel newt-devel sqlite-devel libuuid-devel gtk2-devel jansson-devel binutils-devel curl-devel unixODBC unixODBC-devel spandsp-devel spandsp iksemel iksemel-devel libtool-ltdl libtool-ltdl-devel kernel-devel lua-devel perl-CPAN perl-DBD-Pg perl-DBI perl-App-cpanminus


Не забываем что понадобится компилятор и все сопутствующие приложения, я обычно устанавливаю так:
yum groupinstall -y "development tools" 

Таким образом мы поставили все что необходимо нам. Дальше можете приступать к установке Asterisk, а мы перейдем к настройке.

Realtime. Сперва нам необходимо настроить ODBC, именно им будет пользоваться Asterisk что бы получать настройки из БД. Тут на самом деле, надо поправить только несколько файлов.

/etc/odbc.ini необходимо привести к такому виду:

[asterisk]
Description=PostgreSQL
Driver=PostgreSQL
Database=asrumoscow
Servername=localhost
Username=asterisk
Password=password
Port=5432
Protocol=7.4
Charset=utf8
ReadOnly=No
RowVersioning=No
ShowSystemTables=No
ShowOidColumn=No
Trace=No
TraceFile=/var/log/asterisk/sql.log
FakeOidIndex=No
# Asterisk 13.8 и выше
Pooling=yes

Думаю что в особом пояснении данный файл не нужен, укажите только свои реквизиты доступа к БД.

/etc/odbcinst.ini приводим к виду:

[PostgreSQL]
Description    = ODBC for PostgreSQL
Driver64    = /usr/pgsql-9.6/lib/psqlodbc.so
Setup64        = /usr/pgsql-9.6/lib/psqlodbcw.so
FileUsage    = 1

Если у вас другая версия PostgreSql поправьте пути к библиотекам. На этом настройка ODBC закончена для нас.

Переходим к Asterisk. Что бы он искал настройки в БД используя настроенный нами ODBC, необходимо в файл /etc/asterisk/res_odbc.conf добавить блок:

[realtime-odbc]
enabled => yes
dsn => asterisk  ; имя блока описанное в /etc/odbc.ini
username => asterisk  ; логин от БД
password => password  ; пароль от БД
pre-connect => yes

Теперь осталось сказать Asterisk какие таблицы и для чего он может использовать. В секцию settings файла /etc/asterisk/extconfig.conf добавляем строки:
iax.conf => odbc,realtime-odbc,db_name_config ; тут храним настройки iax 
iaxusers => odbc,realtime-odbc,db_name_iax
iaxpeers => odbc,realtime-odbc,db_name_iax
sippeers => odbc,realtime-odbc,db_name_sip

db_name_* — замените на соответствующие таблицы в БД с которой работает asterisk
Не забываем перезагрузить Asterisk что бы он все это принял. Теперь можно перейти непосредственно к реализации.

Реализация


Для начала нам надо придумать как доставлять информацию о новых IAX каналах ко всем серверам Asterisk. Учитывая что все наши сервера использую Realtime, будем использовать БД. Для этого настроим отдельный сервер с PostgreSql. Создадим базу в которой будет таблица аналогичная таблице из realtime Asterisk. Сейчас у меня таблица выглядит так:
CREATE TABLE public.iax_peers
(
    id serial NOT NULL,
    name character varying(40) COLLATE pg_catalog."default" NOT NULL,
    type type_values,
    username character varying(40) COLLATE pg_catalog."default",
    mailbox character varying(40) COLLATE pg_catalog."default",
    secret character varying(40) COLLATE pg_catalog."default",
    dbsecret character varying(40) COLLATE pg_catalog."default",
    context character varying(40) COLLATE pg_catalog."default",
    regcontext character varying(40) COLLATE pg_catalog."default",
    host character varying(40) COLLATE pg_catalog."default",
    ipaddr character varying(40) COLLATE pg_catalog."default",
    port integer,
    defaultip character varying(20) COLLATE pg_catalog."default",
    sourceaddress character varying(20) COLLATE pg_catalog."default",
    mask character varying(20) COLLATE pg_catalog."default",
    regexten character varying(40) COLLATE pg_catalog."default",
    regseconds integer,
    accountcode character varying(20) COLLATE pg_catalog."default",
    mohinterpret character varying(20) COLLATE pg_catalog."default",
    mohsuggest character varying(20) COLLATE pg_catalog."default",
    inkeys character varying(40) COLLATE pg_catalog."default",
    outkeys character varying(40) COLLATE pg_catalog."default",
    language character varying(10) COLLATE pg_catalog."default",
    callerid character varying(100) COLLATE pg_catalog."default",
    cid_number character varying(40) COLLATE pg_catalog."default",
    sendani yes_no_values,
    fullname character varying(40) COLLATE pg_catalog."default",
    trunk yes_no_values,
    auth character varying(20) COLLATE pg_catalog."default",
    maxauthreq integer,
    requirecalltoken iax_requirecalltoken_values,
    encryption iax_encryption_values,
    transfer iax_transfer_values,
    jitterbuffer yes_no_values,
    forcejitterbuffer yes_no_values,
    disallow character varying(200) COLLATE pg_catalog."default",
    allow character varying(200) COLLATE pg_catalog."default",
    deny character varying(200) COLLATE pg_catalog."default",
    permit character varying(200) COLLATE pg_catalog."default",
    codecpriority character varying(40) COLLATE pg_catalog."default",
    qualify character varying(10) COLLATE pg_catalog."default",
    qualifysmoothing yes_no_values,
    qualifyfreqok character varying(10) COLLATE pg_catalog."default",
    qualifyfreqnotok character varying(10) COLLATE pg_catalog."default",
    timezone character varying(20) COLLATE pg_catalog."default",
    adsi yes_no_values,
    amaflags character varying(20) COLLATE pg_catalog."default",
    setvar character varying(200) COLLATE pg_catalog."default",
    CONSTRAINT iax_peers_pkey PRIMARY KEY (id),
    CONSTRAINT iax_peers_name_key UNIQUE (name)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE public.iax_peers
    OWNER to asterisk;


Структура таблицы должна быть полностью идентична на всех серверах.

Bucardo. Теперь настроим репликацию данной таблицы. Для репликации будем использовать Bucardo, для нашей задачи его более чем достаточно. Установку описывать я не буду, предположим что он установлен и произведена первоначальная настройка. Нам осталось только настроить репликацию нужных таблиц.

Добавляем в Bucardo информацию какие БД будут участвовать в репликации:

Добавим локальную БД которая будет у нас источником всех данных

bucardo add database pbxMaster dbname=asterisk dbhost=127.0.0.1 dbuser=bucardo dbpass=Pa$$w0rD

Добавляем БД удаленного сервера Asterisk
bucardo add database asRUmoscow dbname=asrumoscow dbhost=192.168.0.30 dbuser=bucardo dbpass=Pa$$w0rD

Укажем какую таблицу будем реплицировать:
bucardo add table iax_peers --db=pbxMaster relgroup=iax_peer

Создаём группу для наших БД:
bucardo add dbgroup asRealtime

Добавляем наши БД в новую группу и указываем их назначение.

Укажем источник данных:

bucardo add dbgroup asRealtime pbxMaster:source

Теперь указываем получателей этих данных:
bucardo add dbgroup asRealtime asRUmoscow:target

Сообщаем что и как надо синхронизировать
bucardo add sync asRealtimeSync relgroup=iax_peers dbs=asRealtime

Естественно, bucardo должен знать о всех серверах куда надо реплицировать таблицу, а так же таких серверов должно быть 3 и более.

У меня каждый сервер именуется определенным образом, например asRUmoscow:

  • as — говорит что это сервер с Asterisk.
  • RU — это страна в которой находится сервер.
  • moscow — город в котором расположен сервер.

Тем же именем именуется БД для Asterisk, это позволяет упростить скрипты.
Так как у меня имя таблицы источника и имя таблицы получателя всегда разная, необходимо сообщить об этом Bucardo, для этого выполняем команду:
bucardo add customname iax_peers asrumoscow_iax db=asRUmoscow

Тут мы указали что все данные из таблицы iax_peers реплицировать в таблицу asrumoscow_iax и что это относится только к БД asRUmoscow. Можно перезапустить Bucardo и проверить репликацию, добавив в таблицу iax_peers информацию о канале, она будет продублирована на все сервера.

Perl. Теперь нам надо сделать так что бы сервер регистрировал новые IAX каналы. Тут все довольно просто, есть вот такой Perl скрипт. Суть его проста, он запускается по крону каждые 5 минут, подключается к БД и проверяет для всех ли IAX каналов записанных в таблице hostname_iax есть строки регистрации в таблице hostname_config. Если чего-то не хватает, то скрипт это исправит, естественно он игнорирует IAX канал имя которого совпадает с hostname локального сервера, так как регистрироваться у самого себя идея так себе.

Lua. На данном этапе, получается что у нас информация о каналах реплицируется, сервера сами регистрируются друг у друга. Осталось только решить вопрос с маршрутизацией звонков, в обычном режиме работы для звонка на новый сервер добавляется новый extension, приблизительно такого содержания:

exten => _19xx,1,noop(звонок в Москву)
exten => _19xx,1,dial(sip/${EXTEN},60,r)
exten => _19xx,1,hangup()

Если у вас 10 серверов, то вы должны код выше добавить на все сервера. Это надо упростить, для этого напишем просто один общий extension который будет сам определять куда и как звонить. Для этого мы в таблицу IAX каналов, в столбец setvar добавим информацию о том какие номера обслуживает тот или иной сервер. Например для asRUmoscow основная информация о IAX канале будет выглядеть так:

Тут мы видим в столбце setvar регулярное выражение по которому можно проверить соответствует набранный номер пользователем к этому серверу или нет. Этого более чем достаточно что бы узнать куда хочет позвонить пользователь, осталось только это реализовать.

Для этого у нас есть такой код:

extensions = {
       office = {
           ["_xxxx"] = internal_sip;
      };
} 
-- Функция обработки всех внутренних вызовов
function internal_sip(context, extension)
        local dialString = getStringDial(extension);
        app.Dial(dialString,60,'rTt');
end
-- Проверяем обслуживается пользователь локальным сервером или нет
function isLocalPeer(extension)
        local query = string.format("SELECT setvar FROM %s WHERE name='%s'", hostname.."_iax",hostname);        
        local res = assert(con:execute(query));
        local regexp = res:fetch();
        if (rex.find(extension, regexp)) then 
                res:close();
                return true;
        end
        res:close();
        return false;
end
-- Генерируем строку для приложения Dial в зависимости от того 
-- локальный пользователь или удаленный
function asSystem.getStringDial(extension)
        local dial=nil;
        if (isLocalPeer(extension)) then 
                dial = string.format("sip/%s",extension);
        else 
                local query = string.format("SELECT secret FROM %s WHERE name='%s'",hostname.."_iax",hostname);
                local res = assert (con:execute(query));
                local secret = res:fetch();
                query = string.format("SELECT username,setvar FROM %s",hostname.."_iax");
                res = assert (con:execute(query));
                local server_list = res:fetch({},"a");
                local server = nil;
        local findNum = extension;
        while server_list do
                if rex.find(findNum,server_list.setvar) then
                        server = server_list.username;
                        break;
                end
                server_list = res:fetch({},"a");
        end
        dial = string.format("iax2/%s:%s@%s/%s",hostname,secret,server,extension);
        res:close();
        end
        return dial;
end


Такой не замысловатый код, позволяет не редактировать конфигурационные файлы Asterisk при каждом добавлении нового сервера.

Результаты


Теперь нам особо делать ничего не надо для того что бы добавить новый сервер. Для этого надо выполнить простой набор команд:

Добавить информацию о новой БД в систему репликации:

bucardo add database asRUsmolensk dbname=asrusmolensk dbhost=192.168.15.30 dbuser=bucardo dbpass=Me%gaP@$$
bucardo add dbgroup asRealtime asRUsmolensk:target
bucardo add customname iax_peers asrusmolensk_iax db=asRUsmolensk


Добавить информацию о новом IAX канале в основную БД:
INSERT INTO public.iax_peers(name, type, username, secret, context, host, trunk, auth, deny, permit, qualify,setvar)
        VALUES 
    ('asRUsmolensk','friend','asRUsmolensk', 'IAX_PAS$w0Rd','office','dynamic','yes','md5','0.0.0.0/0.0.0.0','192.168.15.30/255.255.255','yes','^[16]6\d{2,2}'), 

После чего подождать 5 минут и IAX каналы сами поднимутся между всеми серверами и сразу можно звонить на новый сервер.

Эти два действия можно завернуть в Ansible playbook и тогда вообще все будет красиво и модно.

Все это позволяет не только быстро добавлять новые сервера, но и менять маршрутизацию с параметрами IAX каналов. Работает это уже не один год, сейчас сеть разрослась до 25 серверов Asterisk и не уверен что это предел.

Если есть замечания и пожелания, милости просим в комментарии или ЛС.

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий