Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Отличное приложение 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 обрабатывает звонок, если надо
- проигрывает музыку, помещает вызов в [112]IVR или делает любое
- свойственное asterisk-у действие с звонком, затем если нужно соединить
- с реальным человеком, то звонок отправляется на нужного нам сотрудника
- обратно на sipbalanser, тот ищет у себя в локальной базе
- зарегистрированного User Agent-а и если находит отдает ему вызов.
- Часть 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 можно поставить из портов, пакетов и репозитариев, как
- удобно, а ключевые для нас компоненты лучше ставить из исходников,
- чтобы и версии последние были и весь процесс прочувствовать и понять
- что для этих компонентов нужно. Я описывать процесс установки не буду,
- т.к. сам ставил давно, и уже не помню всех подробностей, а ставить
- заного ради статьи - лень J Все что нужно знать о установке подробно
- расписано в README. Единственное для mediaproxy почему-то нет init
- файлов, но это не беда, их легко написать самостоятельно.
- Конфигурирование openser
- Перейдем к основному, как настроить kamailio. Где-то я возможно
- повторюсь с статьей о openser с voip.rus.net, но там описан процесс
- настройки применительно к старым версиям Openser, в новых версиях
- kamailio многое изменилось и процесс настройки слегка изменился,
- особенно это касается работы c NAT.
- Конфигурационный файл я аттачить не буду, а буду приводить куски в
- статье, если эти куски собрать во едино, то получится нужный
- конфигурационный файл. Делаю это я осознанно, чтобы не возникало
- желания пролистать статью, ибо "много букв" J залить
- конфигурационный файл себе и получить все косяки из-за слабого
- представления что где зачем.
- Я буду описывать не каждую строчку, т.к. предназначение некоторых
- банально, а некоторые я и сам не знаю зачем нужны, но в мануалах пишут
- что они нужны :)
- Настоятельно рекомендую тем кто не знаком с процедурой установления
- соединения 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 Code1\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");
- }
Add Comment
Please, Sign In to add comment