Stun turn сервер что это
WebRTC: фреймворк ICE, STUN и сервера TURN
WebRTC (Web Real Time Communication) — это проект с открытым исходным кодом, позволяющий создавать одноранговые (P2P) аудио- и видеосвязи через JavaScript API.
Для того чтобы установить P2P-соединение, одноранговые пользователи должны общаться о типах носителей, которыми они хотят обмениваться, сообщить друг другу, когда они хотят начать или прекратить общение, а также должны найти друг друга в сети. Весь этот процесс называется сигнализацией, но нас интересует только последняя часть — как соединить одноранговые узлы, максимально избегая посредников.
Это не так просто, как кажется. Устройства пользователей обычно не имеют публичного IP-адреса или могут быть не допущены до установления какого-либо прямого соединения. Вот почему нам нужна платформа Interactive Connectivity Establishment (ICE).
Если бы два пользователя совершали телефонный звонок, то один должен был бы только набрать номер телефона другого, а другой должен был бы только принять звонок. Каждый телефонный номер соответствует только одному устройству, а их вполне достаточно, чтобы обеспечить прямое соединение.
С другой стороны, в Интернете исторически не хватало “номеров” для каждого подключенного устройства. С IPv4 было доступно только около 4 миллиардов адресов. Недостаток адресов был решен путем группировки многих устройств под одним общим адресом с маршрутизатором, переводящим адреса в пакеты, проходящие через него. Этот процесс называется трансляцией сетевых адресов (NAT).
Существуют различные типы NAT, но некоторые из них выделяют публичный IP-адрес и порт для потоков UDP (то, что нам нужно). Поэтому, когда нужно создать P2P-соединение с одноранговым узлом, первая задача состоит в том, чтобы выяснить, за каким типом NAT вы находитесь, и если он есть, получить IP-адрес и порт, который сможете дать своему контакту.
В этом поможет протокол STUN (Session Traversal Utilities for NAT). Необходимо предоставить сервер STUN при попытке установить P2P-соединение. В WebRTC вы предоставляете его при создании объекта JavaScript, представляющего соединение:
Чтобы определить, находитесь ли вы за NAT, и получить публичный IP-адрес, если это возможно, агент ICE отправит запрос на указанный вами сервер STUN. Если NAT есть, он установит свой публичный адрес и порт в заголовке сообщения. Сервер STUN попытается пинговать этот публичный адрес с разных IP-адресов, чтобы проверить, находитесь ли вы за NAT, и если да, то какой это тип. Если все пойдет правильно, то сервер STUN вернет адрес и порт.
Если все работает так же гладко для контакта, с которым вы пытаетесь общаться, он также получит свой публичный адрес. Но на этом проблемы не заканчиваются.
Реализации NAT
Не все NAT реализованы одинаково и могут отличаться в том, как они позволяют пакетам проходить. Некоторые реализации NAT, такие как NAT один к одному, позволят установить P2P-соединение. Некоторые, например симметричные, этого не делают.
NAT один к одному (или Full Cone NAT)
В этой реализации, как только внутренний IP-адрес/порт duo сопоставляется с внешним IP-адресом/портом duo, все пакеты, поступающие на внешний адрес/порт, независимо от того, откуда они поступают, будут отправлены через исходный внутренний адрес/порт.
Если за таким NAT стоят одноранговые узлы, то для установления P2P-соединения достаточно получить публичный IP-адрес и порт обоих одноранговых узлов.
Симметричный NAT
В симметричных NAT’ах внешний IP-адрес/порт зависит от внутреннего IP-адреса/порта duo и назначения. Запросы от 198.145.1.2: 3000 к серверу TURN сопоставляются с заданным IP-адресом и портом внешнего источника. Но если один и тот же внутренний хост отправляет пакет в другое место назначения, например в одноранговый узел, с которым он пытается связаться, внешний IP-адрес/порт будет отличаться. Кроме того, внешний хост должен получить пакет от внутреннего хоста, прежде чем он сможет отправить пакет обратно.
Если контакты находятся за симметричным NAT, они не смогут общаться. Вот почему нам нужно другое решение: TURN.
Когда прямое соединение не может быть установлено, связь должна проходить через сервер TURN. TURN обозначает “Traversal Using Relays around NAT”. Как следует из названия, соединение будет проходить через ретрансляционный сервер.
Это очевидно ухудшает производительность и имеет финансовые издержки. В то время как сервер STUN имеет дело с очень маленькими и легкими запросами, сервер TURN ретранслирует весь разговор, что генерирует гораздо больше трафика. Вот почему вы можете найти публичные серверы STUN, но не публичные серверы TURN (по крайней мере, ни один владелец которых не знает, что он является публичным).
ICE в WebRTC
Чтобы быть установленным в TURN, для WebRTConnection нужно указать URL-адрес вашего сервера в объекте RCTPeerConnection:
Серверы TURN по причине, описанной выше, обычно защищены паролем.
Агент ICE сначала попытается установить соединение непосредственно между одноранговыми узлами и переключится на опцию TURN только в том случае, если это не сработает. Вам не нужно заботиться об этом самостоятельно, нужно только слушать событие onicecandidate RTCPeerConnection. Оно будет срабатывать каждый раз, когда обнаруживается кандидат ICE. Затем вам нужно отправить кандидата своему контакту через свой сигнальный механизм:
Когда вы получаете кандидата от своего контакта, нужно доставить его агенту ICE, вызвав addIceCandidate. Остальная часть переговоров и окончательный отбор кандидатов затем осуществляется агентом ICE. В конце переговоров о кандидате, в случае успеха, коллеги могут начать общение.
WebRTC: фреймворк ICE, STUN и сервера TURN
Sep 11, 2020 · 4 min read
WebRTC (Web Real Time Communication) — это проект с открытым исходным кодом, позволяющий создавать одноранговые (P2P) аудио- и видеосвязи через JavaScript API.
Для того чтобы установить P2P-соединение, одноранговые пользователи должны общаться о типах носителей, которыми они хотят обмениваться, сообщить друг другу, когда они хотят начать или прекратить общение, а также должны найти друг друга в сети. Весь этот процесс называется сигнализацией, но нас интересует только последняя часть — как соединить одноранговые узлы, максимально избегая посредников.
Это не так просто, как ка ж ется. Устройства пользователей обычно не имеют публичного IP-адреса или могут быть не допущены до установления какого-либо прямого соединения. Вот почему нам нужна платформа Interactive Connectivity Establishment (ICE).
Если бы два пользователя совершали телефонный звонок, то один должен был бы только набрать номер телефона другого, а другой должен был бы только принять звонок. Каждый телефонный номер соответствует только одному устройству, а их вполне достаточно, чтобы обеспечить прямое соединение.
С другой стороны, в Интернете исторически не хватало “номеров” для каждого подключенного устройства. С IPv4 было доступно только около 4 миллиардов адресов. Недостаток адресов был решен путем группировки многих устройств под одним общим адресом с маршрутизатором, переводящим адреса в пакеты, проходящие через него. Этот процесс называется трансляцией сетевых адресов (NAT).
Существуют различные типы NAT, но некоторые из них выделяют публичный IP-адрес и порт для потоков UDP (то, что нам нужно). Поэтому, когда нужно создать P2P-соединение с одноранговым узлом, первая задача состоит в том, чтобы выяснить, за каким типом NAT вы находитесь, и если он есть, получить IP-адрес и порт, который сможете дать своему контакту.
В этом поможет протокол STUN (Session Traversal Utilities for NAT). Необходимо предоставить сервер STUN при попытке установить P2P-соединение. В WebRTC вы предоставляете его при создании объекта JavaScript, представляющего соединение:
Чтобы определить, находитесь ли вы за NAT, и получить публичный IP-адрес, если это возможно, агент ICE отправит запрос на указанный вами сервер STUN. Если NAT есть, он установит свой публичный адрес и порт в заголовке сообщения. Сервер STUN попытается пинговать этот публичный адрес с разных IP-адресов, чтобы проверить, находитесь ли вы за NAT, и если да, то какой это тип. Если все пойдет правильно, то сервер STUN вернет адрес и порт.
Если все работает так же гладко для контакта, с которым вы пытаетесь общаться, он также получит свой публичный адрес. Но на этом проблемы не заканчиваются.
Реализации NAT
Не все NAT реализованы одинаково и могут отличаться в том, как они позволяют пакетам проходить. Некоторые реализации NAT, такие как NAT один к одному, позволят установить P2P-соединение. Некоторые, например симметричные, этого не делают.
NAT один к одному (или Full Cone NAT)
В этой реализации, как только внутренний IP-адрес/порт duo сопоставляется с внешним IP-адресом/портом duo, все пакеты, поступающие на внешний адрес/порт, независимо от того, откуда они поступают, будут отправлены через исходный внутренний адрес/порт.
Если за таким NAT стоят одноранговые узлы, то для установления P2P-соединения достаточно получить публичный IP-адрес и порт обоих одноранговых узлов.
Симметричный NAT
В симметричных NAT’ах внешний IP-адрес/порт зависит от внутреннего IP-адреса/порта duo и назначения. Запросы от 198.145.1.2: 3000 к серверу TURN сопоставляются с заданным IP-адресом и портом внешнего источника. Но если один и тот же внутренний хост отправляет пакет в другое место назначения, например в одноранговый узел, с которым он пытается связаться, внешний IP-адрес/порт будет отличаться. Кроме того, внешний хост должен получить пакет от внутреннего хоста, прежде чем он сможет отправить пакет обратно.
Если контакты находятся за симметричным NAT, они не смогут общаться. Вот почему нам нужно другое решение: TURN.
Когда прямое соединение не может быть установлено, связь должна проходить через сервер TURN. TURN обозначает “Traversal Using Relays around NAT”. Как следует из названия, соединение будет проходить через ретрансляционный сервер.
Это очевидно ухудшает производительность и имеет финансовые издержки. В то время как сервер STUN имеет дело с очень маленькими и легкими запросами, сервер TURN ретранслирует весь разговор, что генерирует гораздо больше трафика. Вот почему вы можете найти публичные серверы STUN, но не публичные серверы TURN (по крайней мере, ни один владелец которых не знает, что он является публичным).
ICE в WebRTC
Чтобы быть установленным в TURN, для WebRTConnection нужно указать URL-адрес вашего сервера в объекте RCTPeerConnection:
Серверы TURN по причине, описанной выше, обычно защищены паролем.
Агент ICE сначала попытается установить соединение непосредственно между одноранговыми узлами и переключится на опцию TURN только в том случае, если это не сработает. Вам не нужно заботиться об этом самостоятельно, нужно только слушать событие onicecandidate RTCPeerConnection. Оно будет срабатывать каждый раз, когда обнаруживается кандидат ICE. Затем вам нужно отправить кандидата своему контакту через свой сигнальный механизм:
Когда вы получаете кандидата от своего контакта, нужно доставить его агенту ICE, вызвав addIceCandidate. Остальная часть переговоров и окончательный отбор кандидатов затем осуществляется агентом ICE. В конце переговоров о кандидате, в случае успеха, коллеги могут начать общение.
WebRTC для любопытных (часть 1)
Книга довольно поверхностно объясняет как работает WebRTC «под капотом», для подробностей надо читать RFC. Ссылки на RFC различных используемых протоколов буду приводить. Стоит особо отметить главу «Отладка», где неплохо описываются идеи того, как отлаживать различные проблемы с сетью, задержками и прочим.
Что такое Webrtc?
Помимо JavaScript протокол WebRTC реализован также и на других языках програмирования. Можно найти множество реализаций серверов, библиотек, реализующих протокол, примером может стать реализация на go: github.com/pion/webrtc. Пишется реализация и на rust: https://github.com/webrtc-rs/webrtc (проект довольно интересный, потому что это переписываение pion/webrtc на rust).
Протокол WebRTC поддерживается в IETF в группе rtcweb. API WebRTC задокументировано в W3C как webrtc-pc.
Приемущества WebRTC
Далее приводится список того, что даст вам WebRTC. Список не полный, это просто примеры того, что заслуживает внимания. Не волнуйтесь, если какие-то слова вам не знакомы, в течение следующих частей разберем их подробнее.
Итак, приемущества WebRTC:
Множество различных реализаций
Можно работать прямо из браузера
Перепрофилированная существующая технология, то есть не изобретали колес, когда делали WebRTC
Контроль за перегруженностью
Задержка (latency, имеется в виду задержка аудио и/или видеопотока) в пределах 1 секунды
WebRTC это набор разных технологий
Это тема, для объяснения которой потребуется целая книга. Для начала разобъем ее на четыре части:
Эти четыре шага идут друг за другом, каждый предыдущий шаг должен успешно завершиться чтобы начался следующий.
Интересный факт в WebRTC это то, что каждый шаг использует множество других протоколов!
Каждому из этих шагов посвящена отдельная часть, но пока что будет полезным рассмотреть каждый шаг «с высоты птичьего полета».
Сигналинг или как агенты находят друг друга в сети
Когда запускается WebRTC-агент, он не знает с кем ему соединиться, и какого рода информацией он будет обмениваться. Сигналинг (Signaling) решает эту проблему! Сигналинг нужен для того, чтобы два агента могли найти и вызвать друг друга в сети перед тем, как начать обмен информацией.
Сигналинг использует существующий протокол SDP (Session Description Protocol). SDP это простой текстовый протокол. Каждое SDP-сообщение состоит из пар ключ-значение, расположенных в строгом порядке (rfc4566), которые в свою очередь составляют набор медиа-секций. SDP-сообщения, которыми обмениваются WebRTC-агенты содержит такую информацию как:
адреса IP и порты агентов, по которым можно соединиться с агентом (это т.н. ICE-кандидаты)
сколько аудио и видео треков агент желает отправить
какие аудио и видео кодеки поддерживает каждый из агентов
значения используемые во время соединения ( uFrag / uPwd ).
значения используемые для безопасности (отпечаток сертификата)
Отметим, что сигналинг обычно работает как бы в сторонке; то есть приложения не используют WebRTC для обмена SDP сообщениями. Тут подходит любой способ обмена этими сообщениями: REST, Websocket, да хоть письмом по почте можно отправить другому пиру SDP-сообщение, а тот в свою очередь отправит свое. В своем приложении для тестов я вообще использовал firebase для сигналинга.
Установка соединения и NAT Traversal с помощью STUN/TURN
Теперь у обоих сторон WebRTC агентов достаточно информации о том, чтобы соединиться друг с другом. Далее используется другая устоявшаяся технология под названием ICE.
Настоящая магия здесь это т.н. NAT Traversal и STUN/TURN сервера. Обе эти концепции необходимы для соединения с ICE агентом из другой сетки. Далее мы изучим этот вопрос глубже.
Как только связь между двумя агентами установлена, WebRTC переходит к установлениею шифрованного канала передачи. Далее этот канал будет использован для передачи аудио/видео и данных.
Шифрование передачи информации с помощью DTLS и SRTP
Для видео/аудио в WebRTC используется другой протокол: RTP. Для шифрования RTP-пакетов используется протокол SRTP. SRTP сессия инициализируется с помощью ключей шифрования полученных в ходе DTLS сессии (rfc5764). Далее мы обсудим, почему для медиа-данных используется свой собственный протокол.
Теперь все готово! У нас есть двунаправленный и безопасный канал. Если у вас стабильное соединение между вашими WebRTC-агентами, то вышеописанный комплекс процедур достаточен чтобы начать им (агентам) общаться. Однако в жизни все не так идеально, как кажется: мы постоянно будем сталкиваться с потерей пакетов в сети, ограниченной пропускной способностью сети. Дальше мы подумаем, как справляться со всеми этими проблемами.
Общение между пирами через RTP и SCTP
Сейчас мы имеем два WebRTC-агента с безопасным двунаправленным соединением. Давайте начнем взаимодействие! И снова мы используем уже существующие протоколы: RTP (Real-time Transport Protocol), и SCTP (Stream Control Transmission Protocol). Используйте RTP для обмена аудио/видео шифрованным по протоколу SRTP и SCTP для обмена DataChannel-сообщениями, шифрованными с помощью DTLS.
RTP сам по себе очень минимален, но предоставляет все необходимое для стриминга в реальном времени. Важно то, что RTP предоставляет разработчику гибкость в управлении потерями пакетов, задержками и перегрузками так, как он (разработчик) пожелает. Далее мы будем обсуждать по этой теме в части про медиа.
WebRTC это набор протоколов
Рис.1. WebRTC Agent Diagram
Кратко: как работает WebRTC (API)
В этой части показано как JavaScript API отображается на протокол. Это не демонстрация WebRTC API, а скорее некоторый набросок для создания у вас ментальной модели, как все работает вместе. Если вы не знакомы с каким-либо из пунктов, не переживайте, можете вернуться сюда когда узнаете больше!
Метод addTrack создает новый RTP-поток. Для потока генерируется случайный Synchronization Source (SSRC). Созданный RTP поток будет затем описан в Session Description-сообщении внутри медиа-секции после вызова createOffer метода. Каждый вызов addTrack создает новый SSRC и добавляет медиа-секцию в SDP-сообщение.
Сразу после того, как SRTP сессия установлена, зашифрованные медиа-пакеты начнут отправляеться через ICE.
createDataChannel создает новый SCTP-поток, если еще не был добавлен. По умолчанию SCTP выключен, но инициализируется как только одна из сторон потребует data channel.
Сразу после того, как DTLS сессия установлена, SCTP пакеты начнут отправляться через ICE.
createOffer генерирует Session Description для отправки удаленному пиру.
Вызов createOffer ничего не меняет на локальном пире.
После вызова setLocalDescription сгенерированное SDP-сообщение также отправляется на удаленный пир (выше обусждалось, что это можно делать любым способом), и далее на удаленном пире SDP-сообщение (offer) передается в метод setRemoteDescription. Удаленный пир в свою очередь отправляет свой локальный SDP в ответ (answer), который также нужно передать локально в setRemoteDescription.
addIceCandidate позволяет WebRTC-агенту добавить больше удаленных ICE-кандидатов.
В следующей части разберем Signaling и SDP.
Просто о WebRTC
Большинство материала по WebRTC сосредоточено на прикладном уровне написания кода и не способствует пониманию технологии. Попробуем углубиться и узнать как происходит соединение, что такое дескриптор сессии и кандидаты, для чего нужны STUN и TURN сервера.
WebRTC
Введение
WebRTC – технология, ориентированная на браузеры, которая позволяет соединить два клиента для видео-передачи данных. Основные особенности – внутренняя поддержка браузерами (не нужны сторонние внедряемые технологии типа adobe flash) и способность соединять клиентов без использования дополнительных серверов – соединение peer-to-peer (далее, p2p).
Для того, чтобы понять это лучше, рассмотрим три ситуации: оба узла находятся в одной сети (Рисунок 1), оба узла находятся в разных сетях (один в приватной, другой в публичной) (Рисунок 2) и оба узла находятся в разных приватных сетях с одинаковыми IP адресами (Рисунок 3).
Рисунок 1: Оба узла в одной сети
Рисунок 2: Узлы в разных сетях (один в приватной, другой в публичной)
Рисунок 3: Узлы в разных приватных сетях, но с численно равными адресами
Что же будет, если мы все-таки решим соединить узлы через их внутренние адреса? Данные не выйдут за пределы сети. Для усиления эффекта можно представить ситуацию, изображенную на последнем рисунке – у обоих узлов совпадают внутренние адреса. Если они будут использовать их для коммуникации, то каждый узел будет общаться с самим собой.
WebRTC успешно справляется с такими проблемами, используя протокол ICE, который, правда, требует использования дополнительных серверов (STUN, TURN). Обо всем этом ниже.
Две фазы WebRTC
Чтобы соединить два узла через протокол WebRTC (или просто RTC, если связываются два iPhone‘a) необходимо провести некие предварительные действия для установления соединения. Это первая фаза – установка соединения. Вторая фаза – передача видео-данных.
Итак, рассмотрим первую фазу – фазу установки соединения. Она состоит из нескольких пунктов. Рассмотрим эту фазу сначала для узла, который инициирует соединение, а потом для ожидающего.
Отличие лишь во втором пункте.
Несмотря на кажущуюся запутанность шагов здесь их на самом деле три: отправка своего медиа потока (п.1), установка параметров соединения (пп.2-4), получение чужого медиа потока (п.5). Самый сложный – второй шаг, потому что он состоит из двух частей: установление физического и логического соединения. Первая указывает путь, по которому должны идти пакеты, чтобы дойти от одного узла сети до другого. Вторая указывает параметры видео/аудио – какое использовать качество, какие использовать кодеки.
Мысленно этап createOffer или createAnswer следует соединить с этапами передачи SDP и Ice candidate объектов.
Далее будут рассмотрены некоторые сущности подробнее, а именно – медиапоток (MediaStream), дескриптор сессии (SDP) и кандидаты (Ice candidate).
Основные сущности
Медиа потоки (MediaStream)
Основной сущностью является медиа поток, то есть поток видео и аудио данных, картинка и звук. Медиа потоки бывают двух типов – локальные и удаленные. Локальный получает данные от устройств входа (камера, микрофон), а удаленный по сети. Таким образом у каждого узла есть и локальный, и удаленный поток. В WebRTC для потоков существует интерфейс MediaStream и также существует подинтерфейс LocalMediaStream специально для локального потока. В JavaScript можно столкнуться только с первым, а если использовать libjingle, то можно столкнуться и со вторым.
В WebRTC существует довольно запутанная иерархия внутри потока. Каждый поток может состоять из нескольких медиа дорожек (MediaTrack), которые в свою очередь могут состоять из нескольких медиа каналов (MediaChannel). Да и самих медиа потоков может быть тоже несколько.
Рассмотрим все по порядку. Для этого будем держать в уме некоторый пример. Допустим, что мы хотим передавать не только видео себя, но и видео нашего стола, на котором лежит листок бумаги, на котором мы собираемся что-то писать. Нам понадобится два видео (мы + стол) и одно аудио (мы). Ясно, что мы и стол стоит разделить на разные потоки, потому что эти данные, наверное, слабо зависят друг от друга. Поэтому у нас будет два MediaStream‘a – один для нас и один для стола. Первый будет содержать и видео, и аудио данные, а второй – только видео (Рисунок 4).
Рисунок 4: Два различных медиа потока. Один для нас, один для нашего стола
Сразу ясно, что медиа поток как минимум должен включать в себя возможность содержать данные разных типов — видео и аудио. Это учтено в технологии и поэтому каждый тип данных реализуется через медиа дорожку MediaTrack. У медиа дорожки есть специальное свойство kind, которое и определяет, что перед нами – видео или аудио (Рисунок 5)
Рисунок 5: Медиа потоки состоят из медиа дорожек
Как будет всё происходить в программе? Мы создадим два медиа потока. Потом создадим две видео дорожки и одну аудио дорожку. Получим доступ к камерам и микрофону. Укажем каждой дорожке какое устройство использовать. Добавим видео и аудио дорожку в первый медиа поток и видео дорожку от другой камеры во второй медиа поток.
Но как мы различим медиа потоки на другом конце соединения? Для этого каждый медиа поток имеет свойство label – метка потока, его название (Рисунок 6). Такое же свойство имеют и медиа дорожки. Хотя на первый взгляд кажется, что видео от звука можно отличить и другими способами.
Рисунок 6: Медиа потоки и дорожки идентифицируются метками
Так, а если медиа дорожки можно идентифицировать через метку, то зачем нам для нашего примера использовать два медиа потока, вместо одного? Ведь можно передавать один медиа поток, а дорожки использовать в нем разные. Мы дошли до важного свойства медиа потоков – они синхронизируют медиа дорожки. Разные медиа потоки не синхронизируются между собой, но внутри каждого медиа потока все дорожки воспроизводятся одновременно.
Если какую-то дорожку необходимо отключать во время передачи, то можно воспользоваться свойством enabled медиа дорожки.
В конце стоит подумать о стерео звуке. Как известно стерео звук – это два разных звука. И передавать их надо тоже отдельно. Для этого используются каналы MediaChannel. Медиа дорожка звука может иметь много каналов (например, 6, если нужен звук 5+1). Внутри медиа дорожки каналы, разумеется тоже синхронизированы. Для видео обычно используется только один канал, но могут использоваться и несколько, например, для наложения рекламы.
Резюмируя: мы используем медиа поток для передачи видео и аудио данных. Внутри каждого медиа потока данные синхронизированы. Мы можем использовать несколько медиа потоков, если синхронизация нам не нужна. Внутри каждого медиа потока есть медиа дорожки двух видов – для видео и для аудио. Дорожек обычно не больше двух, но может быть и больше, если нужно передавать несколько разных видео (собеседника и его стола). Каждая дорожка может состоять из нескольких каналов, что используется обычно только для стерео звука.
В самой простой ситуации видеочата у нас будет один локальный медиа поток, который будет состоять из двух дорожек – видео дорожки и аудио дорожки, каждая из которых будет состоять из одного основного канала. Видео дорожка отвечает за камеру, аудио дорожка – за микрофон, а медиа поток – это контейнер их обоих.
Дескриптор сессии (SDP)
Для этого используется любой сигнальный механизм. SDP можно передать хоть через сокеты, хоть человеком (сообщить его другому узлу по телефону), хоть Почтой России. Всё очень просто – Вам дадут уже готовый SDP и его нужно переслать. А при получении на другой стороне – передать в ведомство WebRTC. Дескриптор сессии хранится в виде текста и его можно изменить в своих приложениях, но, как правило, это не нужно. Как пример, при соединении десктоп↔телефон иногда требуется принудительно выбирать нужный аудио кодек.
Так как в WebRTC есть возможность редактирования SDP объекта, то после получения локального дескриптора его нужно установить. Это может показаться немного странным, что нужно передавать WebRTC то, что она сама нам дала, но таков протокол. При получении удаленного дескриптора его нужно тоже установить. Поэтому Вы должны на одном узле установить два дескриптора – свой и чужой (то есть локальный и удаленный).
После такого рукопожатия узлы знают о пожеланиях друг друга. Например, если узел 1 поддерживает кодеки A и B, а узел 2 поддерживает кодеки B и C, то, так как каждый узел знает свой и чужой дескрипторы, оба узла выберут кодек B (Рисунок 7). Логика соединения теперь установлена, и можно передавать медиа потоки, но есть другая проблема – узлы по-прежнему связаны лишь сигнальным механизмом.
Рисунок 7: Согласование кодеков
Кандидаты (Ice candidate)
Итак, соединение уже установлено (логическое соединение), но пока нет пути, по которому узлы сети могут передавать данные. Здесь не всё так просто, но начнем с простого. Пусть узлы находятся в одной приватной сети. Как мы уже знаем, они могут легко соединяться друг с другом по своим внутренним IP адресам (или быть может, по каким-то другим, если используется не TCP/IP).
Через некоторые callback‘и WebRTC сообщает нам Ice candidate объекты. Они тоже приходят в текстовой форме и также, как с дескрипторами сессии, их нужно просто переслать через сигнальный механизм. Если дескриптор сессии содержал информацию о наших установках на уровне камеры и микрофона, то кандидаты содержат информацию о нашем расположении в сети. Передайте их другому узлу, и тот сможет физически соединиться с нами, а так как у него уже есть и дескриптор сессии, то и логически сможет соединиться и данные «потекут». Если он не забудет отправить нам и свой объект кандидата, то есть информацию о том, где находится он сам в сети, то и мы сможем с ним соединиться. Заметим здесь еще одно отличие от классического клиент-серверного взаимодействия. Общение с HTTP сервером происходит по схеме запрос-ответ, клиент отправляет данные на сервер, тот обрабатывает их и шлет по адресу, указанному в пакете запроса. В WebRTC необходимо знать два адреса и соединять их с двух сторон.
А почему дескриптор сессии был один, а кандидатов может быть много? Потому что расположение в сети может определяться не только своим внутренним IP адресом, но также и внешним адресом маршрутизатора, и не обязательно одного, а также адресами TURN серверов. Остаток параграфа будет посвящен подробному рассмотрению кандидатов и тому, как соединять узлы из разных приватных сетей.
Итак, два узла находятся в одной сети (Рисунок 8). Как их идентифицировать? С помощью IP адресов. Больше никак. Правда, еще можно использовать разные транспорты (TCP и UDP) и разные порты. Это и есть та информация, которая содержится в объекте кандидата – IP, PORT, TRANSPORT и какая-то другая. Пусть, для примера, используется UDP транспорт и 531 порт.
Рисунок 8: Два узла находятся в одной сети
Тогда, если мы находимся в узле p1, то WebRTC передаст нам такой объект кандидата — [10.50.200.5, 531, udp]. Здесь приводится не точный формат, а лишь схема. Если мы в узле p2, то кандидат таков – [10.50.150.3, 531, udp]. Через сигнальный механизм p1 получит кандидата p2 (то есть расположение узла p2, а именно его IP и PORT). После чего p1 может соединиться с p2 напрямую. Более правильно, p1 будет посылать данные на адрес 10.50.150.3:531 в надежде, что они дойдут до p2. Не важно, принадлежит ли этот адрес узлу p2 или какому-то посреднику. Важно лишь то, что через этот адрес данные будут посылаться и могут достигнуть p2.
Пока узлы в одной сети – все просто и легко – каждый узел имеет только один объект кандидата (всегда имеется в виду свой, то есть свое расположение в сети). Но кандидатов станет гораздо больше, когда узлы будут находится в разных сетях.
Перейдем к более сложному случаю. Один узел будет находиться за роутером (точнее, за NAT), а второй узел будет находиться в одной сети с этим роутером (например, в интернете) (Рисунок 9).
Рисунок 9: Один узел за NAT, другой нет
Этот случай имеет частное решение проблемы, которое мы сейчас и рассмотрим. Домашний роутер обычно содержит таблицу NAT. Это специальных механизм, разработанный для того, чтобы узлы внутри приватной сети роутера смогли обращаться, например, к веб-сайтам.
Предположим, что веб-сервер соединен с интернетом напрямую, то есть имеет публичным IP* адрес. Пусть это будет узел p2. Узел p1 (веб-клиент) шлет запрос на адрес 10.50.200.10. Сначала данные попадают на роутер r1, а точнее на его внутренний интерфейс 192.168.0.1. После чего, роутер запоминает адрес источника (адрес p1) и заносит его в специальную таблицу NAT, затем изменяет адрес источника на свой(p1 → r1). Далее, по своему внешнему интерфейсу роутер пересылает данные непосредственно на веб‑сервер p2. Веб-сервер обрабатывает данные, генерирует ответ и отправляет обратно. Отправляет роутеру r1, так как именно он стоит в обратном адресе (роутер подменил адрес на свой). Роутер получает данные, смотрит в таблицу NAT и пересылает данные узлу p1. Роутер выступает здесь как посредник.
А что если несколько узлов из внутренней сети одновременно обращаются к внешней сети? Как роутер поймет кому отправлять ответ обратно? Эта проблема решается с помощью портов. Когда роутер подменяет адрес узла на свой, он также подменяет и порт. Если два узла обращаются к интернету, то роутер подменяет их порты источников на разные. Тогда, когда пакет от веб‑сервера придет обратно к роутеру, то роутер поймет по порту, кому назначен данный пакет. Пример ниже.
Вернемся к технологии WebRTC, а точнее, к ее части, которая использует ICE протокол (отсюда и Ice кандидаты). Узел p2 имеет одного кандидата (свое расположение в сети – 10.50.200.10), а узел p1, который находится за роутером с NAT, будет иметь двух кандидатов – локального (192.168.0.200) и кандидата роутера (10.50.200.5). Первый не пригодится, но он, тем не менее, генерируется, так как WebRTC еще ничего не знает об удаленном узле – он может находиться в той же сети, а может и нет. Второй кандидат пригодится, и, как мы уже знаем, важную роль будет играть порт (чтобы пройти через NAT).
Запись в таблице NAT генерируется только когда данные выходят из внутренней сети. Поэтому узел p1 должен первым передать данные и только после этого данные узла p2 смогут добраться до узла p1.
На практике оба узла будут находиться за NAT. Чтобы создать запись в таблице NAT каждого роутера, узлы должны что-то отправить удаленному узлу, но на этот раз ни первый не может добраться до второго, ни наоборот. Это связано с тем, что узлы не знают своих внешних IP адресов, а отправлять данные на внутренние адреса бессмысленно.
Однако, если внешние адреса известны, то соединение будет легко установлено. Если первый узел отошлет данные на роутер второго узла, то роутер их проигнорирует, так как его таблица NAT пока пуста. Однако в роутере первого узла в таблице NAT появилась нужна запись. Если теперь второй узел отправит данные на роутер первого узла, то роутер их успешно передаст первому узлу. Теперь и таблица NAT второго роутера имеет нужны данные.
Проблема в том, что, чтобы узнать своей внешний IP адрес, нужен узел находящийся в общей сети. Для решения такой проблемы используются дополнительные сервера, которые подключены к интернету напрямую. С их помощью также создаются заветные записи в таблице NAT.
STUN и TURN сервера
При инициализации WebRTC необходимо указать доступные STUN и TURN сервера, которые мы в дальнейшем будем называть ICE серверами. Если сервера не будут указаны, то соединиться смогут только узлы в одной сети (подключенные к ней без NAT). Сразу стоит отметить, что для 3g-сетей обязательно использование TURN серверов.
STUN сервер – это просто сервер в интернете, который возвращает обратный адрес, то есть адрес узла отправителя. Узел, находящий за роутером, обращается к STUN серверу, чтобы пройти через NAT. Пакет, пришедший к STUN серверу, содержит адрес источника – адрес роутера, то есть внешний адрес нашего узла. Этот адрес STUN сервер и отправляет обратно. Таким образом, узел получает свой внешний IP адрес и порт, через который он доступен из сети. Далее, WebRTC с помощью этого адреса создает дополнительного кандидата (внешний адрес роутера и порт). Теперь в таблице NAT роутера есть запись, которая пропускает к нашему узлу пакеты, отправленные на роутер по нужному порту.
Рассмотрим этот процесс на примере.
Пример (работа STUN сервера)
STUN сервер будем обозначать через s1. Роутер, как и раньше, через r1, а узел – через p1. Также необходимо будет следить за таблицей NAT – ее обозначим как r1_nat. Причем в этой таблице обычно содержится много записей от разный узлов подсети – они приводиться не будут.
Итак, в начале имеем пустую таблицу r1_nat.
Internal IP | Internal PORT | External IP | External PORT |
---|
Таблица 1: Пустая таблица NAT
В таблице 4 столбца. Она задает отображение первых двух столбцов на два последних, то есть каждой паре (IP, PORT), которая адресует узел во внутренней приватной сети, сопоставляется пара (IP, PORT) из внешней публичной сети.
Узел p1 отправляет пакет узлу s1 (STUN серверу). Ниже в таблице указаны четыре интересующие нас поля в заголовке транспортного пакета (TCP или UDP) – IP и PORT источника и приемника. Пусть адреса будут такими:
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
192.168.0.200 | 35777 | 12.62.100.200 | 6000 |
Таблица 2: Заголовок пакета
Узел p1 отправляет этот пакет роутеру r1 (не важно каким образом, в разных подсетях могут быть использованы разные технологии). Роутеру необходимо сделать подмену адреса источника Src IP, так как указанный в пакете адрес заведомо не подойдет для внешней подсети, более того, адреса из такого диапазона зарезервированы, и ни один адрес в интернете не имеет такого адреса. Роутер делает подмену в пакете и создает новую запись в своей таблице r1_nat. Для этого ему нужно придумать номер порта. Напомним, что, так как несколько узлов внутри подсети могут обращаться к внешней сети, то в таблице NAT должна храниться дополнительная информация, чтобы роутер смог определить, кому из этих нескольких узлов предназначается обратный пакет от сервера. Пусть роутер придумал порт 888.
Измененный заголовок пакета:
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
10.50.200.5 | 888 | 12.62.100.200 | 6000 |
Таблица 3: Роутер подменил адрес источника на свой
Где 10.50.200.5 – это внешний адрес роутера.
Таблица r1_nat:
Internal IP | Internal PORT | External IP | External PORT |
---|---|---|---|
192.168.0.200 | 35777 | 10.50.200.5 | 888 |
Таблица 4: Таблица NAT пополнилась новой записью
Здесь IP адрес и порт для подсети абсолютно такие же, как у исходного пакета. В самом деле, при обратной передаче мы должны иметь способ полностью их восстановить. IP адрес для внешней сети – это адрес роутера, а порт изменился на придуманный роутером.
Настоящий порт, на который узел p1 принимает подключение – это, разумеется, 35777, но сервер шлет данные на фиктивный порт 888, который будет изменен роутером на настоящий 35777.
Итак, роутер подменил адрес и порт источника в заголовке пакета и добавил запись в таблицу NAT. Теперь пакет отправляется по сети серверу, то есть узлу s1. На входе, s1 имеет такой пакет:
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
10.50.200.5 | 888 | 12.62.100.200 | 6000 |
Таблица 5: STUN сервер получил пакет
Итого, STUN сервер знает, что ему пришел пакет от адреса 10.50.200.5:888. Теперь этот адрес сервер отправляет обратно. Здесь стоит остановиться и еще раз посмотреть, что мы только что рассматривали. Таблицы, приведенные выше – это кусочек из заголовка пакета, вовсе не из его содержимого. О содержимом мы не говорили, так как это не столь важно – оно как-то описывается в протоколе STUN. Теперь же мы будем рассматривать помимо заголовка еще и содержимое. Оно будет простым и содержать адрес роутера – 10.50.200.5:888, хотя взяли мы его из заголовка пакета. Такое делается не часто, обычно протоколам не важна информация об адресах узлов, важно лишь, чтобы пакеты доставлялись по назначению. Здесь же мы рассматриваем протокол, который устанавливает путь между двумя узлами.
Итак, теперь у нас появляется второй пакет, который идет в обратном направлении:
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
12.62.100.200 | 6000 | 10.50.200.5 | 888 |
Таблица 6: STUN сервер отправляет пакет с таким заголовком
Заголовок изменился очень просто – источник и приемник поменялись местами, что логично, так как направление пакета теперь другое.
Content |
---|
10.50.200.5:888 |
Таблица 7: STUN сервер отправляет пакет с таким содержимым
Это уже содержание пакета. На самом деле, оно может содержать много информации, но здесь указано лишь то, что важно для понимания работы STUN сервера.
Далее, пакет путешествует по сети, пока не окажется на внешнем интерфейсе роутера r1. Роутер понимает, что пакет предназначен не ему. Как он это понимает? Это можно узнать только по порту. Порт 888 он не использует для своих личных целей, а использует для механизма NAT. Поэтому в эту таблицу роутер и смотрит. Смотрит на столбец External PORT и ищет строку, которая совпадет с Dest PORT из пришедшего пакета, то есть 888.
Internal IP | Internal PORT | External IP | External PORT |
---|---|---|---|
192.168.0.200 | 35777 | 10.50.200.5 | 888 |
Таблица 8: Таблица NAT
Нам повезло, такая строчка существует. Если бы не повезло, то пакет бы просто отбросился. Теперь нужно понять, кому из узлов подсети надо отправлять этот пакет. Не стоит торопиться, давайте снова вспомним о важности портов в этом механизме. Одновременно два узла в подсети могли бы отправлять запросы во внешнюю сеть. Тогда, если для первого узла роутер придумал порт 888, то для второго он бы придумал порт 889. Предположим, что так и случилось, то есть таблица r1_nat выглядит так:
Internal IP | Internal PORT | External IP | External PORT |
---|---|---|---|
192.168.0.200 | 35777 | 10.50.200.5 | 888 |
192.168.0.173 | 35777 | 10.50.200.5 | 889 |
Таблица 9: Таблица NAT
По порту 888 понятно, что нужный внутренний адрес это 192.168.0.200:35777. Роутер заменяет адрес приемника с
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
12.62.100.200 | 6000 | 10.50.200.5 | 888 |
Таблица 10: Роутер подменяет адрес приемника
Src IP | Src PORT | Dest IP | Dest PORT |
---|---|---|---|
12.62.100.200 | 6000 | 192.168.0.200 | 35777 |
Таблица 11: Роутер подменил адрес приемника
Пакет успешно приходит к узлу p1 и, посмотрев на содержимое пакета, узел узнает о своем внешнем IP адресе, то есть об адресе роутера во внешней сети. Также он знает и порт, который роутер пропускает через NAT.
Что же дальше? Какая от этого всего польза? Польза – это запись в таблице r1_nat. Если теперь кто угодно будет отправлять на роутер r1 пакет с портом 888, то роутер перенаправит этот пакет узлу p1. Таким образом, создался небольшой узкий проход к спрятанному узлу p1.
Из примера выше можно получить некоторое представление о работе NAT и сути STUN сервера. Вообще, механизм ICE и STUN/TURN сервера как раз и направлены на преодоления ограничений NAT.
Между узлом и сервером может стоять не один роутер, а несколько. В таком случае узел получит адрес того роутера, который является первым выходящим в ту же сеть, что и сервер. Иными словами, получим адрес роутера, подключенного к STUN серверу. Для p2p коммуникации это как раз то, что нам нужно, если не забыть тот факт, что в каждом роутере добавится необходимая нам строчка в таблицу NAT. Поэтому обратный путь будет вновь так же гладок.
TURN сервер – это улучшенный STUN сервер. Отсюда сразу следует извлечь, что любой TURN сервер может работать и как STUN сервер. Однако, есть и преимущества. Если p2p коммуникация невозможна (как например, в 3g сетях), то сервер переходит в режим ретранслятора (relay), то есть работает как посредник. Разумеется, ни о каком p2p тогда речь не идет, но за рамками механизма ICE узлы думают, что общаются напрямую.
Таким образом TURN сервер нужен в том случае, когда оба собеседника находятся за симметричным NAT (каждый за своим).
Краткая сводка
Здесь приведены некоторые утверждения о сущностях WebRTC, которые необходимо всегда держать в голове. Подробно они описаны выше. Если какие-то из утверждений Вам покажутся не до конца ясными, перечитайте соответствующие параграфы.
Сноски:
Так как у всех узлов в этой сети один и тот же роутер. ↩
Этот шуточный пример всегда полезно держать в голове, чтобы различать коммуникацию в технологии WebRTC от сигнальной коммуникации ↩
На синхронизацию всегда тратится дополнительное время. ↩
Во времена Vanilla Ice кандидаты передавались внутри SDP, поэтому связь уже есть. ↩
Можно только согласиться, отказаться нельзя. В случае отказа нужно просто игнорировать запрос на соединение. ↩
Тем не менее, в некоторых реализациях это возможно. Но в том лишь случае, если есть доступ к настройкам видео. Браузеры не могут обращаться к камере до получения медиа потока. ↩
Например, при соединении ftp-клиента с ftp-сервером сначала устанавливается TCP-соединение (для протокола прикладного уровня протокол транспортного уровня можно считать физическим), а только потом передаются данные по протоколу FTP (то есть логика протокола). ↩
Такова реализация libjingle и некоторых браузеров. Это так, потому что кандидаты являются частью SDP объекта и записываются в него. ↩