Настройка скрипта проверки доступности линий
Скрипт для проверки доступности городских номеров будет запускаться раз в две минуты с помощью 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
Остались вопросы?
Я - Компаниец Никита, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.
категории
- DECT
- Linux
- Вспомогательный софт при работе с Asterisk
- Интеграция с CRM и другими системами
- Интеграция с другими АТС
- Использование Elastix
- Использование FreePBX
- Книга
- Мониторинг и траблшутинг
- Настройка Asterisk
- Настройка IP-телефонов
- Настройка VoIP-оборудования
- Новости и Статьи
- Подключение операторов связи
- Разработка под Asterisk
- Установка Asterisk
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 сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.
Доброго дня!
Очень интересная статья, в данный момент желаю применить этот скрипт, но не разу не делал подобного. Есть возможность, с вами связаться и задать вопросы на которые я не нашел для себя ответы?
Очень интересная статья, в данный момент планирую применить данный скрипт, но опыта в этом нет, много вопросов, ответов на которые для себя не нашел, возможно ли у вас получить консультацию.
https://voxlink.ru/kb/asterisk-configuration/nastrojka-skripta-proverki-dostupnosti-linij/
Можете написать нам на почту [email protected]