Использование AGI для блокировки нежелательных звонков в Asterisk

Однажды ко мне обратились пользователи нашей сети с просьбой о блокировке уж слишком назойливой телефонной рекламы и выдали список номеров, которые требуется блокировать. Похожие вопросы периодически задают слушатели во время курсов. Естественно встал вопрос: что делать?

Поразмыслив над ситуацией я пришел к выводу, что нужно учесть следующее:

1. Номеров, которые подлежат блокировке, может быть много. Изменения в списке не должно приводить к изменениям в системе.
2. Попытки позвонить с запрещенных номеров нужно где-то фиксировать.
3. Решение не должно приводить к глобальным изменениям в плане вызовов и/или конфигурации системы.

В качестве платформы у меня имеется Asterisk без каких бы то ни было веб интерфейсов и PostgreSQL для записи детализации разговоров.

Что было решено сделать:

1. Создать в PostgreSQL отдельную базу данных для работы с блокировкой. В базе данных создать две таблицы:

  • Список заблокированных номеров.
  • Журнал для фиксации попыток позвонить.
2. Для работы с базой данных использовать интерфейс AGI (Asterisk Gateway Interface). AGI дает возможность запускать произвольную программу (сценарий) в диал плане. Использование AGI позволяет написать произвольный код для работы с БД на удобном языке.

3. Немного подкорректировать план вызовов для запуска AGI сценария.

Создаем базу данных и таблицы в ней:
psql> create database asteriskban;
psql> \c asteriskban

pqsl> CREATE TABLE banned_numbers( banid serial PRIMARY KEY, banned_num VARCHAR (50) UNIQUE NOT NULL, who_add VARCHAR (50) NOT NULL, when_add TIMESTAMP NOT NULL );

pqsl> CREATE TABLE IF NOT EXISTS ban_log ( logid serial PRIMARY KEY, callerid VARCHAR (50) NOT NULL, dnis VARCHAR (50) NOT NULL, date TIMESTAMP NOT NULL );

psql> INSERT INTO banned_numbers (banned_num,who_add,when_add) VALUES ( '79876543210' , 'jdoe', NOW());
Далее создаем сценарий AGI. Этот скрипт следует разместить в каталоге /var/lib/asterisk/agi-bin/. Для меня Perl наиболее удобный язык сценариев, но вы можете использовать и другие python, php, bash, C, … Ниже содержимое этого сценария с пояснениями.
 > cat /var/lib/asterisk/agi-bin/checkban.pl

#!/usr/bin/perl

$|=1;

#Поскольку используется БД, то на ряду с Asterisk::AG подключаем модуль DBI.

use DBI;

use Asterisk::AGI;

my $AGI = new Asterisk::AGI;

#После запуска AGI сценария Asterisk через STDOUT/STDIN передает в сценарий переменные канала. Мы это записываем в массив %input

my %input = $AGI->ReadParse();

#Готовим и запускаем SQL запрос, в качестве параметра запроса используется номер телефона, который передается как первый аргумент сценария.

my $dbh = DBI->connect('dbi:Pg:dbname=asteriskban;host=127.0.0.1','psqluser','password',{AutoCommit=>1,RaiseError=>1,PrintError=>0});

$sth = $dbh->prepare("SELECT banned_num from banned_numbers WHERE banned_num like ?");

$sth->execute( $ARGV[0] );

#Анализируем возврат запроса. Если запрос вернул данные (заблокированный номер), то звонок переводится в специальный контекст в диал плане: dialout. И в БД вносится запись о попытке запрещенного звонка. Если запрос данные не вернул, то просто завершаем работу сценария. $AGI->verbose добавлено, чтобы повысить информативность отладки.

if ( @row = $sth->fetchrow_array ) {

$AGI->verbose("Call from $row[0] banned",1);

my $sthi = $dbh->prepare("INSERT INTO ban_log (callerid, dnis, date) VALUES (?,?,NOW())");

$sthi->execute( $input{callerid},$input{dnid} );

$sthi->finish;

$AGI->set_context(dialout);

exit 0;

} else {

exit 0;

}

Легко заметить, что большая часть кода сценария - это работа с БД. Именно для этого я и использовал AGI. То что влияет на обработку вызова заключено всего в одной строке $AGI-> set_context(dialout); Остальное обертка вокруг этого.

Далее остается внести коррективы в extensions.conf

Первое создаем подпрограмму. Через эту подпрограмму будет вызываться переход на проверку номера.
 [BannCheck]

exten=>_X.,1,Noop(Check if ${CALLERID(num)} in banned number)

same =>n,AGI(checkban.pl,${CALLERID(num)})

same =>n,return

В контекстах, для которых нужна такая проверка добавляем строку:
 same =>n,gosub(BannCheck,${EXTEN},1)

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

 И наконец контекст, в котором происходит завершение блокированного вызова. Здесь мы отвечаем на вызов, в CDR делаем пометку о том, что вызов был помечен как Blocked. И проигрываем файл с фразой «Ошибочный номер, попробуйте еще раз» (мой маленький троллинг вредных спамеров). После «кладем трубку».

 [dialout]

exten=>_X.,1,Noop(Dial out)

same=>n,Answer()

same =>n,Set(CHANNEL(accountcode)=Blocked)

same =>n,Playback(invalid)

same =>n,Hangup()

Основная идея этой статьи показать, что с помощью AGI функционал вашего Asterisk может быть дополнен чем угодно. Фактически все ограничено только вашей фантазией и навыками написания сценариев. 

Подробности работы с Asterisk и PostgreSQL вы можете изучить на курсах Asterisk базовый и PostgreSQL: Уровень 1. Основы SQL