Работа через маршрутизаторы с трансляцией IP адресов - NAT.
Существует две реализации для обработки вызовов, проходящих через маршрутизаторы с трансляцией адресов (NAT) - это метод с использованием mediaproxy и с использованием rtpproxy. Дальнейшее содержимое этого раздела описывает применение обоих этих методов.
В почтовом списке рассылок "seruser" ведутся большие дебаты о том, какой из этих методов лучше! Оба они описаны в этом документе, но из-за того факта, что создатели этого документа использовали mediaproxy в своих решениях, следует то, что остальная часть этого документа полагается на решение этой задачи с использованием mediaproxy. Если появиться участники, которые хотели бы дополнить материал о решениях на базе rtpproxy, тогда он появиться в будущих версиях этой документации.
Метод обработки вызовов, проходящих через NAT, с использованием Mediaproxy.
Какие действия должны быть выполнены в этом файле ser.cfg:
- Вводиться специализированная обработка вызовов, которая является необходимой при работе с SIP клиентами, которые находятся позади устройств с NAT, таких как DSL маршрутизатор или корпоративной системы сетевой защиты (firewall).
Mediaproxy - это одно из двух решений для поддержки клиентов за NAT устройствами для сервера SER. Другое решение - это rtpproxy. Решение Mediaproxy известно как far-end (на далеком участке) решение для обхода проблемы, связанной прохождением IP пакетов через маршрутизатор с трансляцией адресов (NAT), это означает, что все аспекты, связанные с применением маршрутизаторов с NAT, решаются в точке, где располагается сам SIP прокси сервер, а не в точке, где расположен SIP клиент.
Существует множество преимуществ в обработки проблем, связанных с NAT, в точке, где располагается сам SIP прокси сервер. Основная - это то, что конфигурация для SIP клиентов производиться намного проще. Вот еще некоторые важные преимущества, связанные с применением mediaproxy:
- Поддержка DNS записей SRV дает возможность распределять нагрузку.
- Mediaproxy может быть инсталлировано на другом сервере, где не запущен SER. Этим можно уменьшить нагрузку на сервер, где работает Ваш SIP прокси сервер.
- Поддержка мониторинга через web интерфейс входит в поставку mediaproxy.
Mediaproxy - это отдельный программный продукт, который поставляется отдельно от SER. В дистрибутиве SER содержится только связной механизм, который дает возможность ему работать с работающим mediaproxy. Этот связующий механизм называется "модуль mediaproxy". На самом деле это диспетчерский модуль, который может управлять работой с одним или более mediaproxy серверами.
Обратите внимание: Для нормального функционирования mediaproxy, он должен быть настроен так, чтобы "слушать" на "реальном" IP адресе. Кроме того, в реальных конфигурациях, которые применяются в мире, mediaproxy обычно устанавливается не на сервере, где работает SER, а на отдельном сервере. Обратитесь к приложению для информации относительно настройки mediaproxy. Кроме того, версию mediaproxy, которая использовалась для тестирования этих конфигурационных файлов, можно получить по адресу http://ONsip.org.
Файл конфигурации ser.cfg для поддержки прозрачной работы через NAT маршрутизаторы с использованием Mediaproxy.
Наше решение по обходу проблем, связанных с прохождением IP пакетов через NAT маршрутизаторы, обычно упоминается, как прозрачное прохождение NAT маршрутизаторов, т.к. нет никаких различий между SIP клиентами с публичными IP адресами и клиентами, находящихся за маршрутизаторами с трансляцией адресов.
В этой конфигурации сервера SER, все проблемы связанные с применением NAT устройств, обрабатываются незаметно для SIP клиентов, что не требует какой-либо особой их настройки. Мы не используем STUN, потому что STUN добавляет другие сложности, которые можно избежать.
Для общения с устройствами, находящимися за NAT, мы ввели использование mediaproxy. Mediaproxy - это приложение, проксирующее RTP трафик, которое не является частью сервера SER. О процессе установки Mediaproxy и о скриптах его запуска можно узнать в приложении.
Проблема клиентов, находящихся за маршрутизаторами с NAT довольно сложна, потому что есть множество аспектов, которые нужно учитывать при работе с SIP клиентами, которые находятся за маршрутизатором с NAT, связанные с предоставлением информации, содержащей правильные адреса, для эффективного и правильного выполнения прозрачного проксирования RTP трафика. Пожалуйста, обратитесь к секции описывающей работу через NAT, STUN и проксирование RTP потоков, для всестороннего обзора всех этих аспектов.
Вот краткое резюме главных проблем, связанных с прохождением вызовов через маршрутизаторы с NAT, на которые следует обратить внимание:
- Клиенты, находящиеся за NAT, должны поддерживать открытым SIP порт, обычно это порт с номером 5060. Если этот порт будет закрыт на NAT маршрутизаторе клиента, тогда попытки совершить вызов этого абонента окончатся неудачей, т.к. SIP прокси сервер не сможет передать SIP клиенту, который находиться за NAT, SIP сообщение INVITE. Однако, в этом случае, этот клиент все еще имеет возможность совершать исходящие вызовы.
- Для протокола RTP, могут, а обычно и используются, случайные номера портов. Это означает, что пользователи не смогут открыть порт на их NAT устройстве для протокола RTP.
- Сообщения re-INVITE, должны быть обработаны особым образом, чтобы воспрепятствовать разрывам RTP потоков в середине вызова. Если это случается, то это обычно приводит к односторонней слышимости или вообще отсутствием звука, после обработки сообщения re-INVITE. Стоит отметить: причиной этого может стать то, что в SIP RFC3261 определяется то, что SIP UA может изменить кодеки, номера RTP портов и т.д., в процессе вызова. Комбинация этих проблем состоит из того, что довольно трудно определить, находиться ли каждая из SIP сторон за устройством с NAT или нет, при обработке сообщения re-INVITE.
Итак, как же мы будем решать эти проблемы, связанные с применением NAT? Вот решения этих проблем:
- Конфигурация сервера SER, представленная в этом разделе, использует в своих интересах тот факт, что мы можем заставить SIP прокси сервер держать открытыми соединения через NAT, используя установку natping_interval в mediaproxy. Сделав это, мы, таким образом, обеспечиваем то, что порт 5060 для клиентов, находящихся за NAT, будет доступен с нашего SIP прокси сервера, и мы будем в состоянии в любое время послать ему сообщение INVITE.
- Mediaproxy дает возможность серверу SER не обращать внимания на то, какой RTP порт использует SIP клиент. Это делается так, что наши два SIP клиента полагают, что они общаются непосредственно друг с другом, когда в действительности они работают через сервер, который проксирует медиапотоки, и пересылает RTP данные от одного SIP клиента к другому и обратно. Все это обрабатывается автоматически, когда обрабатывается инициирующее SIP сообщение INVITE и происходит установка сеанса связи. Обратите внимание: В этом примере конфигурации - ser.cfg, мы проксируем RTP потоки только тогда, когда один или более SIP клиентов находиться за устройством с трансляцией адресов. В тех случаях, если оба SIP клиента имеют "реальные" IP адреса, мы не проксируем RTP потоки, т.к. оба SIP клиента могут непосредственно связаться друг с другом. Это шаг к построению масштабируемой VoIP платформы.
- Мы используем в своих интересах тот факт, что мы можем внедрить наш собственный NAT индикатор в заголовочное поле "Record-Route" оригинального сообщения INVITE. Таким образом, мы можем искать этот специальный NAT индикатор при обработке сообщений re-INVITE. Если этот индикатор присутствует, тогда мы знаем, что должны использовать проксирование RTP потоков. Замечание: Эта методика вставки NAT индикатора в заголовочное поле "Record-Route" была предложена Jan Janak из IPtel. Он является одним из разработчиков ядра проекта и довольно авторитетный человек по части этой темы. Обратите внимание: Эта методика внедрения NAT индикатора в сообщения INVITE не требуется, если используется rtpproxy, потому что он может обрабатывать такие ситуации без дополнительной помощи.
Листинг файла ser.cfg, где задействован Mediaproxy.
debug=3 fork=yes # До сих пор мы запускали сервер SER, как обычный процесс. Начиная с этого момента, # мы будем запускать его как демона (в фоновом режиме). Директива fork заставляет # сервер SER запуститься в фоновом режиме (как демон). Это требуется для запуска сервера, # с использованием загрузочного init.d скрипта, описание которого можно найти в приложении. log_stderror=no # Вследствие того, что мы запускаем сервер SER, как процесс, выполняющийся в фоновом режиме, # мы должны установить значение директивы log_stderror в "no", чтобы не мешать, запущенному # серверу SER, работать в фоновом режиме. listen=192.0.2.13 # ЗДЕСЬ ДОЛЖЕН БЫТЬ ВАШ IP АДРЕС port=5060 children=4 dns=no rev_dns=no fifo="/tmp/ser_fifo" fifo_db_url="mysql://ser:heslo@localhost/ser" loadmodule "/usr/local/lib/ser/modules/mysql.so" loadmodule "/usr/local/lib/ser/modules/sl.so" loadmodule "/usr/local/lib/ser/modules/tm.so" loadmodule "/usr/local/lib/ser/modules/rr.so" loadmodule "/usr/local/lib/ser/modules/maxfwd.so" loadmodule "/usr/local/lib/ser/modules/usrloc.so" loadmodule "/usr/local/lib/ser/modules/registrar.so" loadmodule "/usr/local/lib/ser/modules/auth.so" loadmodule "/usr/local/lib/ser/modules/auth_db.so" loadmodule "/usr/local/lib/ser/modules/uri.so" loadmodule "/usr/local/lib/ser/modules/uri_db.so" # Мы загружаем модуль uri, чтобы получить доступ к функции has_totag(). # Эта функция необходима для обработки re-INVITE сообщений, и будет описана далее. loadmodule "/usr/local/lib/ser/modules/domain.so" # Нам необходимо загрузить модуль domain, потому что модуль mediaproxy содержит # зависимости от него, в части функций: is_uri_host_local() и is_from_local(). loadmodule "/usr/local/lib/ser/modules/mediaproxy.so" # В этом месте мы включаем использование mediaproxy. Эта директива уже ввела # в заблуждение множество людей, которые еще только осваивают сервер SER, # потому что эта директива подразумевает только то, что в SER включена поддержка mediaproxy, # однако, mediaproxy - это внешняя программа. Этой директивой мы загружаем только # модуль mediaproxy, который является диспетчером между сервером SER и запущенным # приложением mediaproxy. loadmodule "/usr/local/lib/ser/modules/nathelper.so" # Мы используем некоторые функции модуля nathelper, для определения, находиться ли SIP UA # за маршрутизатором с трансляцией адресов или нет. Важный момент, который нужно понять # - это то, что включение в конфигурацию модуля nathelper, еще не означает того, # что мы будем использовать rtpproxy (или другое приложение для проксирования RTP трафика, # аналогичное по функциональности с mediaproxy). Также, тут стоит отметить, что тут nathelper # упоминается в том же контексте, что и rtpproxy, потому что большинство людей, # которые используют rtpproxy, также использует и nathelper. loadmodule "/usr/local/lib/ser/modules/textops.so" # Модуль textops предоставляет нам сервисные функции для работы с текстом, # поиска подстрок в строке, и для проверки существования определенных заголовочных полей. modparam("auth_db|domain|uri_db|usrloc","db_url","mysql://ser:heslo@localhost/ser") modparam("auth_db", "calculate_ha1", 1) modparam("auth_db", "password_column", "password") modparam("nathelper", "rtpproxy_disable", 1) # Так как мы используем mediaproxy, а не rtpproxy, то мы должны сообщить модулю nathelper, # чтоб он не пытаться взаимодействовать с rtpproxy. Если бы мы не указали этот параметр, # то у нас в syslog появилось бы множество сообщений об ошибках, сообщающих о том, # что невозможно найти rtpproxy. modparam("nathelper", "natping_interval", 0) # Наша система поддержки устройств за маршрутизаторами с NAT использует mediaproxy, # и мы решили использовать механизм поддержки мапинга портов на NAT маршрутизаторе # всегда в активном состоянии, используя keep-alive механизм mediaproxy, который будет # регулярно посылать ping пакеты SIP клиентам. Вследствие этого, мы должны запретить # аналогичный механизм для rtpproxy, т.к. мы в нем уже не нуждаемся. modparam("mediaproxy","natping_interval", 30) # Параметр natping_interval модуля mediaproxy - это очень критическая установка, # которое задает, как часто наш SIP прокси сервер будет отправлять ping пакеты # зарегистрированным SIP клиентам за NAT. Большинство NAT маршрутизаторов # будут держать соответствие портов в открытом состоянии в течение минуты или двух, # таким образом, мы определим 30 секунд для этого интервала. # # Это заставляет SER сервер отправлять 4-байтовый UDP пакет каждому SIP клиенту # каждые 30 секунд. Это все, что нужно, чтобы поддерживать открытым порт до SIP клиентов, # находящимися за маршрутизатором с NAT. # # Обратите внимание: По некоторым сведениям, некоторые маршрутизаторы с NAT # не позволяют использовать механизм keep-alive со стороны публичной части маршрутизатора. # Таким образом, многие клиентские устройства имеют свою собственную реализацию # механизма keep-alive. Если у Вас, через некоторое время, возникает проблема # с односторонней слышимостью, то, возможно, Вы столкнулись именно с этой проблемой. # Единственное решение этой проблемы заключается в том, чтобы включить # на пользовательской стороне механизм keep-alive. Кроме того, имейте в виду, что ситуация, # когда механизм keep-alive задействован как на SIP прокси сервере, так и у SIP клиента # - является вполне нормальной ситуацией. modparam("mediaproxy","mediaproxy_socket", "/var/run/mediaproxy.sock") # Сервер SER и диспетчер mediaproxy общаются через стандартный Unix socket, # который мы и определяем тут. modparam("mediaproxy","sip_asymmetrics","/usr/local/etc/ser/sip-clients") # Возможно, для Mediaproxy понадобиться знать, является ли SIP UA асимметричным в той части, # что номер порта, с которого передаются SIP сообщения, не совпадает с тем, # на который они принимаются. Большинство SIP UA является симметричными, это значит, # что они принимают свои SIP сообщения по тому же порту, с которого они отправляют # свои SIP сообщения. Однако, если Вы обнаружите у себя асимметричных клиентов, # которые не могут быть корректно обработаны, тогда Вы можете определить # их заголовочные поля User-Agent в указанном файле. # # Пример, по умолчанию, этого файла можно найти в поставке сервера SER в директории <ser-sources>/modules/mediaproxy/config/sip-asymmetric-clients. Этот файл примера # был скопирован и переименован, согласно значению, определенному в этой директиве. modparam("mediaproxy","rtp_asymmetrics","/usr/local/etc/ser/rtp-clients") # Возможно, для Mediaproxy понадобиться знать, является ли SIP UA асимметричным в той части, # что номер порта, с которого передаются данные по RTP протоколу, не совпадает с тем, # на который они принимаются. Если Вы обнаружите у себя клиентов, асимметричных # в части RTP протокола, тогда Вы можете определить их заголовочные поля User-Agent # в указанном файле. # # Пример, по умолчанию, этого файла можно найти в поставке сервера SER в директории # <ser-sources>/modules/mediaproxy/config/rtp-asymmetric-clients. Этот файл примера # был скопирован и переименован, согласно значению, определенному в этой директиве. modparam("usrloc", "db_mode", 2) modparam("registrar", "nat_flag", 6) # Когда SIP клиенты пытаются зарегистрироваться на нашем SIP прокси сервере, # нам необходимо указать модулю registrar, каким способом ему сохранять информацию, # связанную с NAT, для каждого конкретного UA. Мы делаем это, используя флаг 6, # который был произвольно выбран (но ранее определен в параметрах раздела loadmodule). # Мы можем определить и любое другое целое число для этого параметра, но флаг с номером 6 # является принятым стандартом для параметра nat_flag. # # Если параметр nat_flag будет установлен до вызова функции save(), которая сохраняет # информацию о местонахождении клиента, тогда сервер SER сохранит информацию, # связанную с NAT, так же как и установит значение флага в соответствующем поле # таблицы местоположений, базы данных MySQL. Сделав так, мы можем вызывать функцию # lookup(location), при обработке SIP сообщений и, для клиентов находящимися # за маршрутизаторами с NAT, будет установлен флаг 6. modparam("rr", "enable_full_lr", 1) route { # ----------------------------------------------------------------- # Sanity Check Section # Секция проверки параметров. # ----------------------------------------------------------------- if (!mf_process_maxfwd_header("10")) { sl_send_reply("483", "Too Many Hops"); return; }; if (msg:len > max_len) { sl_send_reply("513", "Message Overflow"); return; }; # ----------------------------------------------------------------- # Record Route Section # Секция записи маршрутов. # ----------------------------------------------------------------- if (method=="INVITE" && client_nat_test("3")) { # Если получено INVITE сообщение, и именно от SIP клиента, находящегося за маршрутизатором # с NAT, то мы должны вставить специальный NAT флаг, который будет обнаружен нашим SIP прокси # сервером в том случае, если будет получено re-INVITE сообщение. Этот вставленный флаг # может быть обнаружен в нашем блоке свободной маргрутизации для определения того, # является ли отправитель сообщения, клиентом, который находиться за маршрутизатором с NAT или нет. record_route_preset("192.0.2.13:5060;nat=yes"); # ЗДЕСЬ ДОЛЖЕН БЫТЬ ВАШ IP АДРЕС # Если отправитель сообщения находиться за маршрутизатором с NAT, мы явно задаем заголовочное # поле Record-Route, используя функцию record_route_preset(). Мы передаем этой функции IP адрес # нашего SIP прокси сервера. Если Ваш SIP прокси сервер находиться за маршрутизатором, # который поддерживает возможности Application Level Gateway "Шлюз Прикладного Уровня" (ALG), # например, из серии Cisco 3600, то тогда Вам необходимо использовать физический адрес RFC1918 # вашего SIP прокси сервера, т.к. ALG маршрутизатор сaм перезапишет частный IP адрес. # # Стоит отметить: Что те, кто знаком с rtpproxy, может задаваться вопросом, почему этот шаг необходим # с тех пор, как rtpproxy не требует вставки этого флага: nat=yes. Причина того, что он требуется # для mediaproxy и не требуется для rtpproxy в том, что функция use_media_proxy(), # которая будет описана далее в этом примере, создаст новый канал проксирования RTP трафика, # если Вы вызовите эту функцию и заголовочное поле <Call-ID> не будет найдено в списке # активных сеансов mediaproxy. # # Соответствующая функция в rtpproxy имеет возможность указать rtpproxy, принимать какие-либо меры # только в том случае, если предыдущее заголовочное поле <Call-ID> найдено в списке активных # сеансов rtpproxy. # # Эти различия затрагивают также и блок кода loose_route(), т.к. пользователи, использующие mediaproxy, # должны использовать флаг nat=yes, при обработке re-INVITE сообщений, а для пользователей rtpproxy # - этого делать не надо. Исходя из этих аспектов, можно сделать вывод, что rtpproxy является несколько # более простым в использовании. } else if (method!="REGISTER") { record_route(); # Если от SIP клиента, находящегося за маршрутизатором с NAT, получено сообщение не INVITE # и не REGISTER типа, то только тогда мы вызываем функцию record_route(), для гарантии того, # чтобы сообщения проходили через наш SIP прокси сервер с вышестоящих и нижестоящих # SIP прокси серверов или со шлюзов в публичную телефонную сеть (PSTN). }; # ----------------------------------------------------------------- # Call Tear Down Section # Секция обработки отмены вызовов # ----------------------------------------------------------------- if (method=="BYE" || method=="CANCEL") { # В любое время, когда мы получаем SIP сообщение BYE или CANCEL, то мы должны предполагать, # что они вызываются именно для того вызова, соединение которого было установлено через mediaproxy. # Таким образом, в этом случае мы должны попытаться закончить сессию проксирования RTP трафика. # Совершенно безопасно вызывать функцию end_media_session(), даже для вызовов, # для которых не было создано канала проксирования RTP трафика. end_media_session(); # Указываем mediaproxy, закончить сессию проксирования RTP трафика для текущего вызова. }; # ----------------------------------------------------------------- # Loose Route Section # Секция свободной маршрутизации. # ----------------------------------------------------------------- if (loose_route()) { if ((method=="INVITE" || method=="REFER") && !has_totag()) { # В наших требованиях по работе с клиентами, находящимися за маршрутизаторами # с трансляцией адресов, есть пункт, что мы должны специальным образом обрабатывать # сообщения 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, приняты в рамках уже установленного соединения. sl_send_reply("403", "Forbidden"); return; }; if (method=="INVITE") { if (!proxy_authorize("","subscriber")) { # Если получено сообщение INVITE, то мы должны отправить клиенту информацию, # необходимую для дайджест авторизации. Если отправитель сообщения не может # представить правильные данные дайджест авторизации, тогда сервер SER # ответит сообщением с кодом ошибки 403. # # Обратите внимание: Этот пример требует того, чтобы вызывающий и вызываемый клиент # были зарегистрированы на SIP маршрутизаторе. В будущих примерах мы подробнее # остановимся на этом моменте, и покажем, как добавить "проверенные" SIP устройства, # такие, как шлюзы в публичную телефонную сеть proxy_challenge("","0"); return; } else if (!check_from()) { sl_send_reply("403", "Use From=ID"); return; }; consume_credentials(); if (client_nat_test("3") || search("^Route:.*;nat=yes")) { # Теперь мы проверяем NAT статус отправителя сообщения re-INVITE, вызывая функцию # client_nat_test("3"). Также ищем заголовочное поле <Route>, содержащий тег ";nat=yes", # который будет вставлен ранее, обсуждаемой ранее, функцией record_route_preset(). # Если найден тэг ";nat=yes", тогда вызывающий абонент находиться за маршрутизатором с NAT. setflag(6); # Если отправитель сообщения находиться за маршрутизатором с NAT или re-INVITE сообщение # содержит флаг "nat=yes", тогда мы устанавливаем флаг 6 для использование его в дальнейшем. # Этот флаг мы можем проверить функцией reply_route. use_media_proxy(); # Для начала проксирования RTP потоков, мы вызываем функцию use_media_proxy(). # Она будет общаться с внешним mediaproxy сервером, заставляя это открыть UDP порты # для обоих клиентов, или управлять существующим сеансом RTP проксирования для уже # установленного вызова, в зависимости от заголовочного поля <Call-ID>. # Вызов use_media_proxy() вызывает перезапись содержимого SDP, в части IP адреса и портов, # которые выделил mediaproxy сервер для RTP потоков }; }; route(1); return; }; # ----------------------------------------------------------------- # Call Type Processing Section # Секция, обрабатывающая различные типы вызовов. # ----------------------------------------------------------------- if (uri!=myself) { route(4); # В случае, когда сообщение больше не нуждается в обработке нашим SIP маршрутизатором, # мы вызываем маршрутизационный блок, с поддержкой клиентов за NAT маршрутизаторами, # где, при необходимости, используется mediaproxy перед пересылкой сообщения адресату. route(1); return; }; if (method=="ACK") { route(1); return; } else if (method=="CANCEL") { # Теперь сообщения CANCEL явно обрабатываются отдельно. Сообщения CANCEL # могут быть безопасно обработаны простым вызовом t_relay(), т.к. сервер SER автоматически # найдет соответствие для CANCEL и оригинального INVITE сообщения. Следовательно, #просто передаем это сообщение обработчику, по умолчанию. route(1); return; } else if (method=="INVITE") { route(3); return; } else if (method=="REGISTER") { route(2); return; }; lookup("aliases"); if (uri!=myself) { route(4); # Включаем, при необходимости, mediaproxy, перед отправкой сообщения адресату. route(1); return; }; if (!lookup("location")) { sl_send_reply("404", "User Not Found"); return; }; route(1); } # ----------------------------------------------------------------- # Default Message Handler # Обработчик SIP сообщений, по умолчанию. # ----------------------------------------------------------------- route[1] { t_on_reply("1"); # При работе с клиентами, находящимися за маршрутизаторами с NAT, мы должны корректно # обработать ответные сообщения, которые возвращаются клиенту. К этим ответным сообщениям # можно получить доступ в сервере SER при помощи блока reply_route. # # Сервер SER позволяет Вам определять несколько блоков reply_route, которые могут выполнять # множество задач. Здесь мы определяем, что любые ответные сообщения нужно передать # блоку reply_route[1], который определен в конце файла ser.cfg. # # Для вызова блока reply_route, Вам просто нужно установить вызов обработчика до вызова t_relay(), # что мы тут и сделали. if (!t_relay()) { if (method=="INVITE" || method=="ACK") { # Если сообщение невозможно отправить и это либо INVITE, либо ACK сообщение, тогда мы должны # освободить ресурсы, предварительно установленного сеанса mediaproxy. end_media_session(); }; sl_reply_error(); }; } # ------------------------------------------------------------------------ # REGISTER Message Handler # Обработчик SIP сообщения REGISTER. # ------------------------------------------------------------------------ route[2] { sl_send_reply("100", "Trying"); if (!search("^Contact:[ ]*\*") && client_nat_test("7")) { # Если проверка client_nat_test () возвращает true, тогда, мы должны установить флаг 6, # чтобы сообщить модулю регистрации, что клиент находиться за маршрутизатором с NAT. # # Стоит отметить, что мы должны вызвать функцию client_nat_test() только, если обрабатываемое # SIP сообщение содержит заголовочное поле <Contact:>. # Иначе в syslog появиться сообщение об ошибке. # # Замечание: Здесь вы видите регулярное выражение для проверки наличия заголовочного поля # <Contact:> - ^Contact: [] *\*, которое производит проверку полей следующим образом: # # Проверка считается удачной, если строка начинается с текста Contact: и после него есть # любое число пробельных символов (или их нет) (шаблон: []* ), затем должен следовать # символ звездочки ( \* ). Такой шаблон используется по той причине, что SIP клиент может # сформировать это поле в виде: <Contact:*> или <Contact:'пробел[ы]' *>, оба эти варианта # являются правильными. Допуская любое число пробельных символов, мы захватываем этой # проверкой все возможные варианты форматирования этого поля. # # Замечание: Заголовочное поле Contact: будет содержать символ звездочки ("*"), # когда SIP клиент хочет "отменить регистрацию" на SIP прокси сервере. # Фактически, множество SIP клиентов могут и/или будут посылать сообщение REGISTER # с заголовочным полем "Contact: *", при их перезагрузке. setflag(6); # Если клиент действительно находиться за маршрутизатором с NAT, то мы должны сообщить # модулю регистратора сервера SER, что он должен сохранить тот IP адрес и порт, # с которого поступило это SIP сообщение. Эта информация будет использоваться последующими # вызовами функции lookup(location), для поиска реального IP адреса SIP клиента, который # находиться за маршрутизатором с NAT. fix_nated_register(); # Функция Fix_nated_register() специально используется для обработки сообщений REGISTER # от клиентов, находящихся за NAT. Она сильно отличается от функции fix_nated_contact(), # т.к. указанная функция не будет изменять URI в заголовочном поле <Contact:>, # тогда как fix_nated_contact - будет. # # Fix_nated_register () только добавит параметры в заголовочное поле <Contact:> URI, согласно # RFC3261 10.3, описывающем обработку сообщений REGISTER. Если бы мы использовали # здесь функцию fix_nated_contact(), тогда мы бы имели проблемы с совместимостью, когда SIP UA # не обрабатывали бы ответные сообщения "200 OK", которыми сервер SER отвечал бы # после успешной обработки сообщения REGISTER. Это бы приводило к тому, что SIP клиенты # теряли бы регистрацию на сервере. force_rport(); # Функция Force_rport () добавляет полученный IP порт в самое начало заголовочных полей "via" # SIP сообщения. Это дает возможность направлять последующие SIP сообщения на нужный порт # для последующих SIP транзакций. }; if (!www_authorize("","subscriber")) { www_challenge("","0"); return; }; if (!check_to()) { sl_send_reply("401", "Unauthorized"); return; }; consume_credentials(); if (!save("location")) { sl_reply_error(); }; } # ----------------------------------------------------------------- # INVITE Message Handler # Обработчик SIP сообщений INVITE. # ----------------------------------------------------------------- route[3] { if (client_nat_test("3")) { # SIP сообщения INVITE имеют несколько иные требования по тестированию клиентов, на предмет # наличия маршрутизатора с NAT, по сравнению с сообщением REGISTER. Тут производиться проверка # на наличие в SIP сообщении IP адрес, согласно RFC1918, в заголовочном поле <Contact:>, # и получено ли сообщение от SIP UA с того IP адреса и номера порта, которые определены в поле "VIA". setflag(7); # Если мы определяем, что SIP клиент находиться за маршрутизатором с NAT, то мы устанавливаем флаг 7, # который мы будем использовать в дальнейшем. # # Обратите внимание: Функция client_nat_test() только определяет, находиться ли отправитель # SIP сообщения за маршрутизатором с NAT. В этой точке файла ser.cfg мы еще не знаем, # является ли получатель SIP сообщения, устройством, которое находиться за NAT или нет. force_rport(); # Добавляем номер порта, с которого получено сообщение, в заголовочное поле "VIA". fix_nated_contact(); # Теперь мы перезаписываем заголовочное поле <Contact:> SIP сообщение, чтобы оно # содержало IP адрес, соответствующий реальному адресу SIP клиента, который # находиться за маршрутизатором с NAT. }; if (!proxy_authorize("","subscriber")) { proxy_challenge("","0"); return; } else if (!check_from()) { sl_send_reply("403", "Use From=ID"); return; }; consume_credentials(); lookup("aliases"); if (uri!=myself) { route(4); # Включаем, при необходимости, mediaproxy, перед отправкой сообщения адресату. route(1); return; }; if (!lookup("location")) { # Теперь мы ищем контактную информацию о вызываемом абоненте. # # Замечание: тонкое, но очень важная деталь заключается в том, что флаг 6 будет # устанавливаться вызовом функции lookup(location) в блоке route[2], если # вызываемая сторона будет найдена в таблице местоположений базы данных MySQL # и будет помечена, как устройство, находящееся за NAT. Причина установки Флага # 6 заключается в том, что мы это определили для модуля регистрации параметром nat_flag. sl_send_reply("404", "User Not Found"); return; }; route(4); # Включаем, при необходимости, mediaproxy, перед отправкой сообщения адресату. # # Теперь, когда мы позаботились обо всех элементах, связанных с прохождением сообщения # через маршрутизаторы с NAT, мы можем безопасно переслать INVITE сообщение адресату. route(1); } # ----------------------------------------------------------------- # NAT Traversal Section # Секция поддержки прохождения сообщений, для клиентов за NAT # ----------------------------------------------------------------- route[4] { # Маршрут [4] - блок маршрутизации, созданный для удобства. Он включает использование # mediaproxy, если любой отправитель (флаг 7) или получатель (флаг 6) сообщения находиться # за маршрутизатором с трансляцией адресов. if (isflagset(6) || isflagset(7)) { if (!isflagset(8)) { # Флаг 8 используется для проверки того, что mediaproxy уже был один раз задействован # для нашего вызова. Если, по ошибке, mediaproxy будет вызван более одного раза, # то в SIP сообщении будет повреждено содержимое SDP, потому что вызов use_media_proxy() # неправильно его изменит. # # Если флаг 8 установлен, то это предотвращает повторный вызов блока маршрутизации route[4]. setflag(8); use_media_proxy(); }; }; } onreply_route[1] { # Тут мы вводим в использования обработчик сообщений - reply_route. Блок reply_route # определяется точно так же, как и любой другой блок в сервере SER. Единственное отличие # - это то, что он выполняется при вызове функции onreply_route. # # Любые сообщения, передающие в этот блоку, будут возвращены оригинальному отправителю. # Вы можете представить эти сообщения, как ответы на запросы, которые произвела # вызывающая сторона. Типы сообщений, которые попадают в этот блок, будут иметь # целочисленный код ответа (возврата), которые очень похожи на коды ответов в HTTP протоколе. # Например, сюда попадут сообщения с кодами возврата 200, 401, 403, и 404. if ((isflagset(6) || isflagset(7)) && (status=~"(180)|(183)|2[0-9][0-9]")) { # В этом файле ser.cfg нас только интересуют коды ответа 180, 183, и все сообщения с кодами # возврата серии 2xx, для клиентов, находящихся за маршрутизаторами с NAT. Как показано тут, # мы можем проверить статус при помощи регулярного выражения. Если найден любой из этих кодов # ответа, тогда эта проверка вернет TRUE. # # Важная вещь, которую стоит отметить - мы можем проверить набор флагов в других маршрутизационных # блоках, т.к. на них также будет распространяться зона видимости этих флагов. Следовательно, # это флаги - индикаторы NAT, для вызывающего и вызываемого клиента доступны в этом блоке. # Если установлен флаг 6 - вызывающий абонент находиться за NAT, и если установлен флаг 7, # то вызываемый абонент находиться за NAT. if (!search("^Content-Length:[ ]*0")) { # Мы должны вызывать функцию use_media_proxy () только для SIP сообщений, которые имеют # правильное значение параметра <Contact> в теле SDP. Итак, тут мы проверяем, правильность # параметра "c=", просто проверяя длину тела SDP. Предполагаем, что, если имеется тело SDP # в SIP сообщении, тогда имеется в нем и параметр "c=", следовательно, мы можем вызвать # функцию use_media_proxy(). # # Если бы мы вызывали функцию use_media_proxy () без всяких проверок, то, вероятно, # в syslog появлялись бы сообщения об ошибках. use_media_proxy(); }; }; if (client_nat_test("1")) { # В завершение, мы перезаписываем в сообщении заголовочное поле <Contact:>, # чтобы оно содержало реальный IP адрес и порт, SIP клиента, находящегося за маршрутизатором с NAT. fix_nated_contact(); }; }
Использование ser.cfg, предназначенного для работы с клиентами, находящимися за маршрутизаторами с NAT, используя Mediaproxy.
Теперь мы сделали наш сервер SER на 100 % совместимым с клиентами, работающими через NAT, сохраняя работоспособность всех наших SIP клиентов, работающих без NAT. Для проверки работоспособности клиентов за NAT необходимо убедиться, что у вас запущен mediaproxy, работающий на том же физическом сервере, что и сервер SER.
Когда мы запустили mediaproxy, и запустили сервер SER, то Вы можем без проблем регистрировать ваших SIP клиентов. Вне зависимости, находиться ли SIP клиент за маршрутизатором с трансляцией адресов или нет.
Чтобы увидеть, были ли RTP потоки направлены через mediaproxy, Вы можете выполнить скрипт на Питоне (Python), расположенный в /usr/local/mediaproxy/sessions.py (предполагается, что Вы установили mediaproxy в этой директории.)
Вы также можете установить утилиту web мониторинга, расположенную в директории /usr/local/mediaproxy/web/, если у Вас имеется web сервер Apache, запущенный на машине, где работает SIP прокси сервер. Скриншот этой утилиты web мониторинга можно увидеть по адресу: http://www.ag-projects.com/docs/MediaSessions.pdf
Использованные материалы: http://siprouter.onsip.org/doc/gettingstarted/ch08.html