Blog – IP АТС Asterisk
Recent posts (max 10) - Browse or Archive for more

Скоро: Новая схема нумерации версий

Asterisk 1.6.2 будет последним релизом в серии 1.6. Изначальным назначением Asterisk 1.6 было изменение промежутка времени между релизами, чтобы сообществу не приходилось ждать новый выпуск 2-3 года. Намерением было выпускать новую версию 1.6, которая включала бы добавление возможностей и улучшение производительности, каждые 3-4 месяца.

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

Итак, примерно через полтора года выпусков (1.6.0.x, 1.6.1.x, 1.6.2.x), отзыв сообщества был рассмотрен, и весь опыт, полученный во время цикла релизов 1.6, был сведен воедино, чтобы создать улучшенную схему релиов, которая собирает преимущества как долгих циклов стабильных релизов, так и коротких циклов релизов улучшений (feature releases), и в то же время увеличивает уровень предсказуемости, позволяя сообществу планировать размещение соответствующим образом.

Когда бы ни был создан выпуск, он теперь будет помечаться как "Стандартный" (Standard) или "Долговременно поддерживаемый" (LTS, Long Term Support). Asterisk 1.4 рассматривался бы как LTS релиз, что означает, что для него выходят исправления багов в течение более долгого промежутка времени, по крайней мере 4 года. Стандартный релиз имел бы более короткий период выхода исправлений - 1 год.

После того как период поддержки истекает, релиз получает обновления безопасности по крайней мере в течение одного года (Asterisk 1.2 пример тому), и в конце концов настает конец жизненного цикла (end-of-life) (Astrisk 1.0), когда обновления перестают выходить вовсе.

Все релизы Asterisk 1.6.x считаются Стандартными выпусками.

Следующим LTS выпуском будет Asterisk 1.8, который, как ожидается, выйдет во втором квартале 2010 года. Некоторое время будет затрачено на тестирование и подготовку его к полному выпуску Asterisk 1.8.0.

Более подробную информацию о доступных версиях выпусков Asterisk можно получить здесь.

  • Posted: 2010-03-03 17:21
  • Author: vasm
  • Categories: (none)
  • Comments (0)

Что нового в Asterisk 1.6.2

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

Одним из наибольших улучшений в Asterisk 1.6.2 стало добавление поддержки двух новых голосовых кодеков. Теперь в Asterisk включены драйверы кодеков ITU G.722.1 (он же Siren7) и G.722.1C (Siren14). Оба - широкополосные HD кодеки, поддерживающие передачу высококачественного звука с относительно низким битрейтом. Также добавлено распознавание кодеков (необходимое для настройки звонка), запись, проигрывание и транскодирование в реальном времени.

В 1.6.2 также включен ряд улучшений языка диалплана. Новая функция CURLOPT предоставляет точный контроль за выполнением запросов к веб-сервисам, сделанных с помощью функции CURL. Набор средств связи с ODBC получил новые средства отладки, такие как расширенная поддержка транзакций. В диалплан добавлено новое приложение Originate для того, чтобы облегчить асинхронное инициирование вызова из диалплан-скрипта. Кроме того, в 1.6.2 впервые появилось приложение ConfBridge?, позволяющее создавать конференц-соединения без необходимости использования DAHDI для тайминга.

Улучшения в критических низкоуровневых сигнальных возможностях Asterisk включают в себя добавление MFC/R2 в DAHDI. MFC/R2 (часто называемый просто R2) - это один из самых распространенных сигнальных форматов в Центральной и Северной Америке. Поддержка R2 долгое время была в вершине списка пожеланий. Чтобы воспользоваться этим улучшением, необходимо скомпилировать DAHDI с LibOpenR2.

Среди других улучшений - новый синтаксис, призванный упростить процесс написания скриптов диалплана. Расширение позволяет программистам пропускать добавочный номер или идентификатор паттерна в многострочном скрипте, используя ключевое слово "same", как в следующем примере:

exten => _22XX.,1,NoOp(Incoming Call)
same => n,Answer()
same => n,Wait(1)
same => n,Playback(tt-weasels)
same => n,Hangup()

Asterisk 1.6.2 представляет собой последний релиз ветки 1.6.x. Для подробной информации о предстоящей серии 1.8, читайте “Скоро: Новая схема нумерации версий”

  • Posted: 2010-03-03 16:26 (Updated: 2010-03-03 17:29)
  • Author: vasm
  • Categories: release
  • Comments (0)

Asterisk Load Balansing Часть 2

В этой части я расскажу как настроить Kamailio чтобы его можно было использовать как SIP Proxy, на который будет возложена функции:
1. Регистрации SIP User Agent-ов (авторизация возложена на Radius сервер)
2. Преодоления NAT, с помощью media-proxy
3. Маршрутизации вызовов
4. Балансировки вызовов на ноды Asterisk

Как видно на схеме на sipbalanser-е запущены приложения:
1. Kamailio – Наш SIP Proxy
2. MySQL – в моем случае kamailio использует только одну таблицу из целой кучи таблиц разного назначения, я же использую только таблицу openser.locations, в ней храняться зарегистрированные UA, MySQL может находиться на отдельном сервере, у меня пока все на одном сервере
3. Media-dispatcher – управляющий модуль mediaproxy, kamailio подключается к нему через UNIX сокет, служит для управления media-relay и вывода информации для мониторинга, написан на Python
4. Media-relay – релей RTP трафика, написан на Python, но сам трафик релеит Linux ядро, (любителям FreeBSD не рекомендую использовать для релея RTP трафика предыдущие версии mediaproxy, они там работают, но при большом количестве звонков валится mediaproxy), используется механизм contrack, media-relay может находиться на отдельном сервер, и их может быть сколько угодно, очень удобно в плане масштабирования, сигнализацию на одном сервере обрабатываем, а RTP трафик на другом
5. Radiusclient-ng – через него мы контактируем с radius
6. Asterisk - :) о нем наверное сами все знаете

Установка
Установка всего этого добра весьма банальна, все кроме Kamailio и mediaproxy можно поставить из портов, пакетов и репозитариев, как удобно, а ключевые для нас компоненты лучше ставить из исходников, чтобы и версии последние были и весь процесс прочувствовать и понять что для этих компонентов нужно. Я описывать процесс установки не буду, т.к. сам ставил давно, и уже не помню всех подробностей, а ставить заного ради статьи – лень  Все что нужно знать о установке подробно расписано в README. Единственное для mediaproxy почему-то нет init файлов, но это не беда, их легко написать самостоятельно.

Конфигурирование openser
Перейдем к основному, как настроить kamailio. Где-то я возможно повторюсь с статьей о openser с voip.rus.net, но там описан процесс настройки применительно к старым версиям Openser, в новых версиях kamailio многое изменилось и процесс настройки слегка изменился, особенно это касается работы c NAT.
Конфигурационный файл я аттачить не буду, а буду приводить куски в статье, если эти куски собрать во едино, то получится нужный конфигурационный файл. Делаю это я осознанно, чтобы не возникало желания пролистать статью, ибо «много букфф»  залить конфигурационный файл себе и получить все косяки из-за слабого представления что где зачем.
Я буду описывать не каждую строчку, т.к. предназначение некоторых банально, а некоторые я и сам не знаю зачем нужны, но в мануалах пишут что они нужны :)
Настоятельно рекомендую тем кто не знаком с процедурой установления соединения SIP протокола прочитать соответствующие мануалы. Не повредит знать структуру SIP пакетов т.к. kamailio для маршрутизации звонков использует те или иные поля SIP пакета.
Пожалуй начнем.

debug=3
log_stderror=no

отправлять или нет log сообщения в stdout, если стоит нет, есть возможность отправлять лог сообщения на syslog

fork=yes

директива fork заставляет kamailio работать в режиме демона, иначе все сообщения будут попадать в stdout

children=8
disable_dns_blacklist=yes

disable_dns_blacklist=yes без этой опции kamailio может коряво работать с серверами на которые будем маршрутизировать сообщения

auto_aliases=no
port=5060
listen=udp:192.168.0.1:5060

listen – понятен наверное для чего, параметров listen может быть несколько, но надо быть осторожным с ip маршрутами, чтобы сообщение поступив через один интерфейс не уходило обратно через другой, если конечно именно не это требуется :)

alias=voip.telecom.ru:5060
alias= sip.telecom.ru:5060
alias=192.168.0.1:5060

список alias-ов, синонимом которых является сервер

mpath="/usr/local/lib/kamailio/modules/"

Путь до папки с модулями kamailio

loadmodule "pv.so"
loadmodule "db_mysql.so"
    modparam("db_mysql", "auto_reconnect", 1)

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

loadmodule "sl.so"
loadmodule "tm.so"
    modparam("tm", "fr_timer", 10)
    modparam("tm", "fr_inv_timer", 120)
    modparam("tm", "wt_timer", 5)
    modparam("tm", "delete_timer", 2)
    modparam("tm", "ruri_matching", 1)
    modparam("tm", "via1_matching", 1)
    modparam("tm", "unix_tx_timeout", 2)
    modparam("tm", "restart_fr_on_each_reply", 1)
    modparam("tm", "pass_provisional_replies", 0)
loadmodule "rr.so"
    modparam("rr", "enable_full_lr", 1)
    modparam("rr", "append_fromtag", 0)
loadmodule "maxfwd.so"
    modparam("maxfwd", "max_limit", 256)
loadmodule "usrloc.so"
    modparam("usrloc", "db_mode",   1)
    modparam("usrloc", "timer_interval",   120)
    modparam("usrloc", "db_url", "mysql://openser:openserrw@localhost/openser")
    modparam("usrloc", "nat_bflag", 6)
    modparam("usrloc", "expires_column", "expires")

задаем параметры базы данных, куда конектится, какую базу использовать, выбираем для флага сигнализирующего принадлежность клиента к клиентам за натом bflag 6, подробнее о флагах можно почитать на сайте kamailio

loadmodule "registrar.so"
    modparam("registrar", "method_filtering", 1)
    modparam("registrar", "max_expires", 50)
    modparam("registrar", "default_q", 0)
    modparam("registrar", "append_branches", 1)
    modparam("registrar", "case_sensitive", 0)
    modparam("registrar", "max_contacts", 0)
    modparam("registrar", "retry_after", 0)
    modparam("registrar", "method_filtering", 0)

модуль отвечающий за обработку сообщений Register, у меня выставлен таймер максимального времени перерегистрации в 50 секунд, это связано с тем что мы используем у себя переделанный клиент QuteCom?, который если регается реже начинает терять регистрацию и валится в корку. max_expires заставляет всех клиентов регаться не реже чем раз в 50 секунд.

loadmodule "textops.so"
loadmodule "xlog.so"
loadmodule "mi_fifo.so"
    modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")
    modparam("mi_fifo", "fifo_mode", 0660)
    modparam("mi_fifo", "fifo_group", "openser")
    modparam("mi_fifo", "fifo_user", "openser")
    modparam("mi_fifo", "reply_dir", "/tmp/")
    modparam("mi_fifo", "reply_indent", "\t")

/tmp/kamailio_fifo – это сокет для управления и мониторинга kamailio, управляет и мониторит приложении kamctl

loadmodule "uri_db.so"
    modparam("uri_db", "use_uri_table", 0)
    modparam("uri_db", "db_url", "")
loadmodule "siputils.so"
loadmodule "nathelper.so"
    modparam("nathelper", "rtpproxy_disable", 1)
    modparam("nathelper", "natping_interval", 10)
    modparam("nathelper", "received_avp", "$avp(i:42)")

Т.к. будем использовать mediaproxy, то выключаем поддержку rtpproxy, задаем интервал «пингания» UA, чтобы роутеры держали открытыми порты.

loadmodule "avpops.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "dispatcher.so"
    modparam("dispatcher", "flags", 2 )
    modparam("dispatcher", "list_file", "/usr/local/etc/kamailio/dispatcher.list")
    modparam("dispatcher", "dst_avp", "$avp(i:271)")
    modparam("dispatcher", "grp_avp", "$avp(i:272)")
    modparam("dispatcher", "cnt_avp", "$avp(i:273)")

модуль dispatcher служит для реализации load balancing-а, в list file хранятся адреса различных серверов на которые мы будем распределять вызовы.

loadmodule "auth_radius.so"
    modparam("auth_radius", "radius_config","/etc/radiusclient-ng/radiusclient.conf")
    modparam("auth_radius", "service_type",1)
    modparam("auth_radius", "use_ruri_flag", 22)

включаем поддержку radius, указываем путь до конфига радиус сервера

loadmodule "mediaproxy.so"
    modparam("mediaproxy","mediaproxy_socket", "/var/run/mediaproxy/dispatcher.sock")

включаем работу с mediaproxy, задаем путь до сокета media-dispatcher

loadmodule "domain.so"
    modparam("domain", "db_url", "mysql://openser:openserrw@localhost/openser")
    modparam("domain", "db_mode", 1)
    modparam("domain", "domain_table", "domain")
    modparam("domain", "domain_col", "domain")
loadmodule "presence.so"
    modparam("presence", "db_url", "mysql://openser:openserrw@localhost/openser")
    modparam("presence", "max_expires", 3600)
    modparam("presence", "server_address", "sip:sippalanser.is74.ru:5060")
loadmodule "dialog.so"
    modparam("dialog", "dlg_flag", 4)
loadmodule "nat_traversal.so"
    modparam("nat_traversal", "keepalive_interval", 90)
    modparam("nat_traversal", "keepalive_method", "OPTIONS")

Kamailio читает конфигурационный файл от начала до конца, соответственно диалплан исполняется как обычная программа. Существует несколько разновидностей блоков маршрутизации, которые зовутся: route – основной блок маршрутизации, route[x] – что-то типа процедур, только без параметров, все параметры передаются с помощью флагов и переменных, t_on_reply – блок для обработки различных ответов, failure_route – для обработки ошибок

route
{

Основной блок маршрутизации, по аналогии с С/C++ main() {}

if (method=="OPTIONS")
    {
	exit;
    };
    if (method=="PUBLISH")
    {
	exit;
    };
    if (method=="SUBSCRIBE")
    {
	exit;
    };

Я не использую у себя эти сообщения пока, чтобы не мешались оправляю их в dev/null :)

if (!mf_process_maxfwd_header("10"))
    {
	sl_send_reply("483","Too Many Hops");
	exit;
    };
    if (msg:len > max_len )
    {
	sl_send_reply("513", "Message Overflow");
	exit;
    };

Небольшая защита от больших пакетов и зацикленных вызовов.

# -----------------------------------------------------------------
# Record Route Section
# -----------------------------------------------------------------
if (method=="INVITE" && nat_uac_test("2"))
{
	xlog("L_INFO", "record route section | INVITE & nat test: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	record_route_preset("192.168.0.1:5060;nat=yes");
}

Если получено сообшение INVITE и процедура проверки ната вернула нам истину, то явно указываем заголовочное поле Record-Route. (nat_uac_test может по разному определять ваш нат, для этого у него есть несколько методов определения ната, советую почитать документацию и опытно теоретическим путем выяснить какой аргумент этой функции вам подойдет). Функция xlog отсылает указанное сообщение в аргументе на syslog или на stdout

else if (method!="REGISTER")
{
	record_route();
}

Если от SIP клиента, находящегося за маршрутизатором с NAT, получено сообщение не INVITE и не REGISTER типа, то только тогда мы вызываем функцию record_route(), для гарантии того, чтобы сообщения проходили через наш SIP прокси сервер с вышестоящих и нижестоящих SIP прокси серверов или со шлюзов в публичную телефонную сеть (PSTN).

if (method=="BYE" || method=="CANCEL")
{
        xlog("L_INFO", "Call Tear Down | end_media_session: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	end_media_session();
};

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

# -----------------------------------------------------------------
# Loose Route Section
#Секция свободной маршрутизации
# -----------------------------------------------------------------

Эта секция включается когда сообщение адресовано не нашему серверу, а проходит через него транзитом

if (loose_route())
{
xlog("L_INFO", "loose route\n");
	if ((method=="INVITE" || method=="REFER") && !has_totag())
	{
	    sl_send_reply("403", "Forbidden");
	    return;
	};

Мы должны особым образом обрабатывать сообщения Re-Invite, дабы предотвратить разрыв потоков по RTP протоколу, во время обработки этих сообщений. Таким образом, в этом месте мы отдельно обрабатываем эти сообщения для клиентов, находящихся за NAT.
Для гарантии того, что мы получили действительно повторное сообщение INVITE (re-INVITE), мы должны убедиться, что функция has_totag() и loose_route() вернула TRUE. Причина в том, что возможно оригинальное сообщение INVITE содержит предопределенные заголовочные поля для маршрутов, что заставило бы loose_route() вернуть TRUE. Поэтому производим проверку функцией has_totag(), т.к. только для уже установленных соединений будет содержаться флаг "tag=" в заголовочном поле <To> (т.е., только для вызовов, где, вызываемым абонентом, был подтвержден запрос на установку соединения сообщением с кодом "200 OK").
Другими словами, эта новая проверка безопасности основывается на том факте, что уже установленные SIP вызовы будут содержать "totag", тогда как еще не установленные - его не содержат. Для гарантии того, что наша логика "свободной маршрутизации" не будет использована в злонамеренных целях, мы проверяем тот факт, что эти сообщения INVITE и REFER, приняты в рамках уже установленного соединения.

	if (method=="INVITE")
	{
	    if (nat_uac_test("2") || search("^Route:.*;nat=yes"))
	    {

Теперь мы проверяем NAT статус отправителя сообщения re-INVITE, вызывая функцию nat_uac_test("3"). Также ищем заголовочное поле <Route>, содержащий тег ";nat=yes", который будет вставлен ранее, обсуждаемой ранее, функцией record_route_preset(). Если найден тэг ";nat=yes", тогда вызывающий абонент находиться за маршрутизатором с NAT.

		setbflag(6);

Если отправитель сообщения находиться за маршрутизатором с NAT или INVITE сообщение содержит флаг "nat=yes", тогда мы устанавливаем флаг 6 для использование его в дальнейшем.

		use_media_proxy();

Для начала проксирования RTP потоков, мы вызываем функцию use_media_proxy(). Она будет общаться с внешним mediaproxy сервером, заставляя это открыть UDP порты для обоих клиентов, или управлять существующим сеансом RTP проксирования для уже установленного вызова, в зависимости от заголовочного поля <Call-ID>. Вызов use_media_proxy() вызывает перезапись содержимого SDP, в части IP адреса и портов, которые выделил mediaproxy сервер для RTP потоков

	    }
	    else
	    {
		xlog("L_INFO", "loose route |  Re-INVITE from NO NAT: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
		route(4);
	    }
	}
	route(1);
	exit;
    }

Если NAT –а нет, то просто отправляем звонок на route[4] для проверки находится ли абонент за NAT и на route[1] для доставки сообщения по назначению, что будет происходить там, мы выясним чуть позже.

# -----------------------------------------------------------------
# Call Type Processing Section
# Секция, обрабатывающая различные типы вызовов.
# -----------------------------------------------------------------
if (uri!=myself)
{
        xlog("L_INFO", "MESSAGE NOT MYSELF: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	route(4);
	route(1);
	exit;
};

Если сообщение больше не нуждается в обработке нашим SIP серверов, то отправляем его на route[4] route[1]

if (method=="ACK")
{
        xlog("L_INFO", "route ACK detect: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
      	route(1);
        exit;
}

Получено сообщение ACK, отдельное правило создано для нужд отладки, такие сообщение мы должны сразу на route[1] отдать.

else if (method=="CANCEL")
{
	xlog("L_INFO", "route CANCEL detect: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	end_media_session();
	route(1);
	exit;
}

Получено команда отмены вызова, вызываем end_media_session, чтобы наверняка закрыть UDP порты на mediaproxy, даже если UA не за NAT и mediaproxy не использовался, совершенно безопасно вызвать эту команду и отправляем Cancel по назначению чтобы другой UA тоже закончил процедуру установления сессии.

else if (method=="INVITE")
{
         route(3);
  	 exit;
}

Если получено сообщение INVITE то отдаем его сразу на route[3], там содержится вся логика установления соединения.

else if (method=="REGISTER")
{
        xlog("L_INFO", "route REGISTER detect: M=$rm RURI=$ru F=$fu T=$tu IP=$si $mb\n");
	route(2);
	exit;
}

Если получено сообщение REGISTER то будем его обрабатывать в route[2]

route(1);

все остальные сообщения отправляем по назначению.

# -----------------------------------------------------------------
# Default Message Handler
# -----------------------------------------------------------------
route[1]
{
    xlog("L_INFO", "route[1] default handler: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
    t_on_reply("1");

При работе с UA находящимися за NAT мы должны корректно обрабатывать сообщения возвращающиеся к UA, к этим сообщениям можно получить доступ через блок reply_route

    if (!t_relay())

вызываем функцию t_relay, это statefull функция, т.е. с сохранением состояния транзакции. Т.е. если после начала транзакции Invite сообщением отправить ACK или BYE то это сообщение будет отправлено именно тому UA который это сообщение ждет.

    {
	xlog("L_INFO", "route[1] | ERROR: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	if (method=="INVITE" || method=="ACK")
	{
	    xlog("L_INFO", "route[1] | INVITE or ACK error: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	    xlog("L_INFO", "route[1] | end_media_session: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	    end_media_session();
	}
	sl_reply_error();

Ошибка доставки сообщения

    }
}
# ------------------------------------------------------------------------
# Обработка REGISTER
# ------------------------------------------------------------------------
route[2]
{
    sl_send_reply("100", "Trying");
    xlog("L_INFO", "route[2] REGISTER Message Handler M=$rm RURI=$ru F=$fu T=$tu IP=$si");
    if (!search("^Contact:[ ]*\*") && nat_uac_test("2"))
    {
	setbflag(6);
	fix_nated_register();

Fix_nated_register() специально используется для обработки сообщений REGISTER от клиентов, находящихся за NAT

	force_rport();

функция Force_rport () добавляет полученный IP порт в самое начало заголовочных полей "via" SIP сообщения. Это дает возможность направлять последующие SIP сообщения на нужный порт для последующих SIP транзакций.

    fix_contact();

переписываем IP и порт в заловке Contact, чтобы в таблице зарегистрированных UA был не локальный адрес за натом, а адрес ната и порт через который можно достучаться до UA

    }

Проверяем UA от которого пришло сообщение, за NAT он или нет, если проверка истина то выставляем bflag 6, этот флаг будет сохранен в таблице location, kamailio всегда будет знать какой UA за Nat или нет, чтобы иметь возможность задействовать mediaproxy для установления RTP сессии

    if(is_method("REGISTER") && is_present_hf("Expires") && $(hdr(Expires){s.int})==0)
    {
	xlog("L_INFO", "UNREGISTER: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
    }

Если поле Expires=0 значит UA отрегивается, не будем уточнять его параметры авторизации.

    else
    {
	if(!radius_www_authorize(""))
	{
	    xlog("L_INFO", "radius_www_authorize() error M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	    www_challenge("","1");
	    exit;
	}
	else
	{
	     if (!check_to())
	    {
		sl_send_reply("401", "Unauthorized");
		exit;
	    }
	  consume_credentials();
	}
    };

В противном случае авторизуем его. У нас авторизация реализована через Radius, только не спрашивайте как настраивать Radius сервер какие атрибуты надо править. У нас отдельный человек занимается Radius-ом, и настройка всего этого его рук дело. Наш Radius сервер претерпел множественные изменения исходников для нужд нашей компании, поэтому его конфиги вам совершенно не будут интересны. В книге про openser есть пример как использовать аккаунты в базе для регистрации, на сайте есть туториал по работе с radius в нете полно примеров разных конфигураций, так что в этом у вас есть полная свобода.

    if (!save("location"))
    {
	sl_reply_error();
    }
}

Сохраняем информацию о UA в базе kamailio, в таблице locations.

Секция обработки INVITE

route[3]
{
    xlog("L_INFO", "route[3] invite handler: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
   if (nat_uac_test("2"))
   {
	 setbflag(7);

здесь мы выставляем bflag 7, т.е. когда исходящий вызов сигнализатором NAT-а будет флаг 7, а при входящем вызове на абонента за NAT-ом флаг 6

    	force_rport();
    	fix_contact();
   }

Если INVITE от клиента за NAT делаем с ним то же что сделали до этого в route[2] Здесь было бы неплохо сделать авторизацию INVITE, дабы кто попало не позвонил и не поговорил на халяву, если у вас конечно этот сервер будет обслуживать клиентов, которым надо насчитать денег. Но на практике kamailio не до конца справляется с авторизацией INVITE. 20-30 % звонков завершаются аварийно, т.к. kamailio решает что авторизация не пройдена. Товарищ tma c форума asterisk-support.ru пишет «Проблема в том, что INVITE может придти без необходимых для Digest авторизации данных, в результате биллинг/radius "отшивает" запрос, а Kamailio/SER/OpenSER, как следствие, его рвет.» На практике авторизация работает примерно так, что для REGISTER что для INVITE запросов. Приходит первоначальный запрос, если в нем нет необходимой Digest информации для авторизации запроса, то сервер должен вернуть false, потом он отправляет 401 Unauthorized, на что клиент должен попробовать еще раз отправить этот запрос но уже с необходимыми Digest данными, для запросов REGISTER это прокатывает, а вот для INVITE-ов почему-то не всегда, сложный вопрос почему это так, я не знаю причин и пока решил вызовы не проверять, есть идеи как такую проверку реализовать, но это уже отдельная тема

Lookup(“location”) заставляет сервер проверить есть ли UA которому хочет позвонить клиент отправивший INVITE в списке зареганных, если он есть то функция возвращает его реквизиты, а также значение bflag 6, установлен или нет

    if (!lookup("location"))
    {

UA не найден, значит либо он не зареган либо звонят не UA находящемуся на этом сервере

	xlog("L_INFO", "route[3] | not local client: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
        route(5);
	exit;

если нужного номера нет на этом сервере, то поищем его на asterisk сервере, route[5] отвечает за перенаправление вызова на asterisk

     }
    else
    {
	xlog("L_INFO", "route[3] | local client: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
    }

клиент найден на сервер, отправляем его на route[4], который в случае необходимости включит mediaproxy и собственно по назначению на route[1]

    route(4);
    route(1);
}

Включаем mediaproxy

route[4]
{
    #xlog("L_INFO", "route[4] nat traversal: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
    if (isbflagset(6) || isbflagset(7))
    {

Проверяем, если установлен bflag 6, то вызов на UA который за NAT, если установлен bflag 7, то вызов от UA который за NAT

        if (!isbflagset(8))
	{
	    setbflag(8);
	    xlog("L_INFO", "route[4] | use_media_proxy: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
	    use_media_proxy();
	}

Включаем mediaproxy, простенькая защита от многократного включения mediaproxy для одного вызова, реализована посредством конструкции c bflag 8

    }
}

Вот собственно секция для выбора нужной ноды asterisk, работает модуль dispatcher

route[5]
{
     t_on_reply("2");
     t_on_failure("1");

обработка ошибок и сообщений

    xlog("L_INFO", "route[5]->asterisk node: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
    ds_select_domain("1","4");

выбираем группу серверов 1 и механизм распределения вызовов 4 – round robin

    route(4);
    route(1);

отправляем вызов куда следует

}

В файле dispatcher.lis который мы указали здесь modparam("dispatcher", "list_file", "/usr/local/etc/kamailio/dispatcher.list") мы прописываем необходимые сервера куда будем балансировать нагрузку.

1 sip:192.168.0.1:5060
1 sip:192.168.0.2:5060

Вот на эти два сервера и пойдут запросы

2 sip:192.168.0.3:5060
2 sip:192.168.0.4:5060

А сюда бы они пошли если бы мы указали в конфиге kamailio ds_select_domain("2","4");
Обработка ошибок и сообщений.

onreply_route[1]
{
     if ((isbflagset(6) || isbflagset(7)) && (status=~"(180)|(183)|2[0-9][0-9]"))
    {
	if (!search("^Content-Length:[ ]*0"))
	{
	    use_media_proxy();
	};
	if (nat_uac_test("2"))
	{
	    fix_contact();
	};
    };
}
onreply_route[2]
{
    if (status=~"[12][0-9][0-9]")
    {
        fix_nated_contact();
        exit;
    };
}
failure_route[1]
{
    if( t_check_status("408") )
    {
	xlog( "L_NOTICE", "[$Tf] FR: $ci -- TIMEOUT for Gateway $rd\n" );
    }
    else
    {
	xlog( "L_NOTICE", "[$Tf] FR: $ci -- $rs reason $rr\n" );
    };
    if( t_check_status("403") )
    {
	xlog("L_NOTICE", "[$Tf] FR: $ci -- SIP-$rs Forbidden -> ISDN Cause Code 1\n" );
    	return;
    };
    if( t_check_status("486") )
    {
	xlog("L_NOTICE", "[$Tf] FR: $ci -- SIP-$rs Destination BUSY \n" );
	return;
    };
    if( t_check_status("487") )
    {
	xlog("L_NOTICE", "[$Tf] FR: $ci -- SIP-$rs Request Cancelled\n" );
	return;
    };
    if( ds_next_domain() )
    {
	t_on_reply("2");
	xlog( "L_NOTICE", "[$Tf] FR: $ci Next gateway $fU -> $tU via $rd\n" );
	if( !t_relay() )
	{
	    xlog( "L_INFO", "[$Tf] FR: $ci -- ERROR - Can not t_relay()\n" );
	    return;
	};
	return;
    }
    else
    {
             xlog( "L_INFO", "[$Tf] FR: $ci No more buscuits in the gateways" );
	t_reply("503", "Service unavailable -- no more gateways" );
	exit;
    };
    xlog("L_INFO", "failure_route[1]->: M=$rm RURI=$ru F=$fu T=$tu IP=$si\n");
}

Вот в принципе минимальный конфиг для того чтобы ваш kamailio стал полноценным load балансером. Но это лишь мизер всех его возможностей.

Gastify - уведомление о входящем звонке для Gnome

Наткнулся в инете на короткую статью о настройке уведомления о входящем звонке для gnome. Это конечно не Outcall, но мне и не нужна функциональность Outcall, я не пользуюсь телефонными контактами из почтового клиента. А вот уведомление о звонке очень кстати. Можно просто слушать музыку в наушниках, выставить звук ringer в null, и при входящем звонке не дергаться :-), а спокойно снимать наушники и одевать USB гарнитуру Platronics ;-)

В общем, решил попробовать. Для app_notify есть три клиента:

MacOSX

Win

Linux

Установка Gastify

Качаем отсюда - http://gastify.googlepages.com/.

./configure, make, make install.

У меня в gentoo после emerge netcat появился бинарник nc, а не netcat, как в примере на сайте gastify.

Запускаем, проверяем. Надо же, работает, и еще историю звонков показывает. Удобно, софтфон выключен, а лог звонков есть :-)

А зачем оно надо?

Теперь поразмышляем о применении. Архитектура такая:

  • При входящем звонке вызываем AGI скрипт, который делает поиск по корпоративной адресной книге или CRM.
  • С случае совпадения вызываем app_notify с именем звонящего.

Плюс такого решения - доступ к базе с сервера, а не каждого компа.

Minimalistic approach :-)

  • Posted: 2009-09-09 02:18 (Updated: 2009-09-09 02:40)
  • Author: litnimax
  • Categories: CTI
  • Comments (3)

Asterisk Load Balansing. Часть 1.

Пролог

Отличное приложение Asterisk, но свои косяки в нем тоже имеются, от утечек памяти появляющихся под большой нагрузкой, до багов которые еще никто не заметил. В итоге случается так, что до бесконечности Call центр на asterisk на одной машине масштабировать нельзя, рано или поздно утыкаемся в потолок производительности и система начинает периодически падать. Одна беда если просто звонок оборвался, но как правило Call центр у большой компании постепенно обрастает различным функционалом, по нему начинают вести статистику обращений по различным вопросам, по длительности вызовов на различные службы делают выводы о лояльности клиентов, по записанным разговорам решают конфликтные ситуации, по статистике очередей считают зарплаты операторам. В общем: если компания завязана на общение с многочисленными клиентами, то нестабильная работа Call центра нарушает слаженное взаимодействие разных отделов, приводит к уменьшению эффективности работы компании и как следствие уменьшению прибыли. Это все конечно банальные вещи, но просто захотелось излить душу, потому что с данной проблемой я столкнулся в полной мере. Для специалиста по телефонии в большой компании с количеством абонентов > 100000 нестабильность Call центра приводит к появлению стойкой головной боли :) и появлению нервного тика :), не говоря уже о том что даже банальный выезд за город происходит с задней мыслью, а что если Call центр опять свалится, а рядом меня не будет :)

Как быть дальше?

Свелось все к тому, что так больше работать никто не мог. Решили принимать какие-то меры. Первая самая очевидная мера - это разделить Call центр на несколько машин. Да это работает, но не избавляет от всех прелестей по увеличению количества работы после каждой переконфигурации в системе, причем это количество работы растет с увеличением абонентов. Начали думать о каком-нибудь адски крутом проприетарном Call центре за много денег, надо сказать что я был категорически против, вспомнив только однажды увиденный прайс от Nec, с его кучей лицензий и непонятных "кабинетов" становилось плохо:). Слава ктулху мой голос был услышан и сомнительное решение за много денег покупать передумали, а решили попытаться построить решение на уже проверенном и знакомом Asterisk PBX.

Что же мы хотим?

Чего хотелось бы от Call центра, чего на данный момент у нас не было?

1. Безотказную систему, желательно с полным резервирование и без единой точки отказа
2. Легкую реализацию load balansing между машинами Call центра
3. Масштабируемую систему. Чтобы для обработки увеличившейся нагрузки необходимо было бы только добавить еще одну машину, причем чтобы это не требовало перенастройки всей системы, необходимо чтобы это мог сделать обычный инженер по эксплуатации (да у нас и такие есть :))
4. Единую точку хранения учетных записей системы, у нас все сервисы для абонентов привязаны к логину и паролю, авторизацию для пользования сервисам осуществляет Radius, в общем - идеальный вариант, если Call центр будет работать с Radius
5. Единая точка хранения статистики работы Call центра, ну тут и к бабке не ходи, asterisk-addons и mysql :)
6. Гибкое API для разработки собственных интерфейсов управления и просмотра статистики работы Call центра и тут Asterisk нам подходит: AMI, CDR, queue_log
В общем необходимо было реализовать Load Balansing, Radius, масштабируемость и безотказность. Всего ничего :), не говоря уже о том что в процессе разработки наверняка всплывет еще куча всяких ньюансов.

Как же все это сделать?

Краем уха я слышал где-то про SIP софтсвитч на OpenSer?, когда начал искать как же он работает наткнулся на одном из любимых форумов по asterisk на книженцию Building Telephony Systems with OpenSER. Благодаря ей был освоен очень нелегкий в понимании OpenSer?. Изучив документацию стало ясно, что авторизация через Radius это не проблема, балансировка тоже. Решено было строить систему такого вида:

Т.к. мы предоставляем сервис телефонии то имеем стык с PSTN с несколькими операторами по E1. В VoIP загоняем его через 2 Cisco AS5350. Которые гонят трафик по SIP на sipbalanser, в качестве которого используется софтсвитч kamailio, бывший проект OpenSer? (все наверное в курсе что этот проект форкнулся на kamailio и opensips, вроде оба проекта развиваются но документацию мне больше по душе у kamailio). На sipbalanser-е регистрируюся сотрудники Call центра, их авторизует Radius, kamailio для связи с Radius использует radiusclient-ng. Логины и пароли хранятся в общей базе данных компании, в качестве сервера Radius используется Free Radius 2. Сведения о зарегистрированных клиентах хранятся в локальной базе данных на MySql?. Запросы на установление соединения маршрутизируются на sipbalanser, который затем с помощью модуля dispatcher распределяет их на ноды asterisk по алгоитму round robin. Asterisk обрабатывает звонок, если надо проигрывает музыку, помещает вызов в IVR или делает любое свойственное asterisk-у действие с звонком, затем если нужно соединить с реальным человеком, то звонок отправляется на нужного нам сотрудника обратно на sipbalanser, тот ищет у себя в локальной базе зарегистрированного User Agent-а и если находит отдает ему вызов. Вроде все просто :)

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

Отчет о конференции часть вторая :-)

… Блоггинг получается только в дороге… Наверное, потому что на GPRS в шеле сидеть неудобно, и самая интересная работа становится! недоступной, и приходится довольствоваться той, которую можно сделать.

Остановился я на возвращении с конференции на поезде Питер-Москва. Далее не очень интересно. 6 утра, метро, домой к Максу, пельмени, спать. Проснулся после обеда, отменил встречу, сел за ноут. Сразу ожил, и пошла работа.

Первым делом надо поднять рассылку, и разослать доступ на FTP для выкладывания компроматов :-)

Это будет уже третья попытка поднять рассылку по Asterisk'у, но обещаю, она пойдет (просто астерисковое сообщество в рунете явно вырулило но новый качественный уровень).

Решил использовать старый добрый Mailman, так как планирую запустить сразу несколько групп рассылок:

  • для участников Asterisk World 2009 (закрытая)
  • asterisk-users - для админов
  • asterisk-dev - по разработке
  • PBXware - для клиентов "АТС Дизайн"

Заранее поглядел, как оно будет дружить с Zimbra. Будет! Наступил на грабли Unknown virtual host, по которым проходил до 9-ти вечера (это после того, что рулил когда-то в ISP почтой :-). В 21:05 пожал Максу руку, и через 45 минут уже сидел в экспрессе Москва-Дубна. Сейчас попробую добить рассылку по GPRS, если не выйдет, сяду писать анонсы и итоги конференции - уже прошла кристаллизация идей ;-)

/me listening to Chris Lake Feat. Emma Hewitt - Carry Me Away!

Asterisk World 2009 Conference hot news

22:55, едем в поезде.

Завтра в 6 в Москве. Купе слушает Vocal Trance :-)

Саша сидит напротив двух красивых девушек, а я сижу напротив Макса. Играет Super8 and Tap - Made of Love. Лениво переговариваемся с Максом, девушки смеются.

Еще два часа назад мечтали о полке с подушкой, но приехали на вокзал за 30 мин до отхода поезда. Чтобы скрасить время ожидания, "по-умному" купили 9 баночек пива :-) Освежающий глоток холодного пива снял сон как рукой.

Хотя еще на электричке от Зеленогорска просто "вырубились", и проспали первую станцию в Питере. На второй турникеты наотрез отказались нас пропускать в город. Переговоры с тетками успехом не увенчались. Пришлось стоять под дождем в очереди в кассу, и заплатить 210 рублей за троих, чтобы получить билеты, которые нас выпустят. Фан! Чтобы доехать от Зеленогорска до Питира, заплатили 180 рублей. Вход копейка, выход - рубль :-)

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

Часом ранее мы сдали свои номера и собрались перед Гелиос-Отелем. Устроили фото сессию (следите за обновлениями asteriskworld.ru!)

А еще часом ранее завершился двухчасовой утренний блок. Я бы назвал его взаимным выносом мозга. Детальное резюме и разбор полетов будут в рассылке. Если честно, я специально оставил доклад на тему консолидации усилий на самый послед. Потому что материалом для доклада явился материал докладов других участников и обсуждений. Особенно материал ночного штурма c IgorG и switch, который как-то получился спонтанно запланированным. Обмен разумами и энергией был настолько силен, что под утро, когда все разошлись, мы с Максом выпустили свой первый микс - Asterisk World Trancequlity Mix 2009 :-) На самом деле, мы решили сделать music on hold, чтобы клиенту, позвонившему на номер 2323956 сразу стало все понятно. А чтобы исключить непонимание, перед направлением звонка в приложение Queue было решено поставить Wait(30). Для постоянных клиентов сливаем секрет - если при прослушивании музыки нажать любую кнопку, звонок сразу пойдет в Queue с самым высоким приоритетом.

Всем, кто сейчас в дороге - а это большая часть участников, желаю счастливого пути, и до встречи на asterisk-support.ru.

Всегда Ваш,
litnimax

P.S. Зачем придумали сон? Мы вот уже 43 часа не спим, и нормально. Идем Макс в тамбур, покурим, и спать, все равно пиво закончилось…. :-)

Skype для Asterisk beta релиз!

Свершилось. Asterisk стал первой АТС, столь органично интегрированной со Skype. Еще раньше существовали решения по интеграции со Skype, но все они использовали работающего Skype клиента в качестве поставщика Skype API. Представьте себе сервер, с дюжиной виртуальных X серверов с запущенными копиями Skype клиентов, на каждом по отдельному Skype эккаунту….

Решение Skype от Digium - первое и пока уникальное в своем роде. Digium предоставляет chan_skype - обычный канал для Asterisk, внешне выглядящий точно также, как и chan_sip или chan_iax2. Никаких Иксов и Skype клиентов. Запускается с пол-оборота. Все, что надо сделать, это скачать пакет с сайта Digium, и приобрести лицензию. Лицензии кстати бывают многоканальные, таким образом, у компании будет единый многоканальный Skype эккаунт.

В момент написания этой публикации доступны бесплатные лицензии сроком действия в один месяц! Спешите опробовать!

Установка

До безобразия просто:

snowflake tmp # wget 'http://downloads.digium.com/pub/telephony/skypeforasterisk/skypeforasterisk-1.4_0.9.10-x86_32.tar.gz'
--2009-07-31 04:45:11--  http://downloads.digium.com/pub/telephony/skypeforasterisk/skypeforasterisk-1.4_0.9.10-x86_32.tar.gz
Resolving downloads.digium.com... 76.164.171.232
Connecting to downloads.digium.com|76.164.171.232|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2346062 (2.2M) [application/x-gzip]
Saving to: `skypeforasterisk-1.4_0.9.10-x86_32.tar.gz'
100%[=================================================================================================>] 2,346,062   1.55M/s   in 1.4s
2009-07-31 04:45:13 (1.55 MB/s) - `skypeforasterisk-1.4_0.9.10-x86_32.tar.gz' saved [2346062/2346062]
snowflake tmp # tar zxf skypeforasterisk-1.4_0.9.10-x86_32.tar.gz
snowflake tmp # cd skypeforasterisk-1.4_0.9.10-x86_32
snowflake skypeforasterisk-1.4_0.9.10-x86_32 # ls
chan_skype.c  chan_skype.conf.sample  chan_skype.exports  Makefile  README  res_skypeforasterisk.so  skypeforasterisk.h  version.h
snowflake skypeforasterisk-1.4_0.9.10-x86_32 # make
gcc -o chan_skype.o -c chan_skype.c -pipe -fPIC -DAST_MODULE=\"chan_skype\" -MD -MT chan_skype.o -MF .chan_skype.o.d -MP
gcc -o chan_skype.so -pthread -shared -Wl,--version-script,chan_skype.exports chan_skype.o
***********************************
Skype For Asterisk build completed.
Type 'make install' to install.
***********************************

Далее надо зарегистрировать канал.

snowflake skypeforasterisk-1.4_0.9.10-x86_32 # wget 'http://downloads.digium.com/pub/register/linux/register'
--2009-07-31 04:52:44--  http://downloads.digium.com/pub/register/linux/register
Resolving downloads.digium.com... 76.164.171.232
Connecting to downloads.digium.com|76.164.171.232|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1177820 (1.1M) [text/plain]
Saving to: `register'
100%[=================================================================================================>] 1,177,820   1.00M/s   in 1.1s
2009-07-31 04:52:45 (1.00 MB/s) - `register' saved [1177820/1177820]
snowflake skypeforasterisk-1.4_0.9.10-x86_32 # chmod +x register
snowflake skypeforasterisk-1.4_0.9.10-x86_32 # ./register
Digium Product Registration - Version 3.0.3
Copyright (C) 2004-2007, Digium, Inc.
Use the '-l' option to see license information for software
included in this program.
Please select a category
1 - Digium Products
2 - Cepstral Products
0 - Quit
Your Choice: 1
You selected 1, Digium Products
Please select a product
1 - Asterisk Business Edition
2 - Asterisk Business Edition C Expansion
3 - Asterisk For Smart Cube
4 - Asterisk For Smart Cube Expansion
5 - G.729 Codecc
6 - High Performance Echo Can
7 - Skype For Asterisk
8 - Skype For Asterisk (Beta)
9 - Fax for Asterisk
10 - Free Fax for Asterisk
11 - Vestec Speech Engine
0 - Quit
Your Choice: 8
You selected 8, Skype For Asterisk (Beta)
Please enter your Key-ID:

Ввести полученный код и установить канал:

snowflake skypeforasterisk-1.4_0.9.10-x86_32 # make install
install -m 755 -d /usr/lib/asterisk/modules
install -m 755 chan_skype.so /usr/lib/asterisk/modules
install -m 755 res_skypeforasterisk.so /usr/lib/asterisk/modules

Настраиваем chan_skype.conf

Конфигурационный файл с комментариями находится в /etc/asterisk/. Образец конфига находится в папке с исходными текстами. Простейший конфиг:

snowflake asterisk # cat chan_skype.conf  | grep -v '^;'
[general]
engine_directory=/tmp/skype
default_user=pbxware.ru
bind_address=x.x.x.x
bind_port=0
[pbxware.ru]
secret=xxx
context=default
exten=s
disallow=all
allow=ulaw
direction=incoming
auth_policy=accept:m-i-6
auth_policy=accept:martini
auth_policy=block

Загружаем модуль

Модуль подгрузится автоматически после рестарта Asterisk, или это можно сделать вручную:

snowflake*CLI> load chan_skype.so
[2009-07-31 05:00:22]   == Parsing '/home/asterisk.pbxware/etc/asterisk/chan_skype.conf': [2009-07-31 05:00:22] Found
[2009-07-31 05:00:22] DEBUG[3264]: core.cpp:1311 sfa_startup: License directory set to: /var/lib/asterisk/licenses
[2009-07-31 05:00:22] DEBUG[3269]: core.cpp:1424 sfa_startup: starting skyhost as: skypeforasterisk -z -f /var/spool/asterisk/skype/data
[2009-07-31 05:00:22] DEBUG[3269]: core.cpp:1426 sfa_startup: skyhost environment is : HOME=/var/spool/asterisk/skype
[2009-07-31 05:00:22] DEBUG[3268]: core.cpp:1512 sfa_startup: starting skypewatcher as: skypewatcher 3269
[2009-07-31 05:00:24] DEBUG[3267]: core.cpp:415 skyhost_watcher: got SkyHost Copyright (C) 2003-2008 Skype Technologies S.A.
[2009-07-31 05:00:24] DEBUG[3267]: core.cpp:415 skyhost_watcher: got Proprietary and confidential, do not share this application.
[2009-07-31 05:00:24] DEBUG[3267]: core.cpp:415 skyhost_watcher: got Ready to accept connections
[2009-07-31 05:00:24] DEBUG[3267]: core.cpp:420 skyhost_watcher: skyhost is ready!
[2009-07-31 05:00:24]     -- Launched Skype for username 'pbxware.ru'
[2009-07-31 05:00:24]   == Registered channel type 'Skype' (Skype For Asterisk Channel Driver)
[2009-07-31 05:00:24]   == Manager registered action SkypeBuddies
[2009-07-31 05:00:24]   == Manager registered action SkypeBuddy
[2009-07-31 05:00:24]   == Manager registered action SkypeAccountProperty
[2009-07-31 05:00:24]   == Manager registered action SkypeAddBuddy
[2009-07-31 05:00:24]   == Manager registered action SkypeRemoveBuddy
[2009-07-31 05:00:24]   == Manager registered action SkypeLicenseStatus
[2009-07-31 05:00:24]   == Manager registered action SkypeLicenseList
[2009-07-31 05:00:24]   == Registered custom function SKYPE_CALL_PROPERTY
[2009-07-31 05:00:24]   == Registered custom function SKYPE_ACCOUNT_PROPERTY
[2009-07-31 05:00:24]   == Registered custom function SKYPE_BUDDIES
[2009-07-31 05:00:24]   == Registered custom function SKYPE_BUDDY_FETCH
[2009-07-31 05:00:24]  Loaded chan_skype.so => (Skype For Asterisk Channel Driver)
snowflake*CLI>
snowflake*CLI> skype
login   logout  set     show
snowflake*CLI> skype
login   logout  set     show
snowflake*CLI> skype show
buddies   hostid    licenses  settings  user      users     version
snowflake*CLI> skype show licenses
Skype For Asterisk Licensing Information
========================================
Total licensed channels: 2
Licenses Found:
File: S4AB-CCKG2FR2G5PF.lic -- Key: S4AB-CCKG2FR2G5PF -- Expires: 2009-08-31 -- Host-ID: 39:c9:af:ea:f4:b1:5c:74:35:52:48:1b:09:57:38:c8:35:7c:f4:5c -- Channels: 2 (OK)
snowflake*CLI> skype show users
Skype UsersLI>
pbxware.ru: Logged In

Готово!

Тестовый звонок

snowflake*CLI>
[2009-07-31 03:37:43] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:37:43] DEBUG[25311]: AContactGroup.cpp:87 OnGroupChanged: Contact 'xpoison' added to CONTACTS_WAITING_MY_AUTHORIZATION group for user 'pbxware.ru'
[2009-07-31 03:37:43] DEBUG[25311]: AAccount.cpp:113 process_contact_authorization: Contact 'xpoison' requesting authorization from user 'pbxware.ru'
[2009-07-31 03:37:43] DEBUG[25311]: AAccount.cpp:148 process_contact_authorization: Contact 'xpoison' denied for user 'pbxware.ru'
[2009-07-31 03:37:43] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:37:43] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:37:43] DEBUG[25311]: AContactGroup.cpp:107 OnGroupChanged: Contact 'xpoison' removed from CONTACTS_WAITING_MY_AUTHORIZATION group for user 'pbxware.ru'
[2009-07-31 03:37:43] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:37:51] DEBUG[25311]: core.cpp:255 create_control_socket: creating socket sfa-control-0x82680a0-00000012
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:815 new_call: Incoming call for Skype user pbxware.ru from xpoison (Вадим)
[2009-07-31 03:37:51] -- Executing [s@default:1] Set("Skype/pbxware.ru-082350d0", "FROM_OUTSIDE=1") in new stack
[2009-07-31 03:37:51] -- Executing [s@default:2] Set("Skype/pbxware.ru-082350d0", "LANGUAGE()=ru") in new stack
[2009-07-31 03:37:51] WARNING[27311]: func_language.c:61 language_write: LANGUAGE() is deprecated; use CHANNEL(language) instead.
[2009-07-31 03:37:51] -- Executing [s@default:3] Goto("Skype/pbxware.ru-082350d0", "menu-main|s|1") in new stack
[2009-07-31 03:37:51] -- Goto (menu-main,s,1)
[2009-07-31 03:37:51] -- Executing [s@menu-main:1] Answer("Skype/pbxware.ru-082350d0", "") in new stack
[2009-07-31 03:37:51] -- Executing [s@menu-main:2] Wait("Skype/pbxware.ru-082350d0", "1") in new stack
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:37:51] DEBUG[27311]: core.cpp:1663 sfa_call_process: accepting socket connection from voice engine
[2009-07-31 03:37:51] DEBUG[27311]: core.cpp:1636 process_ve_frame: received audio socket address 127.0.0.1:48581
[2009-07-31 03:37:51] DEBUG[27311]: core.cpp:1773 sfa_call_set_audio_socket_address: sending audio socket address 127.0.0.1:13738
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:730 queue_event: got control event type 4 with subclass 4.
[2009-07-31 03:37:51] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:37:52] -- Executing [s@menu-main:3] Set("Skype/pbxware.ru-082350d0", "FROM_OUTSIDE=1") in new stack
[2009-07-31 03:37:52] -- Executing [s@menu-main:4] Set("Skype/pbxware.ru-082350d0", "CHANNEL(language)=ru") in new stack
[2009-07-31 03:37:52] -- Executing [s@menu-main:5] GotoIfTime("Skype/pbxware.ru-082350d0", "00:00-23:59|mon-sun|*|*?menu-work-time|s|1") in new stack
[2009-07-31 03:37:52] -- Goto (menu-work-time,s,1)
[2009-07-31 03:37:52] -- Executing [s@menu-work-time:1] Ringing("Skype/pbxware.ru-082350d0", "") in new stack
[2009-07-31 03:37:52] -- Executing [s@menu-work-time:2] Set("Skype/pbxware.ru-082350d0", "choice_counter=0") in new stack
[2009-07-31 03:37:52] -- Executing [s@menu-work-time:3] Wait("Skype/pbxware.ru-082350d0", "2") in new stack
[2009-07-31 03:37:54] -- Executing [s@menu-work-time:4] Gosub("Skype/pbxware.ru-082350d0", "call-record|401|1") in new stack
[2009-07-31 03:37:54] DEBUG[27311]: func_db.c:70 function_db_read: DB: rec_a/xpoison not found in database.
[2009-07-31 03:37:54] -- Executing [401@call-record:1] GotoIf("Skype/pbxware.ru-082350d0", "0?record") in new stack
[2009-07-31 03:37:54] DEBUG[27311]: func_db.c:70 function_db_read: DB: rec_b/401 not found in database.
[2009-07-31 03:37:54] -- Executing [401@call-record:2] GotoIf("Skype/pbxware.ru-082350d0", "0?record") in new stack
[2009-07-31 03:37:54] -- Executing [401@call-record:3] GotoIf("Skype/pbxware.ru-082350d0", "0?record") in new stack
[2009-07-31 03:37:54] -- Executing [401@call-record:4] Return("Skype/pbxware.ru-082350d0", "") in new stack
[2009-07-31 03:37:54] -- Executing [s@menu-work-time:5] Set("Skype/pbxware.ru-082350d0", "choice_counter=0") in new stack
[2009-07-31 03:37:54] -- Executing [s@menu-work-time:6] Set("Skype/pbxware.ru-082350d0", "TIMEOUT(response)=5") in new stack
[2009-07-31 03:37:54] -- Response timeout set to 5
[2009-07-31 03:37:54] -- Executing [s@menu-work-time:7] Set("Skype/pbxware.ru-082350d0", "TIMEOUT(digit)=3") in new stack
[2009-07-31 03:37:54] -- Digit timeout set to 3
[2009-07-31 03:37:54] -- Executing [s@menu-work-time:8] BackGround("Skype/pbxware.ru-082350d0", "zdravstujte") in new stack
[2009-07-31 03:37:54] WARNING[27311]: channel.c:3055 set_format: Unable to find a codec translation path from 0x4 (ulaw) to 0x0 (nothing)
[2009-07-31 03:37:54] -- <Skype/pbxware.ru-082350d0> Playing 'zdravstujte' (language 'ru')
[2009-07-31 03:37:55] -- Executing [s@menu-work-time:9] BackGround("Skype/pbxware.ru-082350d0", "record/vas-privetstvuet-ats-design") in new stack
[2009-07-31 03:37:55] -- <Skype/pbxware.ru-082350d0> Playing 'record/vas-privetstvuet-ats-design' (language 'ru')
[2009-07-31 03:37:58] -- Executing [s@menu-work-time:10] BackGround("Skype/pbxware.ru-082350d0", "to-contact-sales") in new stack
[2009-07-31 03:37:58] -- <Skype/pbxware.ru-082350d0> Playing 'to-contact-sales' (language 'ru')
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:730 queue_event: got control event type 4 with subclass 1.
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds
[2009-07-31 03:38:01] -- Executing [h@menu-work-time:1] ExecIf("Skype/pbxware.ru-082350d0", "0|Set|CDR(userfield)=") in new stack
[2009-07-31 03:38:01] DEBUG[27311]: chan_skype.c:3334 unlink_client_call: channel 0x82350d0
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3255 do_monitor: poll returned 1
[2009-07-31 03:38:01] DEBUG[27311]: chan_skype.c:3348 destroy_client_call: channel 0x82350d0
[2009-07-31 03:38:01] DEBUG[25311]: chan_skype.c:3245 do_monitor: entering poll for 2 fds

Красота! :-)

  • Posted: 2009-07-31 05:10 (Updated: 2009-07-31 05:13)
  • Author: litnimax
  • Categories: (none)
  • Comments (4)

Знакомство с софтфоном LinPhone

Не помню уже что искал, но набрел на страничку Yeaphone. Вот! Этого мне всегда не хватало - полноценно работающего с Linux USB телефона. Нет, я конечно же подключал самые разные USB телефоны… Но они выглядели как внешняя звуковая карта, так как кнопки на них не работали, а LCD выглядел неживым. Хоте нет! Какой-то девайс мне удалось запустить через usbhid, и я ловил нажатия его кнопок… Например, в консоли… Но как перенаправить их в отдельное устройство, я не докачал.

И вот, Yaphone:

The goal of the Yeaphone project is to provide a user interface on a Yealink USB handset (USB-P1K / P1KH / P4K) for the VoIP software Linphone thus making a PC keyboard and monitor unneccessary.

Итак, есть двое неизвестных:

  • Какой-то Yalink USB телефон
  • ПО LinPhone, которое к тому же и обладает консольной версией. С него и начнем.

Установка LinPhone

jaguar ~ # emerge -av linphone

These are the packages that would be merged, in order:
Calculating dependencies... done!
[ebuild  N    ] net-libs/libosip-3.1.0  641 kB
[ebuild  N    ] net-libs/ortp-0.15.0_p1  USE="ssl -debug -doc -examples -ipv6 -minimal -srtp" 486 kB
[ebuild  N    ] net-libs/libeXosip-3.1.0  479 kB
[ebuild  N    ] media-libs/speex-1.2_beta3_p2  USE="ogg sse" 1,024 kB
[ebuild  N    ] media-libs/mediastreamer-2.2.3_p1-r1  USE="X alsa gsm speex theora x264 -arts -debug -doc -examples -ilbc -ipv6 -jack -oss -portaudio -video" 634 kB
[ebuild  N    ] net-voip/linphone-3.1.1  USE="gtk ncurses nls -doc -ipv6 -video" LINGUAS="ru -cs -de -es -fr -hu -it -ja -nl -pl -pt_BR -sv" 7,627 kB
Total: 6 packages (6 new), Size of downloads: 10,888 kB
Would you like to merge these packages? [Yes/No]

No! Я не против SRTP, и iLBC и video тоже хочу!

jaguar ~ # euse -E srtp ilbc video video aesicm syslog
/etc/make.conf was modified, a backup copy has been placed at /etc/make.conf.euse_backup
jaguar ~ # emerge -av linphone

These are the packages that would be merged, in order:
Calculating dependencies... done!
[ebuild  N    ] net-libs/libosip-3.1.0  641 kB
[ebuild  N    ] net-libs/libsrtp-1.4.4-r1  USE="aesicm syslog -console -debug -doc" 492 kB
[ebuild  N    ] net-libs/ortp-0.15.0_p1  USE="srtp ssl -debug -doc -examples -ipv6 -minimal" 486 kB
[ebuild  N    ] net-libs/libeXosip-3.1.0  479 kB
[ebuild  N    ] media-libs/libsdl-1.2.13-r1  USE="X alsa audio dga directfb joystick video xinerama xv -aalib -arts -custom-cflags -esd -fbcon -ggi -libcaca -nas -opengl -oss -pulseaudio -svga" 3,295 kB
[ebuild  N    ] media-libs/speex-1.2_beta3_p2  USE="ogg sse" 1,024 kB
[ebuild  N    ] dev-libs/ilbc-rfc3951-0-r1  323 kB
[ebuild  N    ] media-libs/mediastreamer-2.2.3_p1-r1  USE="X alsa gsm ilbc speex theora video x264 -arts -debug -doc -examples -ipv6 -jack -oss -portaudio" 634 kB
[ebuild  N    ] media-plugins/mediastreamer-x264-1.1.7  321 kB
[ebuild  N    ] media-plugins/mediastreamer-ilbc-2.0.0  USE="-debug" 629 kB
[ebuild  N    ] net-voip/linphone-3.1.1  USE="gtk ncurses nls video -doc -ipv6" LINGUAS="ru -cs -de -es -fr -hu -it -ja -nl -pl -pt_BR -sv" 7,627 kB
Total: 11 packages (11 new), Size of downloads: 15,946 kB
Would you like to merge these packages? [Yes/No] Yes

Пошел "курить"… Вот так это делается в Gentoo.

Запуск LinPhone

Все интуитивно понятно. Что понравилось:

  • Это второй SIP софтфон под Linux с поддержкой Video! И если инициация и завершение вызова в Ekiga были похожи на разворот бульдозера, то linphone справляется с этим очень быстро.
  • При закрытии окна (клик на крестик) в режиме ожидания linphone свернулся в лоток. При закрытии окна в режиме разговора софтон свернулся в лоток, и завершил вызов. Пахнет продуманным юзабилити.
  • Включение / Отключение поддержки видео под рукой. Снова запах юзабилити. Что не нравилось в Ekiga, так это "утопленность" активации / деактивации поддержки видео. Мелочь.. Но нет ничего важнее мелочей!

Это первое, на что обратил внимание. Наверняка есть и косяки, но они всплывут позже :-)

Использование linphonec

LinPhone console version - наконец-то! Я помню свой восторг, когда перешел на использование Music Player Daemon (MPD) и конольного клиента MPC. Управлять с консоли проигрыванием музыки сперва было восхитительно, а потом вошло в привычку, такую же, как использование screen. И вот новый lifehack - консольный SIP клиент!

max@explorer ~ $ linphonec
Warning: UDP port 5060 seems already in use ! Cannot initialize.
Ready
Warning: video is disabled in linphonec, use -V or -C or -D to enable.
linphonec>

Уууу! Они не связали GUI и консольного клиента :-( В Music Player Daemon можно было запускать одновременно много клиентов, консольного, GUI, WEB, да какой угодно, а тут, похоже, два разных SIP клиента. Ладно, переварим. Кстати говоря, GUI linphone занимает 8.2M оперативной памяти, а linephonec - 1.5! Т.е., там где оперативка в дефиците, например, на 256M лэптопах, это будет решением.

max@explorer ~ $ linphonec
Ready
Warning: video is disabled in linphonec, use -V or -C or -D to enable.
linphonec> Registration on sip:sip.pbxware.ru successful.

Уф! Ну хоть базу эккаунтов и настройки они делят между собой.

linphonec> help
Commands are:
---------------------------
      help	Print commands help
      call	Call a SIP uri
 terminate	Terminate the current call
    answer	Answer a call
autoanswer	Show/set auto-answer mode
     proxy	Manage proxies
 soundcard	Manage soundcards
      ipv6	Use IPV6
     refer	Refer the current call to the specified destination.
       nat	Set nat address
      stun	Set stun server address
  firewall	Set firewall policy
 call-logs	Calls history
    friend	Manage friends
      play	play from a wav file
    record	record to a wav file
      quit	Exit linphonec
  register	Register in one line to a proxy
unregister	Unregister from default proxy
  duration	Print duration in seconds of the last call.
    status	Print various status information
---------------------------
Type 'help <command>' for more details.

Не Asterisk show commands конечно, но поглядим….

linphonec> proxy
Syntax error.
'proxy list' : list all proxy setups.
'proxy add' : add a new proxy setup.
'proxy remove <index>' : remove proxy setup with number index.
'proxy use <index>' : use proxy with number index as default proxy.
'proxy unuse' : don't use a default proxy.
'proxy show <index>' : show configuration and status of the proxy numbered by index.
'proxy show default' : show configuration and status of the default proxy.
linphonec> proxy list
****** Proxy 0 - this is the default one - *******
sip address: sip:sip.pbxware.ru
route:
identity: sip:700@sip.pbxware.ru
register: yes
expires: 600
registered: yes
linphonec>

Управление проксями из CLI. Good.

linphonec> call 710
Contacting sip:710@sip.pbxware.ru
linphonec> linphonec>

Пошел вызов!

linphonec> linphonec> terminate
Call ended
linphonec> linphonec> Request Cancelled.
linphonec>

Вызов завершен :-)

linphonec> call-logs
Outgoing call at Sat Jul 25 14:38:29 2009
From: <sip:700@sip.pbxware.ru>
To: <sip:710@sip.pbxware.ru>
Status: aborted
Duration: 0 mn 31 sec
linphonec>

Истрия звонков :-)

Так, а как насчет отладки?

max@explorer ~ $ linphonec --help
ERROR: bad arguments
usage: linphonec [-c file] [-s sipaddr] [-a] [-V] [-d level ] [-l logfile]
       linphonec -v
  -c  file             specify path of configuration file.
  -d  level            be verbose. 0 is no output. 6 is all output
  -l  logfile          specify the log file for your SIP phone
  -s  sipaddress       specify the sip call to do at startup
  -a                   enable auto answering for incoming calls
  -V                   enable video features globally (disabled by default)
  -C                   enable video capture only (disabled by default)
  -D                   enable video display only (disabled by default)
  -S                   show general state messages (disabled by default)
  -v or --version      display version and exits.
max@explorer ~ $

Понятно… Проверим….

max@explorer ~ $ linphonec -d 6 -S

Ой. Да, хватает отладочной информации….

На этом перввя серия обзора с софтфоном Linphone завершается. Впереди как минимум две серии:

  • Игры с linphonecsh - LinPhone Console Shell. Чую, еще та штучка. Вот что пишут разработчики на своем сайте:
     What could be the applications of this tool ? For example:
        * run VoIP calls from scripts
        * from web cgi pages
        * from javascript in a browser window...
    
    Дайте подумать… Это же callback для WEB сайта без размещения там Asterisk. не нужны call файлы или взаимодействие через AMI. Просто зарегистрировать клиента, и выдавать ему номер для контакта… Чудесно.
  • Надо купить телефон Yalink… Поглядим, что там у них на сайте… Где-то я видел все эти телефоны… Ба! да это же товарищи из SkypeMate, мы с ними на Cвязь-Экспоком'е 2009 познакомились! Ну-ка… Точно! Вот он - http://skypemate.ru/catalogue/?wid=4. Ну что же, значит, продолжение будет учень скоро!

Бизнес-форум интеграторов Asterisk

В Санкт-Петербурге 1-2 августа состоится долгожданное событие, которое позволит встретиться наиболее активным участникам сообщества Asterisk в России – сотрудникам и владельцам компаний-интеграторов, разработчикам, фрилансерам и энтузиастам – людям, которые посвящают Asterisk большую часть своего рабочего (а зачастую и свободного) времени.

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

PS. Кстати, началась регистрация на очередной Astricon. До 1го июля регистрация будет стоить всего $100. Спешите стать ранней пташкой, хотя я не рассчитываю что кто-то из русскоязычных читателей туда отправится.

  • Posted: 2009-07-19 19:51 (Updated: 2009-07-19 20:03)
  • Author: IgorG
  • Categories: (none)
  • Comments (0)