22.06.2017

Настройка voip телефона Polycom 331 для работа с TLS и DNS SRV

Странная логика у этого аппарата, ну понятно, что в виду нехватка документации объяснить какие-то пункты я не смогу, например, мне не понятно, как взаимодействует раздел SIP и line1. Но моя задача была настроить телефон так, чтобы он работал с TLS и DNS SRV, т.к. в текущем проекте, мы использовали DNS failover. Сразу скажу, что все получилось и сама схема DNS SRV failover прекрасна.

Общая схема такая: Polycom 331tlsKamailio —udp— Asterisk

Итак, вот скрины настроек.

Остальные настройки вне line1 нужно сделать дефолтными, и всё пойдет.

3.02.2016

Ограничение вызовов для exten

Ограничение одновременных вызовов в Астериске . Смысл прост: Ищем каналы по выражению “116-” а потом считаем кол-во элементов в массиве.

same => n,GotoIf($[“${FIELDQTY(CHANNELS(${CALLERID(num)}-), )}” > “1”]?busy-custom,s,1)

3.02.2016

Asterisk. Annoucement. Dialplan. ChannelRedirect. Elastix. Part 2.

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

В этой заметке, мы несколько изменим решение, т.к. оказывается оно еще должно работать с роботом AMI которым пользователи активно пользуются.

Итак, для начала скрипт АМИ, которым осуществляется вызов:

Action: Login
Username: admin
Secret: werkjnfgopw

 

Action: Originate
ActionID: 12345
Channel: Local/117@tapi
Context: agent-custom
Exten: 12122396200
Priority: 1

Ранее смысл решения основывался на том, чтобы сохранить внутренний номер телефона, привязать к нему имя правильного канала (то куда позвонили) и собственно воспроизвести в этот канал файлик с названием “extension_x”.

Для реализации у нас в  файле extension_custom.conf:

Этот контекст ловит определенный номер формата #*00x  и записывает приветствие в файлик “extension_x”. Тут все просто.

[custom-record] ; Context for recording voice messages
exten => _#*00X,1,Noop(${EXTEN})
same => n,Set(CHANNEL(language)=ru)
same => “”n,Set(MONITOR_FILENAME=${DIR}${CALLERID(num)}_${EXTEN:4})
same”” => n,Playback(beep)
same => n,Answer
same => n,Record(${MONITOR_FILENAME}:wav)
same => n,Playback(vm-msgsaved)
same => n,Playback(${MONITOR_FILENAME})
same => n,Playback(vm-goodbye)

этот макрос выполняется когда мы совершаем вызов вручную в функцию Dial встроен код ‘M^(testsub)’ делалось это прям через веб инфтерфейс Эластикса.

[macro-testsub]
exten => s,1,NoOp(${CHANNEL})
exten => s,n,NoOp(${MYVAR})
exten => s,n,Set(DB(br/${MYVAR})=${CHANNEL})
exten => s,n,NoOp(${DB(br/${MYVAR})})

Этот контекст должен вызываться когда с ext приходит команда ##X что означает, что ext хочет воспроизвести приветствие.

[transfer-context]
exten => “” _##X,1,NoOp(${DB(br/${CALLERID(num)})})
exten”” => “”_##X,2,Set(DB(from_ext/last_ext)=${CALLERID(num)})
exten”” => “”_##X,3,ChannelRedirect(${DB(br/${CALLERID(num)})},play-context,${EXTEN},1)
exten”” => _##X,4,Playback(${DIR}${CALLERID(num)}_${EXTEN:-1}) ;
exten => _##X,5,Hangup()

А Это вспомогательный контекст, в него мы перескакием, когда собственно случается событие приоритета 3 в transfer-context. До сих пор не понятно, почему нельзя передать переменную через channelredirect – приходит ее сохранять в DB затем извлекать…

[play-context]
exten => _##X,1,noop(${DB(from_ext/last_ext)})
exten => _##X,2,Playback(${DIR}${DB(from_ext/last_ext)}_${EXTEN:-1}) ;
exten => _##X,3,Set(DB(from_ext/last_ext)=”0″)
exten => _##X,4,Hangup()

 

Итак, при вызове функции AMI Originate как я описал выше, вызывается два пользователя: внутренний 116 например и внешний внутренний вызывается в контексте [tapi]

[tapi]

exten => 116,1,Answer
exten => 116,n,SIPAddHeader(Alert-Info: Ring Answer)
exten => 116,n,NoOp(Context: agent)
exten => 116,n,Dial(SIP/116)
exten => 116,n,hangup

а затем вызывается контекст [agent-custom]: Это все делает тот AMI скрипт. В подчеркнутых строчках сохранение переменной какой внутренний номер позвонил и вызов макроса dialer-dial, который…

exten => _1XXXXXXXXXX,1,Answer

exten => _1XXXXXXXXXX,n,NoOp(Context: agent)
exten => _1XXXXXXXXXX,n,noop(CALLERID: “””${CALLERID(all)})
exten””” => _1XXXXXXXXXX,n,NoOp(EXTEN: ${EXTEN})
exten => _1XXXXXXXXXX,n,noop(SIP_to: ${SIP_HEADER(TO)})
exten => _1XXXXXXXXXX,n,noop(SIP_from: ${SIP_HEADER(FROM)})
exten => _1XXXXXXXXXX,n,Set(__FROM_EXT=${CHANNEL(name)})
exten => _1XXXXXXXXXX,n,Dial(SIP/t1/991${EXTEN},60,r^M(dialer-dial))
exten => _1XXXXXXXXXX,n,noop(DIALSTATUS= ${DIALSTATUS})
exten => _1XXXXXXXXXX,n,gotoif($[“${DIALSTATUS}”= “BUSY”]?busy-custom,s,1)
exten => _1XXXXXXXXXX,n,gotoif($[“${DIALSTATUS}”= “CONGESTION”]?congestion-custom,s,1)
exten => _1XXXXXXXXXX,n,gotoif($[“${DIALSTATUS}”= “CHANUNAVAIL”]?congestion-custom,s,1)
exten => _1XXXXXXXXXX,n,gotoif($[“${DIALSTATUS}”= “NOANSWER”]?congestion-custom,s,1)
exten => _1XXXXXXXXXX,n,hangup

 

который… сохранит для нас связку внутреннего номера и канала который мы вызываем  в базу Астериска.

[macro-dialer-dial]

exten => s,1,NoOp(${FROM_EXT:6:3})
exten => s,n,Set(MYVAR_A=${FROM_EXT:6:3})
exten => s,n,Set(DB(br/${MYVAR_A})=${CHANNEL})

26.12.2015

Asterisk. Annoucement. Dialplan. ChannelRedirect. Elastix.

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

Задача: Записать заранее приветствие в нескольких вариантах, и затем воспроизводить его абонентку по нажатию на кнопки быстрого набора.

Проблема: Быстрый набор – это просто очередной INVITE с заранее запрограммированным кодом, поэтому, для астериска это выглядит как поступление нового звонка.

Решение: Я решил эту задачу как для DTMF так и для использования speed-dial.

Ключ к решению: Основная проблема это заставить астериск воспроизводить заранее записанное приветствие нужному абоненту, если просто внести в dialplan запись вида ##1, где номер приветствия то астериск его проиграет тому кто прислал вызов, а нам нужно воспроизвести его абоненту, да еще и в терминологии Asterisk в другой канал.

Коротенько: Записывать приветствия мы будем формата XXXX_N.wav , XXXX- номер эксентшина который записывает приветствие, а N – это номер самого приветствия, таким образом мы можем записывать несколько приветствий для одного экстеншина.

[custom-record] ; Context for recording voice messages
exten => _#*00X,1,Noop(${EXTEN})
same => n,Set(CHANNEL(language)=ru)
same => “”n,Set(MONITOR_FILENAME=${DIR}${CALLERID(num)}_${EXTEN:4})
same”” => n,Playback(beep)
same => n,Answer
same => n,Record(${MONITOR_FILENAME}:wav)
same => n,Playback(vm-msgsaved)
same => n,Playback(${MONITOR_FILENAME})
same => n,Playback(vm-goodbye)

Воспроизведение:  При звонке мы сохраняем данные кто звонил и имя канала куда позвонили   : ${CALLEDRID(num)} – ${CHANNEL}, Делать это нужно в время команды Dial поэтому используется pre_dial_handler, для Эластикс это просто Опции в разделе General Setting. Tt – дает возмоножсть пользоваться DTMF, а ^M(testsub) выполняет макрос testsub.

[macro-testsub]
exten => s,1,NoOp(${CHANNEL})
exten => s,n,NoOp(${MYVAR})
exten => s,n,Set(DB(br/${MYVAR})=${CHANNEL})
exten => s,n,NoOp(${DB(br/${MYVAR})})

Чтобы запомнить переменную кто звонил мы добавляем строчку “exten => s,n,Set(__MYVAR=${CALLERID(number)})”  в macro-user-callerid и отправляем этот макрос в файл extensions_override_elastix Это означает, что Эластикс будет читать его их этого файла, чтобы не случилось в конфигурации. __ – означает что эта переменная будем передана вновь создаваемому каналу командой Dial.

И теперь при получении кода ##N от пользователя мы просто смотрим есть такая переменная в DB Астериска, если есть, то переводим сохраненный ранее канал в место где начинается воспроизведение.

[transfer-context]
exten => “”_##X,1,NoOp(${DB(br/${CALLERID(num)})})
exten”” => “”_##X,2,ChannelRedirect(${DB(br/${CALLERID(num)})},transfer-context,${EXTEN},3)
exten”” => _##X,3,Playback(${DIR}${MYVAR}_${EXTEN:-1}) ;
exten => _##X,4,Hangup()

Таким образом никаких скриптов, все гибко, красиво, изящно.

Бай.

 

PS: http://asteriskfaqs.org/2010/09/09/asterisk-users/set-channel-variable-from-within-other-channel.html

PS: https://wiki.asterisk.org/wiki/display/AST/Function_DB

 

 

 

 

14.06.2015

ACK. OPENSIPS. SEMS. ROUTE

Столкнулся с проблемой при использовании SEMS. При звонке ACK отправлялся на тот же IP что и сервер. И не доходил до абонента. проблема решается удалением IP адреса из списка локальных доменов. решение было найдено в архиве за 2009 год.

Приятного Opensips всем.

4.06.2015

Opensips Ростелеком chel.media.usi.ru

Чтобы подключить услугу НЕОФОН от Ростелекома на Опенсипсе – необхоидмо выполнить несколько условий.

 

1. Ростелеком трЕбует:

FROM: sip: 7337788@chel.media.usi.ru

2. Исходящий звонок должен происходить обязательно с железки с которой была регистрация, вот рабочая версия для модуля registrant:

modparam(“uac_registrant”,”uac”, “sip:chel.media.usi.ru,,sip:7298171@chel.media.usi.ru,,7337788,PASSWORD,sip:7298171@123.123.123.123:5060,,,”)

3. при получении пакета 407 необходимо отсылать логин и пароль:

route[AUTHCHECK] {
if (t_check_status(“(401)|(407)”)) {

xlog(“got auth request from”);

if ( uri=~”sip:.+@chel.media.usi.ru” ) {
$avp(authrealm) = “Realm”;
$avp(authuser) = “7337788”;
$avp(authpass) = “PASSWORD”;
}

}

failure_route[2] {
xlog(“second failure route”);
uac_auth();
route(AUTHCHECK);

}

6.11.2014

Mysql. With recursive. Суммировать результаты запроса. Having.

Привет,  давеча обнаружил что в таблице со звонками есть двойные звонки. Соответственно возникают две задачи:

1. Удалить дублирующися записи (легко),

2. Делать выборку без дублирующихся записей (легко),

3. Посчитать сумму звонков в продублированных записях. (интересно).

 

1 и 2 я опущу, это не интересно. А вот третий пункт делается так:

with recursive mytable as (select max(duration) from acc where time > ‘2014-08-01 00:00:00’ and srcip like ‘ip address’ and duration > 0 group by callid having count(callid) > 1) select sum(max) from mytable;

но это не включает секунды третьего звонка которые тоже есть.

создадим темповою табличку для экспериментов:

create temporary table acc2 as (select * from acc order by time desc limit 1000);

yooxy# delete from acc3 where id in (select max(id) from acc3 where time > ‘2014-08-01 00:00:00’ and srcip like ‘address’ and duration > 0 group by callid having count(callid) > 1);
DELETE 224
yooxy_1711=# delete from acc3 where id in (select max(id) from acc3 where time > ‘2014-08-01 00:00:00’ and srcip like ‘address’ and duration > 0 group by callid having count(callid) > 1);
DELETE 27
yooxy_1711=# delete from acc3 where id in (select max(id) from acc3 where time > ‘2014-08-01 00:00:00’ and srcip like ‘address’ and duration > 0 group by callid having count(callid) > 1);

DELETE 0.

правда это вручную, зато все под контролем. ну и удалим таблицу.

drop table acc2.

 

25.07.2014

Load Balancing SIP

UAC – > LB -> (PROXY1 | PROXY2 | … ) -> UAS

Задача: раскидать звонки между sip proxy, сначала на PROXY1, в случае его недоступности на PROXY2.

В идеале если хочется распределять нагрузку между несколькими прокси серверами, то нужно делать LB  в режиме stateless (использовать функцию forward() совместо с модулем dispatcher) однако в таком случае нет возможности сделать failover в случае падения одного из проксей, т.к. response нельзя поймать в отдельный блок. (хотя возможно разместив код в разделе onreply_route… надо будет проверить).

Сначала учтем ситуацию что в сообщении уже содержится route и вызовем функцию loose_route. без нее вы рискуете на некоторых звонках, это зависит от вашего окружения, получить зацикливание сообщений ACK. Нужно обязательно проверить, чтобы loose вызывался для ACK сообщений и участвующих в диалоге, а не для первых сообщений в диалоге, т.к. это может привести к пробеганию через ваш прокси трафика на чужие сервера, либо на поставщиков услуг, что страшнее, т.к. это сразу минус бабки.

Ниже представлен конфиг:

request_route {
if (loose_route()) {                                                #Если сообщение содержит Route: то функция выдаст 1. Если его там нет, то 0. Здесь мы проверяем, есть ли в сообщении Route и если да, то…
xlog(“L_INFO”,”ACK TRansported…”);   #Выводим в лог любую хрень полезную для понимания
t_relay();                                                                 #Оправляем сообщение согласно этому заголовку Route. 
exit;                                                                            #Завершаем выполнение скрипта, т.к. больше LB делать ничего не надо. 
}

if (!ds_select_domain(“1”, “8”))                   # Здест мы пользуемся функцией которая выберет для нас адрес прокси (proxy1) и подставит его в URI (первая строчка в SIP сообщении)
{
xlog(“L_ALERT”,”404 No route $ru”); # ВЫводим в лог сообщение елси ни один из проксей не доступен.
send_reply(“404″,”No route”);                  #Посылаем “клиенту” 404 сообщение, мол, нет возможности отправить вызов.
exit;                                                                          # Завершаем скрипт
} else {
t_on_failure(“RTF_DISPATCH”);            # Если таки прокси для сообщения определен, то привязываем ответ к разделу “RTF_DISPATH”, там мы поймаем ответ, и если он говорит о проблеме с прокси, то                                                                                             отправим сообщение на другой прокси.
 xlog(“L_ALERT”,”Passed dispatcher $ru via $du $rm”);   # Запишем в лог информацию о том, что сообщение будет отправленно на $ru.(при вызове функции ds_select_domain $ru меняется на новый
route(RELAY);                                                      # собственно отправляем сообщение в раздел где оно отправится к $ru.
}
}

 

 

1.08.2013

Номера в городах России

Получили таблицу стоимости городских номеров.

Появился список городов в которых можно приобрести номер для рекламных целей. Подключение номера 5-7 дней. Переадресация выставляется на любой Ваш номер.

Для заказа 100@yooxy.ru или по телефону +7 351 7502213.

Город Стоимость Аб. плата
Архангельск 950 590
Астрахань 950 590
Барнаул 950 590
Владивосток 2850 590
Волгоград 950 590
Воронеж 1900 590
Екатеринбург 950 590
Ижевск 950 590
Иркутск 950 590
Казань 950 590
Калининград 950 590
Кемерово 950 590
Краснодар 950 590
Красноярск 950 590
Минеральные Воды 950 590
Москва 950 590
Мурманск 950 590
Нижний Новгород 950 590
Нижний Тагил 950 590
Новосибирск 950 590
Омск 950 590
Оренбург 950 590
Пермь 950 590
Петропавловск-Камчатский 2850 590
Ростов-на-Дону 950 590
Рязань 1900 590
Самара 950 590
Санкт-Петербург 950 590
Саратов 950 590
Сочи 950 590
Ставрополь 950 590
Сургут 1140 590
Тверь 1900 590
Тольятти 950 590
Томск 950 590
Тула 1900 590
Тюмень 1140 590
Ульяновск 950 590
Уфа 950 590
Хабаровск 2850 590
Челябинск 950 590
Южно-Сахалинск 2850 590
Якутск 2850 590
Ярославль 1900 590

11.09.2012

Аналитика занятости линий для БД

Скриптик для анализа одновременного занятия линий.

Суть простая выбираем промежуток когда считаываем кол-во звонков, например каждые 5 минут начиная с 2012-09-01. И смотрим какие звонки к какому промежутку относятся. на выходе пара: время – кол-во линий.

Играя SQL запросами можно получить статистику по каналу или по префиксу, вообщем инструмент нужный, но данная реализация крайне медленная. Времени на разработку совсем нет, а решение принимать хочется на основании конкретных данных.

#!/usr/bin/perl

use strict;
use DBI;
use Time::Local;
use Date::Parse;

my $dbh = DBI->connect(‘dbi:Pg:database=yooxy’, ‘pavel’,”) or die $DBI::errstr;

#get all calls
my $date_start = ‘2012-09-09 00:00:00’;

my $leg = 300; #in seconds
my $calls_query = ‘select time,duration from acc where time > \”.$date_start.’\’;’;
my $sth = $dbh->prepare($calls_query);
$sth->execute;
my $ary_ref = $sth->fetchall_arrayref;

my $start_time = str2time($date_start);
my $a;
my $i,my $i2;
my %values;
$dbh->disconnect;
foreach ($a=$start_time;$a = $a + $leg;) {
$i2++;
foreach (@$ary_ref) {
$i++;
my $date1 = $_->[0];
my $duration = 60;# $_->[1];
my $line = $a;

my $start = str2time($date1);
my $end = str2time($date1) + $duration;

if (($line > $start) and ($line < $end)) { print “o$start < $line < $end\n”; $values{$a}++};

}

if ($values{$a} > 0) {print scalar(localtime($a)),”;”,$values{$a},”\n”;} else {print scalar(localtime($a)),”;”,0,”\n”;

};

last if $a > time();
}
print “\ncalls $i2; iter $i;”;