Сервис видеозвонков запущен, вы проверяете его вечером с домашнего ноутбука: камера включается, собеседник виден, звук идёт. Утром коллега открывает ту же комнату из офиса. Страница загрузилась, в списке участников есть его имя и ваше, кнопка «Войти» нажата. А дальше чёрный прямоугольник и бесконечное «Соединение…». Никаких ошибок сертификата, никакой недоступности сайта. Логи сервера сообщают, что клиент зарегистрировался, подписался на комнату и благополучно ничего не получил.

Эта картина типична для WebRTC, и она часто путает именно потому, что «всё же открывается». Дело в том, что у видеозвонка два разных канала с разной судьбой. По одному ходят короткие служебные сообщения: кто вошёл, кто что предлагает, какие сетевые адреса у кого. Он обычно живёт поверх обычного HTTPS, и корпоративный прокси его пропускает вместе с остальным вебом. По другому идут сами звук и видео, и вот он требует либо прямого пути, либо посредника. Если не получается ни то, ни другое, вы видите ровно ту картину из начала.

Разберёмся, как участники ищут дорогу друг к другу, когда в этом поиске нужен TURN, что именно он делает с потоком, как посчитать для него канал и как выдавать доступ, чтобы ваш сервер не превратился в бесплатный транзитный узел для всего интернета.

Как два участника ищут дорогу друг к другу

Для начала выбросим из головы идею, что у браузера есть «адрес», который достаточно сообщить собеседнику. Ноутбук в офисе сидит за NAT: у него внутренний адрес вроде 10.х.х.х, а наружу он выходит под общим адресом всей организации. Ноутбук дома тоже за NAT, только за роутером провайдера. Напрямую друг другу они не адресуются, потому что внутренние адреса другой стороне бесполезны, а внешний адрес сам по себе ещё не говорит, куда пропустит пакет устройство на границе сети.

Поэтому WebRTC действует как человек, который ищет вход в здание: пробует все двери по очереди. Каждый участник собирает набор кандидатов — вариантов адреса и порта, по которым его теоретически можно достучаться. Кандидатов бывает три вида:

  • host — собственный адрес интерфейса, в том числе внутренний. Работает, если оба участника в одной локальной сети;
  • server reflexive — внешний адрес и порт, под которыми участника «видит» внешний сервер. Его можно узнать с помощью STUN;
  • relay — адрес на сервере-посреднике, куда участник сам просит принимать и пересылать пакеты. Его выдаёт TURN.

Кандидатами стороны обмениваются через сигнализацию, а затем начинается проверка связности: каждая пара «мой кандидат — его кандидат» получает пробный запрос, и из тех, что ответили, выбирается рабочая. Этот процесс называется ICE. Приоритет у него понятный: сначала дешёвое и быстрое (локальный адрес, прямой путь через внешний адрес), и только в конце, если ничего не получилось, — путь через ретранслятор. Пара relay почти всегда — запасной вариант, поэтому на домашнем интернете TURN вы можете вообще не заметить: звонок честно идёт напрямую, и сервер-посредник лежит без дела.

Отсюда сразу видно, почему из офиса беда, а из дома нет. Домашний NAT обычно «мягкий»: если изнутри ушёл пакет наружу, ответный пакет с того адреса пропускается обратно, и два таких NAT договариваются между собой. В офисе же между сотрудником и интернетом стоит межсетевой экран с политикой. Он может запрещать исходящий UDP целиком, пропускать только TCP на 80 и 443, заставлять весь веб идти через прокси, а ещё разбирать трафик и отсекать то, что на вид не похоже на веб. Тогда все пробные пакеты между ICE-кандидатами тихо пропадают, а чёрный прямоугольник — просто следствие: ни одна пара не прошла проверку.

STUN отвечает на вопрос «кто я снаружи», TURN берёт на себя перевозку

Эти два протокола часто произносят через запятую, как «STUN/TURN-сервер», и поэтому кажется, что это одна и та же вещь с двумя названиями. Работа у них принципиально разная.

STUN — это маленькое зеркало. Клиент отправляет на STUN-сервер запрос, сервер отвечает: «С моей стороны ты выглядишь как такой-то адрес и порт». Больше сервер ничего не делает. Видео через него не идёт, состояния звонка он не держит, ресурсов почти не ест. Но полученный адрес пригоден лишь тогда, когда NAT и фильтры позволяют входящим пакетам от собеседника достичь этого адреса. Если не позволяют — зеркало честно показало внешний адрес, и пользоваться им всё равно нельзя.

TURN — это уже не зеркало, а почтовое отделение. Клиент приходит на сервер, авторизуется и просит выделить ему адрес-ретранслятор (allocation). Теперь собеседник посылает пакеты на этот адрес, сервер принимает их и пересылает клиенту по уже установленному клиентом исходящему соединению. В обратную сторону то же самое. Для сети офиса клиент всего лишь ходит наружу на один известный адрес — а это ровно то, что политики ограничений чаще всего допускают. Любое дальнейшее общение с собеседником прячется за этим адресом.

Цена такой любезности: весь медиатрафик идёт через вашу машину. STUN можно держать на самой скромной конфигурации, а TURN — это постоянная пересылка видео, и здесь сервер начинает упираться в сетевой канал, а не в процессор или память. Если вы используете платформу вроде LiveKit, встроенный TURN решает именно эту задачу: он помогает подключиться клиентам, у которых прямое соединение ограничено (документация по развёртыванию LiveKit). Подход тот же, только роль посредника совмещена с медиасервером.

Один звонок, три маршрута

Посмотрим на простейший звонок один на один: сотрудник A в офисе, сотрудник B дома. Сигнализация идёт в обоих случаях через ваш сервер по HTTPS, в схеме её мы не рисуем. Рисуем только медиа.

Прямой путь (host / server reflexive):

  A (офис) ============================== B (дом)
           один поток в каждую сторону,
           ваш сервер в передаче не участвует

Через TURN (relay):

  A (офис) ---- плечо 1 ----> [ TURN-сервер ] ---- плечо 2 ----> B (дом)
  A (офис) <--- плечо 2 ----- [ TURN-сервер ] <--- плечо 1 ----- B (дом)

           каждый поток проходит сервер насквозь:
           приходит на него и уходит с него

Картинка нужна, чтобы увидеть главное: в варианте с relay у каждого направления появляется «вход» на сервер и «выход» с сервера. Если принять, что видео и звук каждого участника условно занимают 1,5 Мбит/с, то прямой звонок не создаёт на вашей стороне никакой нагрузки: 1,5 Мбит/с идёт от A к B и 1,5 Мбит/с от B к A по их собственным каналам. А теперь тот же звонок через TURN. A отправляет серверу 1,5 Мбит/с, сервер отправляет их B. B отправляет серверу 1,5 Мбит/с, сервер отправляет их A. Итого на сетевом интерфейсе сервера: 3 Мбит/с входящего и 3 Мбит/с исходящего — на один звонок на двоих.

Число 1,5 Мбит/с здесь взято для расчёта, это не измерение: реальная скорость зависит от кодека, разрешения, движения в кадре и от того, как клиент подстраивает качество под канал. Для планирования важен не абсолютный размер, а структура: поток, прошедший через relay, платит ваш канал дважды — один раз на входе и один раз на выходе.

Бывает и так, что ретрансляция нужна только одной стороне, а у другой достаточно открытый адрес. Тогда пара кандидатов состоит из relay-адреса одного участника и обычного адреса другого. Схема упрощается до одного «прохода» через сервер, но поток каждого направления всё равно проходит через ваш интерфейс: входит и выходит. С точки зрения нагрузки на канал результат тот же, 3 и 3 Мбит/с в нашем условном примере, поэтому считать удобнее на уровне звонка, не вдаваясь, у какой стороны relay-кандидат.

Сколько звонков на самом деле идёт через relay

Теперь главный вопрос сайзинга: сколько таких звонков окажется на сервере одновременно. Сразу отбросим соблазн считать от штата. Допустим, у компании 500 зарегистрированных сотрудников. Это число ни о чём не говорит: большинство из них в данный момент не на звонке, а из тех, кто звонит, значительная часть соединяется напрямую. Для канала важны три величины: сколько звонков идут одновременно в пике, какая их доля вынуждена использовать relay и сколько потоков в каждом звонке.

Возьмём условный офис: в пик одновременно идут 40 звонков один на один (то есть 80 участников). Скорость на каждого участника — те же условные 1,5 Мбит/с. Меняется только доля звонков, которые не смогли пройти напрямую и ушли в relay. Каждый такой звонок даёт серверу по 3 Мбит/с на вход и на выход.

Нагрузка на интерфейс TURN-сервера при 40 одновременных звонках 1:1 (условный расчёт)
Доля звонков через relayЗвонков через серверВходящий трафик, Мбит/сИсходящий трафик, Мбит/сИсходящих за час непрерывной нагрузки, ГБ
10 %412125,4
25 %10303013,5
60 %24727232,4
100 %4012012054

Арифметика проста: звонков через сервер умножаем на 3 Мбит/с в каждую сторону. Чтобы получить гигабайты, делим мегабиты на 8 и умножаем на число секунд: 30 Мбит/с — это 3,75 МБ/с, за час получается 13,5 ГБ исходящего трафика (и столько же входящего). Если провайдер считает трафик в обе стороны или ограничивает только исходящий, это стоит учесть заранее.

Обратите внимание, насколько сильно доля relay двигает итог. При 10 % сервер почти не заметен, при 100 % нужен канал на 120 Мбит/с в каждую сторону, и это только на 40 звонков. Доля относится к вещам, которые заранее не знает никто: она зависит от того, какие сети у ваших людей, что разрешено в офисе, сколько сотрудников работает из мест с жёсткими фильтрами. Поэтому разумнее на пилоте посмотреть в статистике WebRTC на выбранную пару кандидатов, где у каждой стороны есть тип (host, srflx или relay), и посчитать реальную долю. Эти данные клиент отдаёт сам, и их можно собирать с первых пользователей.

Групповые звонки меняют форму расчёта, а не его смысл. Если комнату обслуживает медиасервер (SFU), каждый участник отправляет серверу свой поток и получает потоки остальных. Для шестерых при тех же условных 1,5 Мбит/с на поток и без какой-либо экономии качества это 1,5 Мбит/с вверх и 5 × 1,5 = 7,5 Мбит/с вниз на каждого. Если один из шести подключён через TURN, через ретранслятор идут все его 1,5 Мбит/с вверх и 7,5 Мбит/с вниз; остальные пятеро ходят к серверу напрямую. Для вашего интерфейса это та же логика: считается то, что реально прошло через relay, а в комнате с асимметричной нагрузкой «вниз» заметно тяжелее, чем «вверх». Настоящие значения зависят от того, как платформа снижает качество и сколько потоков она рассылает каждому, поэтому ориентироваться стоит на собственные замеры.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Что сеть офиса может сделать с вашим TURN

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

Чаще всего встречаются такие варианты поведения сети:

  • UDP закрыт, TCP разрешён. Прямые пути между участниками исчезают, потому что медиа по умолчанию любит UDP. TURN может принять клиента по TCP, а если разрешён только порт HTTPS, то и на нём, но медиа поверх TCP чувствительнее к потерям и задержкам;
  • Разрешён только вебовый трафик через прокси. Если прокси не умеет пропускать произвольный TLS-туннель к вашему адресу, TURN недостижим;
  • Фильтр смотрит внутрь TLS. Системы глубокой инспекции могут отсекать соединения, которые не похожи на браузерный веб, даже если порт 443 формально открыт;
  • Разрешены только адреса из списка. Тогда в списке придётся оказаться вашему домену или адресу, а это решение принимает владелец сети.

Поэтому важно разделять три сетевых пути, и документация по портам делает это явно: сигнализация и API, ICE по UDP или TCP и TURN идут разными дорогами, а точные порты определяет конфигурация (порты и firewall в LiveKit). Следствие, которое объясняет нашу историю с чёрным прямоугольником: открытый HTTPS сам по себе не доказывает, что медиатрафик дойдёт. Поэтому проверяйте каждый путь отдельно, и проверяйте его из той сети, где живут проблемные пользователи, а не с сервера и не из дома:

  1. Страница и сигнализация открываются — значит, домен, доверенный сертификат и путь по HTTPS в порядке. Это нужно, но не доказывает медиа.
  2. Клиент получает ICE-кандидаты нужного типа. Если нет ни одного relay-кандидата, значит, до TURN он не дошёл или его не пустили по реквизитам.
  3. Выбранная пара в статистике звонка — relay, и байты в ней растут. Только это подтверждает, что поток реально идёт через посредника.

Снаружи же ваш сервер должен быть правильно представлен самому себе. В описании развёртывания подчёркнуто, что для доступности нужны корректный публичный адрес, домен, доверенный TLS и доступные транспортные порты (развёртывание LiveKit). Типичная ошибка на виртуальной машине — когда сервер знает только свой внутренний адрес и рассылает его клиентам как relay-адрес. Клиент честно пробует недоступное, выжидает таймаут и сдаётся. Если сервер стоит за дополнительным NAT или у машины несколько адресов, публичный адрес обязательно нужно указать явно.

И последнее про сети: даже если путь найден, он может умирать со временем. NAT и межсетевые экраны забывают неактивные соединения, а молчаливая потеря состояния выглядит как «звонок повис через несколько минут». Это отдельная история с таймерами, и о ней подробно рассказано в материале про keep-alive и соединения, которые умирают молча. Для TURN это означает, что регулярный обмен служебными пакетами по выделенному адресу нужен не из вежливости, а чтобы промежуточные устройства не стёрли запись.

Временные ключи вместо общего пароля

Теперь про вторую половину задачи: как пустить на сервер только своих. TURN по определению принимает чужие пакеты и пересылает их куда попросят. Если оставить его без аутентификации или раздать всем один постоянный логин и пароль, вскоре вы получите открытый relay: любой желающий сможет гонять через него свой трафик за ваш счёт и с вашего адреса. Исходящие 120 Мбит/с из таблицы выше превратятся в чужие 120 Мбит/с.

Coturn умеет проверять пользователей двумя способами. Первый — long-term credentials: у каждого пользователя есть постоянная пара «имя — пароль», которую сервер знает заранее. Он годится для служебных клиентов, но совсем плох для браузера: пароль приходится отдавать в код страницы, где его увидит любой, а значит, он перестаёт быть секретом. Второй способ — схема временных учётных данных TURN REST API на основе общего секрета. Тут сервер и ваш бэкенд знают один секрет, и бэкенд по запросу авторизованного пользователя выдаёт ему имя и пароль, которые вычисляются из времени и идентификатора пользователя (пример конфигурации coturn). Сам секрет клиент никогда не видит.

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

Есть тонкость, на которой часто спотыкаются. В примере конфигурации отдельно разъяснено различие между use-auth-secret и lt-cred-mech: это не два независимых способа проверки, которые можно включить вместе и получить «или то, или другое». Выбирая схему с общим секретом, настраивайте именно её и не смешивайте с механизмом постоянных пар по привычке. Иначе легко получить конфигурацию, в которой клиенты с временными данными получают отказ, а вы не понимаете почему, или, наоборот, остаётся более слабая дорога, о которой вы не подозревали.

Ещё несколько практических мер, которые помогают сохранить relay закрытым:

  • выдавайте реквизиты только после авторизации в вашем приложении, через ваш бэкенд, а не отдавайте их анонимному запросу;
  • выбирайте короткий срок жизни — столько, сколько нужно звонку, а не рабочему дню;
  • не кладите общий секрет в клиентский код и в репозиторий с фронтендом;
  • ограничьте диапазон портов для relay-адресов и откройте на межсетевом экране только его, а не весь диапазон портов подряд;
  • следите за выделениями (allocations) и трафиком: резкий рост при неизменной нагрузке пользователей — признак того, что реквизиты утекли.

Диапазон relay-портов — тоже часть вашего расчёта. Каждый ретранслируемый участник занимает на сервере отдельный порт, и от размера диапазона зависит, сколько allocations сервер физически способен выдать. Запас стоит делать заметным, но не безразмерным: открытый «на всякий случай» диапазон из десятков тысяч портов — это зря расширенная поверхность.

Как это ложится на VPS

Теперь можно связать расчёт с железом. Для TURN решающее значение имеют сетевой интерфейс и качество канала: пересылка пакетов — работа простая и по большей части упирается в пропускную способность и количество пакетов в секунду. Процессор и память вступают в игру меньше, но и они нужны: шифрование TLS для клиентов, которые ходят по TURN поверх TLS, учёт allocations, журналы, параллельная работа медиасервера, если TURN встроен в него. Если же вы разворачиваете платформу вроде LiveKit, на той же машине живёт и сам медиасервер, который тоже потребляет CPU на каждый участник, и тогда ограничение определяется суммой.

Поэтому подбирать VPS стоит сверху вниз от расчёта канала, а не от красивых цифр в тарифе. Берёте ожидаемый пик (скажем, строку 25 % из таблицы: 30 Мбит/с в каждую сторону), закладываете запас, например, на рост доли relay в плохие дни, и сравниваете с реальной пропускной способностью, которую вам даёт провайдер. Ресурсы планов — только отправная точка для проверки: у MAATRIX это Ba с 1 vCPU, 2 ГБ RAM и 40 ГБ NVMe, Uab с 2 vCPU, 4 ГБ и 80 ГБ, Heka с 4 vCPU, 8 ГБ и 160 ГБ, Maat с 8 vCPU, 16 ГБ и 240 ГБ, Djed с 16 vCPU, 32 ГБ и 320 ГБ. Для пилота с небольшой группой сотрудников естественно начать с младших вариантов и смотреть на загрузку интерфейса и процессора под реальными звонками, а потом переходить выше или ниже. Объявлять заранее, сколько звонков выдержит конкретный план, было бы гаданием: это зависит от кодеков, схемы комнат и от доли relay.

Есть и соображения, не связанные с цифрами. Расположение. Ретранслятор добавляет задержку на каждое плечо, и чем он дальше от участников, тем хуже слышна разница между «поговорили» и «перебивали друг друга». Для российского офиса и сотрудников из дома разумно смотреть на ближайшую из доступных локаций (в публичном предложении указаны UK, US, RU и FR) и проверять реальную задержку из ваших сетей, а не по карте. Доступ root на VPS нужен, чтобы управлять межсетевым экраном, диапазонами портов и сертификатами: все эти настройки TURN-сервера лежат на вашей стороне, и без полного доступа к системе вы бы не смогли их задать. Если нужно быстро поднять тестовый стенд, подойдёт то, что виртуальная машина создаётся автоматически через пару минут после оплаты, а оплата принимается картой РФ и через СБП. Страница с предложением — аренда VPS MAATRIX.

Как отличить настоящую проблему relay от всего остального

Вернёмся к сотруднику, у которого висит чёрный прямоугольник, и пройдём по симптомам в порядке от простого к сложному. Это экономит часы, потому что мысль «нужен TURN» приходит первой, а причиной бывает совсем другое.

Сигнализация не работает вовсе. Участники не видят друг друга в списке, комната не открывается. Это не TURN: проверяйте домен, сертификат, прокси и путь WebSocket. До медиа дело не дошло.

Сигнализация работает, медиа нет, relay-кандидатов у клиента нет. Клиент не получил от TURN адрес. Причины: неверные или просроченные временные реквизиты, часы на бэкенде и сервере расходятся (время входит в расчёт пароля, и пара, выданная «в прошлом», отвергается), порт TURN недоступен из этой сети, или в конфигурации клиента вообще не прописан TURN-адрес.

Relay-кандидаты есть, пара не выбирается. Клиент пришёл на TURN и получил адрес, но собеседник не может на него отправить. Смотрите публичный адрес сервера, диапазон relay-портов и правила межсетевого экрана на стороне сервера. Часто именно здесь выясняется, что на экране открыт порт самого TURN, а диапазон ретранслируемых портов забыт.

Пара relay выбрана, медиа идёт плохо. Это уже вопрос канала, задержки и потерь: посмотрите, по какому транспорту клиент подключён (UDP или TCP), не упёрся ли интерфейс сервера в потолок, нет ли потерь на пути. Медиа поверх TCP хуже переносит потери, потому что один потерянный пакет задерживает всех, кто за ним.

Пара relay выбрана, звонок хороший, но в офисе у всех так. Значит, прямой путь в этой сети недоступен для всех, и доля relay там близка к 100 %. Это не поломка, а характеристика сети, и её нужно закладывать в расчёт: возвращаемся к таблице и берём нижнюю строку для сотрудников, которые сидят в этом офисе одновременно.

Чтобы эта диагностика не превращалась в расследование каждый раз, имеет смысл заложить в клиент простой сбор данных: тип выбранной пары кандидатов, транспорт (UDP или TCP) и признак, что соединение прошло через relay. Это несколько полей на звонок, но они позволяют увидеть долю ретрансляции по офисам и по сетям, а не по ощущениям, и показать администратору сети конкретный список того, что нужно открыть.

Считать нужно поток, а не людей

Вернёмся к началу. Дома звонок шёл напрямую, потому что домашней сети нечего было запрещать, и ваш сервер в разговоре не участвовал. В офисе прямой путь закрыт, и тот же звонок оказывается на вашей машине дважды: на входе и на выходе. Поэтому ёмкость TURN нельзя вывести из численности штата: 500 зарегистрированных пользователей не нагружают канал, если в пик говорят пятеро и четверо из них напрямую.

Размер сервера складывается из трёх чисел, и каждое из них вы можете замерить, а не угадывать: сколько звонков идёт одновременно, какая доля из них действительно выбрала пару relay и сколько мегабит приходится на одного участника. Умножьте их, учтите, что каждый ретранслируемый поток проходит через интерфейс в обе стороны, добавьте запас — и вы получите число, которое сможете проверить под нагрузкой на тестовом VPS. Всё остальное — сертификаты, диапазоны портов, временные реквизиты, правила межсетевого экрана — вопрос того, чтобы эти потоки доходили и чтобы пользоваться ими могли только свои.