artem
28.12.2017
1130

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

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

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

 

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         => ‘admin@example.com,boss@example.com’,         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

 

Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

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

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

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

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

ближайшие Вебинары

ONLINE

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 сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.