Rate this post

Про Asterisk, думается, слышал чуть ли не каждый админ — он встречается буквально на каждом шагу, будь то офисная АТС или нечто связанное с телекомом. Но если в первом случае его применение практически всегда оправданно, то во втором могут иметь место некоторые проблемы. Существуют ли альтернативы Asterisk?n

SIP: Краткое введение

Прежде всего стоит разобраться, как работает SIP в принципе и что это вообще за протокол. Итак, SIP — Session Initiation Protocol, служащий исключительно для целей установления и завершения сессии. Обмен пакетами от установления соединения до его завершения называется диалогом. Медиапоток по данному протоколу не передается — для этого есть протокол RTP. Инкапсулированный же в пакете SIP SDP (Session Description Protocol) определяет, в частности, и параметры медиапотока, такие как используемый кодек и тип данных.nnВажно понимать, что прохождение этих двух потоков — SIP/SDP и RTP (или, как говорят связисты, сигнализации и данных) — совершенно независимо друг от друга. Посмотрим, что происходит при нормальном вызове (очень упрощенно, правда).n

  • Вызывающая сторона инициирует соединение, посылая SIP-пакет INVITE, в нем же инкапсулирован и SDP с информацией о желаемых параметрах RTP. Этот пакет может пройти через несколько SIP-прокси — о них чуть ниже.
  • В зависимости от того, проходит ли вызов через SIP-прокси или нет, вызывающая сторона получает либо ответ от SIP-прокси TRYING (а уж прокси тем временем направляет INVITE вызываемой стороне) и следом RINGING, либо сразу RINGING. Пакет RINGING означает, что вызываемая сторона посылает сигнал абоненту «звоню» и теперь нужно ожидать, условно говоря, «поднятия трубки».
  • Как только вызываемая сторона поднимает трубку, она посылает SIP-пакет с кодом 200 «OK». В этот пакет снова инкапсулирована нагрузка SDP, содержащая параметры, которые поддерживаются этой стороной. Вызывающая же сторона, если параметры совпадают, посылает ответный пакет ACK.
  • Клиенты соединяются по протоколу RTP напрямую независимо от того, проходил ли предыдущий обмен пакетами SIP через SIP-прокси или нет. Происходит разговор.
  • При отбое отбивающая сторона посылает SIP-пакет BYE, а вторая сторона посылает опять же пакет ACK. Соединение завершено.

Теперь стоит разобраться с устройством сети SIP — какие логические сущности в ней вообще имеются.

nsip телефонияn

  • UAC и UAS — User Agent Client и User Agent Server. Очень условно их можно назвать вызывающей и вызываемой стороной. В SIP-терминалах и программных клиентах реализуется как UAC, так и UAS.
  • SIP-прокси занимается в основном маршрутизацией.
  • SIP-регистратор сохраняет информацию о логическом местоположении UAC/UAS.
  • SIP-редиректор предоставляет UAC/UAS информацию о маршрутизации и в случае необходимости перенаправляет вызов в другое место.
  • Сервер местоположения на самом деле представляет собой базу данных, где указано, какой адрес или какие адреса соответствуют тому или иному SIP URI. Фактически это лишь удобная абстракция.
  • B2BUA может находиться на месте SIP-прокси, но им не является. В случае с прокси терминал устанавливает соединение с другим терминалом напрямую (хоть оно и проходит через прокси, но лишь до определенного этапа. И прокси в общем случае не изменяет заголовки и тело SIP-пакета). В случае с B2BUA терминал сначала устанавливает с ним соединение, затем B2BUA устанавливает соединение с вызываемым терминалом, и в дальнейшем весь процесс происходит исключительно через него, с использованием двух независимых сессий — вызывающий терминал-B2BUA и B2BUA-вызывающий терминал. Применение B2BUA дает гораздо большую гибкость, нежели применение обычного SIP-прокси, — хотя бы потому, что он позволяет изменять тело SIP-пакета. Кроме того, по названным выше причинам его применяют и при тарификации вызовов. Но есть у него и недостаток — большее потребление памяти.

Стоит отметить, что зачастую логические сущности объединяются в одном ПО. Так, SIP-прокси чаще всего объединяется с регистратором и редиректором.nnSIP-прокси существуют двух типов — stateless и stateful.Если ты знаком с iptables, думаю, объяснять их различия нет необходимости. А если нет… Stateful SIP-прокси хранит со-стояние транзакции, то есть знает, какому пакету INVITE соответствует тот или иной пакет ACK. Stateless-прокси же ничего этого не знает и тупо передает пакет куда следует.nnИ тут мы плавно переходим к вопросу, какие еще свободные средства для работы с SIP-протоколом (помимо Asterisk,о котором знают все) существуют в *nix-системах. На данный момент есть два проекта, предназначенные для этой цели, —Kamailio и OpenSIPS. Они имеют общего предка, так что подробно описывать их различия смысла нет. В статье будет рас-смотрен OpenSIPS.n

ЧТО ТАКОЕ OPENSIPS? УСТАНОВКА

OpenSIPS обеспечивает функциональность практически всех серверных компонентов, которые были упомянуты выше, —от SIP-прокси до B2BUA. Отличие же его от Asterisk’а заключается, во-первых, в том, что OpenSIPS манипулирует паке-тами и сессиями SIP на более низком уровне, чем это делаетAsterisk, а во-вторых, Asterisk по сути является комбайном,например содержит в себе, помимо SIP, медиа сервер. Кроме того, Asterisk не поддерживает масштабирование.nnOpenSIPS имеет давнюю историю: как минимум он (если считать и его предшественников) не младше Asterisk, а с учетом того, что SIP для последнего неродной, возможно, даже и старше. Он также поддерживает и модульную архитектуру,при этом модулей там предостаточно. В настоящий момент с нуля разрабатывается ветка 2.0, которая будет иметь другую архитектуру, — текущая закладывалась без учета части современных реалий, которые в те времена еще и не прогнозировались. Однако данная ветка находится в состоянии активной разработки, а я предпочитаю стабильные версии,поэтому здесь будет описана версия 1.11.3, которая, кстати,является LTS.nnИтак, поехали устанавливать:n

$ wget -qO - http://apt.opensips.org/key.asc | sudo apt-key add -n$ sudo sh -c "echo 'deb http://apt.opensips.org/stable111 main' > /etc/apt/sources.list.d/opensips.list"n$ sudo apt-get updaten$ sudo apt-get install opensips opensips-console

НАЧАЛЬНАЯ НАСТРОЙКА OPENSIPS

В файле /etc/opensips/opensipsctlrc нужно настроить имя или адрес SIP-домена:n

SIP_DOMAIN=192.168.56.103

Имя SIP-домена не обязательно должно совпадать с DNS-именем хоста, на котором запущен OpenSIPS, однако некоторые SIP-терминалы не позволяют указывать его отдельно.В тестовом случае это не настолько критично, но, если сервер будет находиться в продакшене, этот момент лучше учесть.Также для нормальной работы все равно рекомендуетсяDNS-сервер со сконфигурированными полями NAPTR и SRV.В общем-то, базовая настройка на этом закончена, пора пере-ходить к написанию скрипта маршрутизации.n

СКРИПТ МАРШРУТИЗАЦИИ

Основной конфигурационный файл OpenSIPS (в моем случае он был расположен по пути /etc/opensips/opensips.cfg) делится на три части:n

  • Глобальные параметры, такие как логирование с отладкой,адреса, на которых OpenSIPS слушает, настройка количества дочерних процессов.
  • Параметры модулей. Сюда относятся путь к каталогу с мо-дулями, список самих загружаемых модулей и их параметры, например для модуля транзакций tm предусмотрена настройка самых разнообразных таймеров.
  • Наконец, логика маршрутизации. Именно здесь находитсясамая вкусная часть OpenSIPS. Каждый SIP-пакет, приходящий на (и проходящий через) OpenSIPS, обрабатывает-ся в соответствии с данной логикой. Разумеется, именно поэтому она может быть достаточно сложной. Как правило, здесь имеются несколько маршрутов — основной, аналогичный точке входа в какую-либо программу и несколь-ко дополнительных, направление на которых вызывается из основного. Дополнительные маршруты аналогичны функциям в процедурных языках программирования.

В принципе, для создания базового конфигурационного файла можно использовать команду osipsconfig, но его, темне менее, придется править ручками. Ниже будут приведены наиболее важные части из файла конфигурации с комментариями (к слову, допустимы как однострочные комментариив *nix-стиле, так и многострочные в стиле C++). Итак, часть секции глобальных параметров:n

# Указываем на какм адресе или порте будет случать OpenSIPSnlisten=udp:192.168.56.103:5060n# Отключаем поддержкуTCP и TLSndisable_tcp=yesndisable_tls=yes

А теперь посмотрим некоторые параметры модулей:n

# Пути к модулямnmpath="/usr/lib/opensips/modules"n# Загружаем модули sl и tmnloadmodule "sl.so"nloadmodule "tm.so"nn#Настраиваем параметры модуля tmnmodparam("tm", "fr_timeout", 5)nmodparam("tm", "fr_inv_timeout", 30)nmodparam("tm", "restart_fr_on_each_reply", 0)nmodparam("tm", "onreply_avp_mode", 1)n# Загружаем модуль-обертку SIGNALINGnloadmodule "signaling.so"n<...>

Пожалуй, стоит обратить внимание на данные модули.Первым загружается модуль sl, который отвечает за stateless-прокси. Параметры его мы не трогаем. Следующая строчказагружает модуль tm, который отвечает за stateful-прокси, —и уж тут как раз некоторые параметры нужно изменить. Рас-смотрим, что они означают:n

  • fr_timeeout — интервал между запросом и предварительным ответом (таким как trying). Транзакция на моментпредварительного ответа еще не завершена. Этот тайм-аут истекает, если нет никакого ответа, при наличии жепредварительного ответа он сбрасывается. В секундах;
  • fr_inv_timeout — интервал между предварительным и окончательным ответом. Тайм-аут истекает, если с моментаполучения предварительного ответа окончательного таки не пришло. Опять же в секундах;• restart_fr_on_each_reply — указывает, должен ли fr_timeoutначинать отсчет сначала, если вызываемая сторона регу-лярно шлет предварительные ответы. Нулевое значение —false, любое другое — true;
  • onreply_avp_mode — как будут обрабатываться AVP(Atribute-Value pair) в маршруте Reply. Если не вдаватьсяв подробности, 1 означает, что AVP, относящиеся к данно-му маршруту, можно будет видеть и использовать и в не-которых других ветвях скрипта маршрутизации. Это, тем не менее, снижает производительность.

Модуль SIGNALING представляет собой обертку вокруг мо-дулей tm и sl для удобства работы.nnТеперь наконец можно перейти к разбору структуры последней секции, которая, собственно, и является скриптом обработки запросов.nnПервым идет так называемый main route block, в который попадают все пакеты. Как уже говорилось выше, этот блок аналогичен точке входа в стандартные программы. Рассмотрим, что он делает:n

    n

  1. Пакет входит в данный блок, и происходят некоторые проверки.
  2. Если пакет не предназначен нам, мы его направляем в блокrelay.
  3. А если же он пришел к нам, мы, в зависимости от нужд,совершаем какие-то действия. Отмечу, что здесь можно направлять поток обработки в другие блоки, аналогично тому, как в программе на си из функции main() можно вызывать другие функции, описанные в коде программы.

nКроме блока типа route (к которому относится и названный выше и который обрабатывает входящие запросы), существует еще несколько типов блоков маршрутизации. Опишу неко-торые из них:• branch_route — позволяет задавать действия в пределаходной транзакции. Понятно, что это актуально лишь в слу-чае stateful-маршрутизации;nn• failure_route — обрабатывает полученные отрицатель-ные (с кодом большим или равным 300) ответы — при-чем ответы как приходящие извне, так и генерируемыесамим OpenSIPS. Опять же актуально только для stateful-маршрутизации;nn• onreply_route — обрабатывает все нормальные ответы.Может быть как stateful — в случае если мы специальноуказываем, что ответ принадлежит какой-либо транзак-ции, так и stateless — если указываем, что маршрутизацияглобальная;nn• error_route — обрабатывает ошибки при парсинге SIP-пакетов.nnПоскольку даже самый простой скрипт маршрутизации,который почти ничем не занимается (реализует только базовый регистратор и редиректор), достаточно сложен, мы его разберем по косточкам:n

# Точка входаnroute{n# # Следующий условный оператор осуществляет проверку поля заголовка MaxForwards - счетчик «прыжков» маршрутизации. Если его значение равно нулю, посылается ответ 483 и прекращается обработка данного пакета. Помимо этого, функция mf_process_maxfwd_header() смотрит. а имеется ли вообще такой заголовок в пакете, если нет, то он создается и устанавливается в заданное значение - в данном случае 10.nif (!mf_process_maxfwd_header("70")) {nsl_send_reply("483","Too Many Hops");nexit;n}n# Далее мы проверяем наличие тега у поля To, который показывает, относится ли данный пакет к какому-либо диалогу.nif (has_totag()) {n# Пакет OPTIONS, помимо собственно запроса доступных опций, обычно используется в качестве средства проверки соединения. Следующая проверка служит для того, чтобы удостовериться, действительно ли пакет предназначен именно нашему прокси.nif (is_method("OPTIONS") &&nuri==myself && (! uri=~"sip:.*[@]+.*")) {noptions_reply();nexit;n}n# Смотрим, есть ли в пакете указание, куда его маршрутизировать дальше. Функция loose route() сама по себе очень многозначная (как и многие другие функции), и, если таковое указание имеется, она действует в соответствии с секцией 16.12 RFC 3261 за некоторыми исключениями (о них лучше почитать в документации).nif (loose_route()) {n# В серьезных скриптах маршрутизации здесьnи спрятана вся логика - например, осуществляется аккаунтинг. Однако, поскольку скрипт у нас исключительно простой, пакет мы просто маршрутизируем по направлению, которое в нем и задано.nroute(relay);n} else {n#В случае же, если пакет не содержит маршрута, мы смотрим, не является ли он пакетом ACK, пришедшим в ответ на сообщение об ошибке, и переправляем его куда следует.nif (is_method("ACK")) {nif ( t_check_trans() ) {nt_relay();nexit;n} else {n# Если же данный запрос ACK не принадлежит никакой транзакции, мы просто его игнорируем.nexit;n}n}n#В остальных же случаях мы отправляем сообщение с кодом "404", аналогичное подобному же в HTTP.nsl_send_reply("404","Not here");n}nexit;n}n#Обрабатываем запросы, не относящиеся к заданному диалогу. Запрос CANCEL мы не трогаем и пересылаем дальше.nif (is_method("CANCEL")) {nif (t_check_trans()) {nt_relay();n}nexit;n}n#Функция t check trans() тоже имеет двойное назначение - если запрос не относится ни к ACK, ни к CANCEL, но относится к какой- то транзакции ретрансляции, она его ретранслирует дальше, что следующая строчка и делает. t_check_trans();n# Фильтруем пакеты, у которых есть поле Route, но не задано поле To (за исключением пакета ACK), и логируем о подобных попытках.nt_check_trans();n#Если запрос адресован не нам, добавляем поле Record-Route для принудительной маршрутизации SIP-трафика через наш прокси.nif (loose_route()) {nxlog("L_ERR",n"Attempt to route with preloaded Route'sn[$fu/$tu/$ru/$ci]");nif (!is_method("ACK")) {nsl_send_reply("403",n"Preloaded Route denied");n}n}n#Если в запросе не фигурирует URI, который хоть как-то относится к нашему серверу, мы его отправляем в route(relay).nif (!is_method("REGISTER|MESSAGE")) {nrecord_route();n}n#Поддержку presence (сообщений о статусе присутствия абонента) тоже не реализуем, для чего отключаем методы.nif (!uri==myself) {nroute(relay);n}n#Поддержку presence (сообщений о статусе присутствия абонента) тоже не реализуем,nдля чего отключаем методы.nPUBLISH и SUBSCRIBEnif (is_method("PUBLISH|SUBSCRIBE")) {nsl_send_reply("503",n"Service Unavailable");nexit;n}n#Обработка запроса REGISTER. Для упрощения скрипта мы даем возможность регистрироваться всем, безо всякой аутентификации. Кроме того, база местоположений по тем же соображениям временная, в настоящую БД ничего не пишетсяnif (is_method("REGISTER")) {nif (!save("location", "m")) {nsl_reply_error();n}nexit;n}n#Функция lookup() проверяет, есть ли у нас в базе местоположений данный пользователь. Если его нет, мы создаем новую транзакцию и возвращаем "404". Опять же в серьезных скриптах здесь еще и проверяются поддерживаемые клиентом методы, чего мы не делаем.nif (!lookup("location")) {nt_newtran();nt_reply("404", "Not Found");nexit;n}nroute(relay);n}n#Блок relay, который и обрабатывает все проходящие пакеты.nroute[relay] {n#В случае INVITE мы смотрим, есть ли отрицательный результат транзакции, и, если есть, отправляем в соответствующий блок.nif (is_method("INVITE")) {nt_on_failure("fail");n}n#Наконец, пропускаем пакет дальше и, если он почему-либо не проходит,выдаем ошибку "500".nif (!t_relay()) {nsend_reply("500",n"Internal Server Error");n}n}n# Блок fail, о котором было упомянуто выше.nfailure_route[fail] {n#Если транзакция была отменена, мы просто выходим из блока.nif (t_was_cancelled()) {nexit;n}n}

По завершении конфигурирования нужно проверить конфиг на наличие синтаксических ошибок с помощью следующей команды:IP телефонияnnКонфигурация учетной записи в LinphonennAsterisk IP телефонияnnСоздание профиля в Twinklen

# sudo opensips -C

Затем включаем возможность запуска OpenSIPS в файлеn

/etc/default/opensips:nRUN_OPENSIPS=yesnИ стартуем его:n$ sudo service opensips start

Можно приступать к тестированию.n

ТЕСТИРОВАНИЕ КОНФИГУРАЦИИ

Для тестирования используем два клиента, запущенныена двух машинах. Я использовал Linphone и Twinkle. В пер-вом для добавления учетной записи открываем настройки(Linphone → Preferences) и переходим на вкладку Manage SIPAccounts, где и добавляем с помощью кнопки Add.nnВ поле YourSIP identity ставим SIP-идентификатор (вида sip:имя_пользо-вателя@SIP-домен), а в поле SIP Proxy address пишем адрес(не SIP-домен!) SIP-прокси.В случае же с Twinkle при первом запуске будет пред-ложено создать учетную запись. Назови профиль и выберитип Wizard для создания учетной записи. Далее вбиваем всенужное. Единственное отличие от Linphone состоит в том,что в Twinkle имя пользователя пишется отдельно от домена.nnПосле регистрации, чтобы убедиться, что клиенты доступ-ны, можно посмотреть список местоположений пользователей на сервере, для чего нужно набрать MI-команду:n

# sudo opensipsctl fi fo ul_dump

Эта команда вызывает функцию MI (интерфейса управления) модуля usrloc — ul_dump, которая и выводит список пользователей, фактически зарегистрированных на сервере.nПосле этого уже можно звонить. С этим не должно возник-нуть особых проблем, но если они все-таки будут — используй функцию xlog() для логирования и tcpdump/Wireshark для ис-следования пакетов.nnSIP телефонияn

ЗАКЛЮЧЕНИЕ

OpenSIPS позволяет манипулировать с пакетами SIP чрезвычайно гибко. Однако порог вхожденияв него достаточно высокий — тре-буется не только досконально знатьпротокол, но и понимать, что дела-ет та или иная функция, и иметьпредставление, для чего в общемможет понадобиться какой-либомодуль, даже сам список которыхвыглядит внушительно.nnВ статье, разумеется, был за-тронут лишь самый минимумиз того, что умеет OpenSIPS, —не были рассмотрены ни автори-зация при регистрации, ни акка-унтинг, ни, тем более, настройкаOpenSIPS в качестве B2BUA. Темне менее общее представлениео возможностях OpenSIPS и о син-таксисе его файла конфигурацииу тебя должно сложиться. Ну а в том случае, если тебе это нуж-но, — дальше сможешь разобраться и сам.nn