artem
28.12.2017
4396

Настройка скрипта проверки доступности линий

Скрипт для проверки доступности городских номеров будет запускаться раз в две минуты с помощью crond. После запуска скрипт поочерёдно совершает вызов на каждый из проверяемых номеров. После получения проверочного вызова, Asterisk записывает в AstDB информацию о получении вызова и отбивает вызов (т.к. Asterisk не принимает вызов, расходы на исходящую и, в случае номеров 8800, входящую […]

Скрипт проверки доступности линий

Скрипт для проверки доступности городских номеров будет запускаться раз в две минуты с помощью crond. После запуска скрипт поочерёдно совершает вызов на каждый из проверяемых номеров. После получения проверочного вызова, Asterisk записывает в AstDB информацию о получении вызова и отбивает вызов (т.к. Asterisk не принимает вызов, расходы на исходящую и, в случае номеров 8800, входящую связь будут минимизированы). Список неответивших номеров должен быть отправлен на адреса [email protected] и [email protected] (указаны для примера).

1. Настраиваем контексты для совершения проверочного вызова.
Помещаем данные в тот контекст, который отвещает за исходящие вызовы:

[incomig]
exten => _X.,1,GotoIf($[«${CHECK_THRU_TRUNK}» = «»]?end)
same  => n,Dial(SIP/${CHECK_THRU_TRUNK}/${EXTEN},60)
same  => n(end),Hangup

2. Теперь добавим в контекст, куда приходят входящие вызовы, переход в контекст incoming, если вызов пришёл с номера 74444444444. В качестве примера будем считать, что входящие вызовы приходят в контекст from-income, но мы не будем вносить в него изменения, вместо этого создадим контекст from-trunk-pre

context=from-trunk-pre

В этот контекст будут попадать входящие вызовы.

[from-trunk-pre]
exten => _X.,1,Set(DIALED_NUM=${EXTEN})
same  => n,Goto(cont,1)
exten => _+X.,1,Set(DIALED_NUM=${EXTEN})
same  => n,Goto(cont,1)

exten => cont,1,Gosub(sub-fix-cid,s,1)
same  => n,GotoIf($[«${CALLERID(num)}» = «84444444444»]?incoming,${DIALED_NUM},1)
same  => n,Goto(from-income,${DIALED_NUM},1)

[sub-fix-cid]
exten => s,1,GotoIf($[${REGEX(«^[0-9]{7}$» ${CALLERID(num)})} = 0]?cont1) ;»
same  => n,Set(CALLERID(num)=8495${CALLERID(num)})
same  => n,Set(CALLERID(ANI-num)=${CALLERID(num)})
same  => n,Goto(end)
same  => n(cont1),GotoIf(${REGEX(«^[78]?[2-9][0-9]{9}$» ${CALLERID(num)})}?fix_cid)
same  => n,GotoIf(${REGEX(«^\+?7[2-9][0-9]{9}$» ${CALLERID(num)})}?fix_cid)
same  => n,Goto(end)
same  => n(fix_cid),Set(CALLERID(num)=8${CALLERID(num):-10})
same  => n,Set(CALLERID(ANI-num)=${CALLERID(num)})
same  => n(end),Return

В диалплане выше указаны контексты from-income и incoming, как пример для контекстf исходящего вызова. Также указан номер  84444444444 это пример номера, с которого идет проверочный вызов. В вашем случае будет другой.

3. Контекст для совершения проверочных вызовов:

[trunk-checker-dial]
exten => _X.,1,GotoIf($[«${CHECK_THRU_TRUNK}» = «»]?end)
same  => n,Dial(SIP/${CHECK_THRU_TRUNK}/${EXTEN},60)
same  => n(end),Hangup

Нужен ещё один контекст для приёма второго плеча, создаваемого AMI-действием Originate. Всё, что будет делать этот контекст — принимать вызов ожидать две секунды и разрывать вызов, код контекста:

[trunk-checker-src]
exten => s,1,ResetCDR
same  => n,NoCDR
same  => n,Answer
same  => n,Wait(2)
same  => n,Hangup

На самом деле, при текущем алгоритме проверки доступности номеров, контекст trunk-checker-src не должен вызываться никогда, т.к. Asterisk будет всегда отбивать проверочный вызов, но контекст лучше всё равно создать на тот случай, если Asterisk всё-таки ответит на проверочный вызов, например, из-за ошибки в настройках.

Скрипт проверки городских номеров
Скрипт должен располагаться по следующему пути: /opt/trunk_checker/trunk_checker.pl

#!/usr/bin/perl

use warnings;
use strict;
use IO::Socket;
#use Data::Dumper;
use utf8;

use constant AMI_CONNECT_TIMEOUT        => 3;
use constant AMI_READ_TIMEOUT           => 30;
use constant AMI_RES_VALUE              => 0;
use constant AMI_RES_ACTIONID           => 1;
use constant AMI_RES_DATA               => 2;
# PID
use constant PID_DIR            => ‘/var/run/trunk_checker’;
use constant PID_FILE           => ‘trunk_checker’;

# Get script name and path
my $cwd = »;
my $script_name = »;
if($0 =~ /^(.*)/([^/]+)$/) { $cwd = $1; $script_name = $2 } else { print «Couldn’t determine cwd (WTF?)»; exit 1 }
# Variables
my $config_file = $cwd.’/trunk_checker.conf’;
our @check_nums = ();
my @failed_check = ();
our %config = ();
my $check_delay = 6;
my $allowed_time_diff = 40;
my $sendEmail = $cwd.’/sendEmail.pl’;

unless(do $config_file) {
print «ERROR    Config file $config_file is missing or invalid: $!n»;
exit 1;
}
if(not defined $config{trunk} or $config{trunk} eq » or not defined $config{context} or $config{context} eq » or not defined $config{cid} or not defined $config{db_family} or $config{db_family} eq ») {
print «ERROR    Config file $config_file is missing required parameters (trunk, context, cid, db_family are required)n»;
exit 1;
}
if(not defined $config{ami_host} or $config{ami_host} eq » or not defined $config{ami_port} or $config{ami_port} eq » or not defined $config{ami_user} or $config{ami_user} eq » or not defined $config{ami_secret} or $config{ami_secret} eq ») {
print «ERROR    Config file $config_file is missing AMI connection parameters (ami_host, ami_port, ami_user, ami_secret)n»;
exit 1;
}

### PID stuff
my $pid_file = PID_DIR.’/’.PID_FILE;

# Check if still running
my $cur_pid = »;
if(-r $pid_file) {
if(open PID, $pid_file) {
$cur_pid = <PID>;
chomp $cur_pid;
$cur_pid = » unless $cur_pid =~ /^[0-9]+$/;
close PID;
}
}
if($cur_pid) {
if(open PS, «ps -p $cur_pid -o comm,args —no-headers |») {
while(<PS>) {
chomp $_;
if($_ =~ /$script_name/) { print «Still running $cur_pidn»; exit }
}
close PS;
}
}

# Leave PID file
unless(-d PID_DIR) { mkdir PID_DIR or print «Couldn’t create «.PID_DIR.»n» }
if(open PID, «>$pid_file») {
PID->autoflush(1);
print PID «$$n»;
close PID;
} else { print «Couldn’t write PID file $pid_filen» }
###

# connect to Asterisk AMI
my $ami_sock ami_connect($config{ami_host},$config{ami_port},$config{ami_user},$config{ami_secret});
if(! $ami_sock) { print «ERROR  Couldn’t connect to «.$config{ami_host}.»:».$config{ami_port}.»n»; exit 1 }

my $num_i = 0;
foreach my $num (@check_nums) {
$num_i++;

my $chan = ‘Local/’.$num.’@trunk-checker-dial/n’;
my $exten = ‘s’;
my $prio = ‘1’;

ami_orig_app($ami_sock,$chan,$config{context},$exten,$prio,$config{cid},»,{ CHECK_THRU_TRUNK => $config{trunk} });
sleep($check_delay);

my $db_key = $num;
my $resp = ami_dbget($ami_sock,$config{db_family},$db_key);
if(defined $resp->[AMI_RES_DATA] and @{$resp->[AMI_RES_DATA]}) {
my $call_receive_time = $resp->[AMI_RES_DATA]->[0];
if($call_receive_time =~ /^d+$/) {
my $now = time();
if(abs($call_receive_time — $now) > $allowed_time_diff) {
push(@failed_check,$num);
}
} else { push(@failed_check,$num); }
} else { push(@failed_check,$num); }

sleep(3);
}

if(@failed_check) {
my $subj = ‘Часть номеров не ответила при проверке доступности номеров’;
my $message = «Следующие номера не ответили на тестовый вызов:n».join(‘, ‘,@failed_check);
if(defined $config{mail_to} and $config{mail_to} ne ») {
`$sendEmail -u ‘$subj’ -t ‘$config{mail_to}’ -m ‘$message’ -o message-charset=UTF-8`;
}
}

close $ami_sock;

###
### SCRIPT END
###

### Connect to Asterisk manager interface
### login and password are read from /etc/asterisk/manager.conf
sub ami_connect {
my ($ami_host, $ami_port, $ami_user, $ami_pass) = @_;
my $sock = 0;

$sock = IO::Socket::INET->new(
Proto           => «tcp»,
PeerAddr        => $ami_host,
PeerPort        => $ami_port,
Timeout         => AMI_CONNECT_TIMEOUT
);
$sock or return 0;
$sock->autoflush(1);

my $action_id = gen_pass(10);
my $login_msg = «Action: loginrnActionID: $action_idrnUsername: $ami_userrnSecret: $ami_passrnEvents: offrnrn»;
print $sock «$login_msg»;

my $response = [«», «»];

while($response->[AMI_RES_ACTIONID] ne $action_id) {
$response = read_ami_message($sock);
if($response->[AMI_RES_VALUE] eq «Error») {
print «ERROR    Authentication failedn»;
close $sock;
return 0;
} elsif($response->[AMI_RES_VALUE] eq «») {
print «ERROR    AMI read timed outn»;
close $sock;
return 0;                 }
}

return $sock;
}

sub ami_orig_app {
my ($sock, $chan, $cont, $ext, $prio, $cid, $src_ext, $vars) = @_;

if($cid =~ /[^0-9+]/) {
if($src_ext) { $cid = $cid.'<‘.$src_ext.’>’ } else { $cid = $cid.'<‘.$ext.’>’ }
}

my $action_id = gen_pass(10);
my $msg = «Action: OriginaternActionID: $action_idrnChannel: $chanrnContext: $contrnExten: $extrnPriority: $priornTimeout: 14000rnCallerid: $cidrn»;
my $var_str=»»;
foreach (keys %$vars) {
$var_str = $var_str.»$_=$vars->{$_}|»;
}
$msg = $msg.»Variable: «.substr($var_str,0,-1).»rn» if $var_str ne «»;
$msg = $msg.»rn»;

print $sock «$msg»;

my $ami_message = read_ami_message($sock);
if($ami_message->[AMI_RES_VALUE] eq «») { print «ERROR  AMI read timed outn» }

return $ami_message;
}

sub ami_dbget {
my $sock = ${shift(@_)};
my ($family, $key) = @_;

my $action_id = gen_pass(10);
my $msg = «Action: DBGetrnActionID: $action_idrnFamily: $familyrnKey: $keyrnrn»;

print $sock «$msg»;

my $response = [», »];

my $res = [‘dummy’, »];
while($res->[AMI_RES_ACTIONID] ne $action_id) {
$res = read_ami_message($sock,’DBGet’,AMI_READ_TIMEOUT);
if($res->[AMI_RES_VALUE] eq «») {
print STDERR «ERROR    AMI DBGet read timed outn$msg»;
return $response;
} elsif($res->[AMI_RES_VALUE] eq «Success») {
$response->[AMI_RES_DATA] = [];
my $res1 = [‘dummy’, »];
while($res1->[AMI_RES_VALUE] ne ») {
$res1 = read_ami_message($sock,’DBGetResponse’,2);
if($res1->[AMI_RES_ACTIONID] eq $action_id) {
if($res1->[AMI_RES_VALUE] eq ‘DBGetResponse’) {
if(defined $res1->[AMI_RES_DATA]->{‘Val’}) {
push(@{$response->[AMI_RES_DATA]},$res1->[AMI_RES_DATA]->{‘Val’});
} else {
print STDERR «ERROR     ‘Val’ not defined in DBGetResponsen»;
}
} elsif($res1->[AMI_RES_VALUE] eq ‘DBGetComplete’) {
last;
} elsif($res1->[AMI_RES_VALUE] eq ») {
print STDERR «ERROR     AMI DBGetResponse read timed outn»;
last;
} else {
print STDERR «ERROR     AMI DBGetResponse read returned unexpected response $res1->[AMI_RES_VALUE]n»;
last;
}
}
}
}
}

return $response;
}

#
# Parse AMI message, return @result
# $result[0] = Event or Response
# $result[1] = ActionID
# $result[2] = hash of parameters
#
sub read_ami_message {
my ($sock,$log_label,$timeout) = shift(@_);
if(not defined $log_label) { $log_label = ‘Unspecified’; }
if(not defined $timeout) { $timeout = AMI_READ_TIMEOUT; }
my @result = («», «», {});

eval {
local $SIG{ALRM} = sub { exit };
alarm $timeout;

my $ami_line = «start»;
while($ami_line ne «») {
$ami_line = <$sock>;
if(not defined $ami_line) { last; }
$ami_line =~ s/rn//g;
#print «$ami_linen»;
if($ami_line =~ /^([^:]+): (.*)$/) {
if($1 eq «Event» or $1 eq «Response») { $result[AMI_RES_VALUE] = $2 }
elsif($1 eq «ActionID») { $result[AMI_RES_ACTIONID] = $2 }
else { $result[AMI_RES_DATA]->{$1} = $2 }
}
}
};

return @result;
}

### Generate password
### gen_pass(pass_len,var,num_only)
### var — if set, pass_len will randomly vary by +-2 characters
### num_len — if set, only numbers will be used in password generation
sub gen_pass {
my $pass_len = shift(@_);
my $var = shift(@_);
my $num_only = shift(@_);

my @chars = (‘a’..’z’,’A’..’Z’,’0′..’9′,’_’,’-‘);
if($var) { $pass_len = int(rand 4) + $pass_len — 2 }
if($num_only) { @chars = (‘0’..’9′) }
my $pass = «»;
foreach (1..$pass_len) { $pass .= $chars[rand @chars]; }
return $pass;
}   В переменную $sendEmail = ‘/sendEmail.pl’; Укажите полный путь к файлу. В одной директории со скриптом должен находиться файл настроек trunk_checker.conf, пример файла настроек: %config = (         mail_to         => ‘[email protected],[email protected]’,         trunk           => ‘out_prov’,         context         => ‘trunk-checker-src’,         cid             => ‘74444444444’,         db_family       => ‘trunk_checker’,         ami_host        => ‘localhost’,         ami_port        => ‘5038’,         ami_user        => ‘admin’,         ami_secret      => ‘admin_secret’, ); @check_nums = ( ‘81111111111’, ‘82222222222’, ‘83333333333’, );

81111111111, 82222222222, 83333333333 — указаны для примера. Должны указываться номера, которые собираетесь проверять.

74444444444 — номер с которого будуте проверять

mail_to — список адресов электронной почты, на которые нужно отправлять уведомление о неответивших номерах (разделитель — запятая)
trunk — имя SIP-транка, через который будут совершаться проверочные вызовы
context — имя контекста для второго плеча AMI-действия Originate (в контексте должен быть определён экстеншен s)
cid — номер, который нужно подставлять при исходящих вызовах через out_prov
db_family — имя ветки в AstDB, в которой скрипт будет проверять время получения последнего проверочного вызова (в диалплане Asterisk должна быть указана та же ветка для сохранения время получения последнего проверочного вызова)
ami_host — хост для подключения по AMI
ami_port — порт для подключения по AMI
ami_user — имя пользователя для подключения по AMI
ami_secret — пароль для подключения по AMI

Для отправки уведомлений по почте используется скрипт sendEmail.pl, он должен находиться в одной директории со скриптом trunk_checker.pl. Настройки SMTP-сервера для отправки почты нужно указать в теле скрипта sendEmail.pl (параметры server, port, username, password и from).

И под конец помещаем выполнение скрипта в кронтаб

*/2 * * * * /opt/trunk_checker/trunk_checker.pl

Телефонная книга CallMetrix
Корпоративный телефонный справочник для удобной связи между сотрудниками организации
Скачать описание CallMetrix
Подписаться
Уведомить о
guest
3 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Алексей
Алексей
18.01.2022 13:37

Доброго дня!
Очень интересная статья, в данный момент желаю применить этот скрипт, но не разу не делал подобного. Есть возможность, с вами связаться и задать вопросы на которые я не нашел для себя ответы?

Алексей
Алексей
18.01.2022 13:42

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

https://voxlink.ru/kb/asterisk-configuration/nastrojka-skripta-proverki-dostupnosti-linij/

Ирина Шавульская
Администратор
Ответить на  Алексей

Можете написать нам на почту [email protected]

Остались вопросы?

Я - Компаниец Никита, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.

VoIP оборудование


ближайшие курсы

10 доводов в пользу Asterisk

Распространяется бесплатно.

Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.

Безопасен в использовании.

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

Надежен в эксплуатации.

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

Гибкий в настройке.

Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.

Имеет огромный функционал.

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

Интегрируется с любыми системами.

То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.

Позволяет телефонизировать офис за считанные часы.

В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.

Отличная масштабируемость.

Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.

Повышает управляемость бизнеса.

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

Снижает расходы на связь.

Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.