NAT, файрволы, STUN, TURN, ICE: как WebRTC реально добирается до телефона

Автор: Николай СапуновОбновлено: август 202622 мин чтения
Содержание статьи +

Опубликовано: 2026-05-20 · Время чтения: 22 мин · Автор: Николай Сапунов, CEO Фора Софт

Последняя сверка: 2026-05-20 со стандартами: IETF RFC 8489 (STUN, февраль 2020), RFC 8656 (TURN, февраль 2020), RFC 8445 (ICE, июль 2018), RFC 8838 (Trickle ICE, январь 2021), RFC 4787 (NAT Behavioral Requirements for Unicast UDP, январь 2007), RFC 5780 (NAT Behaviour Discovery Using STUN, май 2010), RFC 7675 (STUN Usage for Consent Freshness, октябрь 2015), RFC 8826 (Security Considerations for WebRTC, февраль 2021), W3C WebRTC 1.0 Recommendation (март 2023) и отчёты WebRTC-статистики Chrome / Firefox / Safari за Q1–Q2 2026.

TL;DR

WebRTC-звонок между двумя телефонами почти никогда не идёт напрямую – каждый телефон сидит за домашним роутером, корпоративным файрволом или carrier-grade NAT мобильного оператора, которые прячут реальный IP-адрес от открытого интернета. Четыре кирпичика, которые решают эту задачу, – это NAT (коробка, переписывающая адреса, на каждом роутере), STUN (крошечный протокол из RFC 8489, через который устройство узнаёт, как его адрес виден снаружи), TURN (релей-сервер из RFC 8656, который пересылает медиа, когда прямой путь не работает) и ICE (алгоритм из RFC 8445, который параллельно проверяет все возможные маршруты и выбирает тот, что работает). В реальном мире примерно 15–20 % потребительских WebRTC-сессий нуждаются в TURN, чтобы соединиться, и эта доля доходит до 100 % для AI voice-агентов и большинства IoT-сценариев – потому что у одной стороны вообще нет публичного пути наружу. Если этот слой ошибиться, продукт окажется сломанным ровно в тех сетях, которыми ваши клиенты пользуются чаще всего: корпоративные офисы, университетский Wi-Fi, мобильные данные за carrier-grade NAT и любые конструкции с симметричным NAT посередине.

Зачем это понимать

Если вы делаете продукт на WebRTC – приложение видеоконференций, телемедицинскую консультацию, браузерный просмотр видеонаблюдения, виджет поддержки в чате, AI voice-агента – то NAT traversal – это самая частая причина, по которой звонок не соединяется. Сигналинг отработал, камеры включились, SDP обменялись, пользователь видит «соединяемся…» – и дальше тишина. Почти каждый такой провал упирается в ICE, STUN или TURN. Продакт-менеджер или основатель, который не понимает этого слоя, в итоге платит вендору вроде Twilio или Daily.co за задачу, у которой есть open-source решения, или гоняет TURN-кластер, который тихо съедает шестизначные суммы в год на egress AWS, потому что трафик никто не направил через дешёвый транзит. Эта статья объясняет, что делает каждый кусок, когда он нужен, сколько стоит и какие практические дефолты должен отгрузить стриминг- или real-time-продукт в 2026 году.

Что такое NAT, в одном абзаце

Network Address Translation – NAT – это трюк, который позволяет всем устройствам в доме делить один публичный IP-адрес. Ваш ноутбук, телефон, телевизор и термостат имеют приватные IP-адреса во внутренней сети (192.168.x.x или 10.x.x.x), а у роутера один публичный адрес (203.0.113.45). Когда ноутбук шлёт пакет наружу, роутер переписывает source с приватного адреса ноутбука на публичный адрес роутера и запоминает соответствие в таблице. Когда приходит ответ, роутер по таблице переписывает destination обратно с публичного на приватный и доставляет пакет ноутбуку. Это механизм, который IETF описал в RFC 4787 (январь 2007), и причина, по которой публичный интернет не исчерпал IPv4-адреса в 2008 году, как предсказывала каждая прогнозная модель. Цена: устройство за NAT недостижимо снаружи, пока что-то изнутри не пробьёт «дырку» наружу. Для исходящего HTTP-запроса в Google это невидимо. Для входящего WebRTC-звонка с чужого телефона это и есть вся задача.

Четыре сорта NAT и тот, который ломает день

Разные NAT-коробки ведут себя по-разному, когда устройство отправляет пакеты наружу, а другое пытается ответить. Классическая таксономия пришла из RFC 3489 (март 2003) – первой спецификации STUN – и хотя RFC 8489 заменил её, эти четыре названия – то, как индустрия говорит про NAT в 2026 году.

Первый – Full-Cone NAT. Как только устройство за NAT отправляет пакет с внутренней пары (X, x) на любой внешний адрес, NAT создаёт маппинг на внешнюю пару (X', x'), и любой внешний хост в интернете может слать пакеты на (X', x'), и они дойдут до (X, x). Самый разрешающий. Самый удобный для WebRTC.

Второй – Address-Restricted Cone NAT. Маппинг создаётся так же, но через него могут отвечать только те внешние хосты, на которые устройство уже отправляло пакеты (по IP, без проверки порта). Большинство домашних роутеров ведут себя именно так.

Третий – Port-Restricted Cone NAT. То же, но внешний хост должен совпадать и по IP, и по source port, с которым устройство к нему обращалось. Для WebRTC всё ещё решаемо: STUN-обмен говорит каждой стороне, что увидит другая сторона, и они одновременно пробивают парные «дырки».

Четвёртый – Symmetric NAT. NAT назначает разный внешний порт для каждого разного destination. Маппинг, который видел STUN-сервер, – (X', x'_stun) – не тот маппинг, который нужен пиру, потому что пир сидит на другом адресе и получает другой порт (X', x'_peer). STUN-кандидат бесполезен. Два пира не могут друг друга найти без помощи.

Современная более точная терминология из RFC 4787 делит поведение NAT на два независимых свойства: поведение маппинга (как NAT выбирает внешний порт – endpoint-independent, address-dependent, address-and-port-dependent) и поведение фильтрации (какие внешние хосты могут пробиваться через маппинг обратно). NAT с endpoint-independent mapping и endpoint-independent filtering – это то, что обычно называют «Full-Cone»; NAT с address-and-port-dependent mapping – это «Symmetric». IETF предпочитает термины RFC 4787, потому что они точны; стриминговая индустрия по-прежнему пользуется четвёркой названий, потому что все понимают, о чём речь.

Плохая новость для WebRTC: значимая доля carrier-grade NAT (CGNAT) у мобильных операторов и заметная часть корпоративных NAT ведут себя как Symmetric. Хорошая новость: у ICE есть запасной путь – TURN – который этот случай вытягивает. Вся архитектура ниже придумана ровно для того, чтобы симметричный NAT не убивал звонок.

Рис. 1. Как NAT переписывает пакет на выходе и обратно, и четыре классические разновидности NAT, которые решают, смогут ли два пира за двумя NAT вообще найти друг друга напрямую.

STUN – устройство узнаёт свой публичный адрес

STUN – Session Traversal Utilities for NAT – самый маленький из четырёх протоколов. Это инструмент с одной задачей: устройство спрашивает публичный STUN-сервер «как мой адрес выглядит снаружи?», и STUN-сервер отвечает публичным IP и портом, которые он увидел в запросе. Текущая спецификация – RFC 8489, опубликована в феврале 2020 года, она заменяет RFC 5389 от 2008-го. На проводе это 20-байтный фиксированный заголовок плюс пары атрибут-значение, поверх UDP (по умолчанию порт 3478), TCP (порт 3478) или TLS поверх TCP (порт 5349).

Обмен – один round trip. Клиент посылает Binding Request STUN-серверу. Сервер видит пакет, приходящий с переписанной NAT-ом пары (X', x'), и копирует её в атрибут XOR-MAPPED-ADDRESS в Binding Response. Клиент теперь знает свой публичный адрес – по крайней мере тот, который этот конкретный NAT даёт для этого конкретного destination. Это называется server-reflexive candidate, сокращённо srflx.

В этой формулировке важны две детали. Первая – server-reflexive: адрес – это отражение, которое увидел сервер, а не абсолютное свойство устройства. Симметричный NAT даст разный reflexive-адрес для каждого destination – ровно поэтому STUN в одиночку не справляется с симметричным NAT. Вторая – candidate: адрес – один из нескольких возможных маршрутов для входящего пакета, и ICE будет ранжировать его среди прочих (host, relay) и пробовать в порядке приоритета.

STUN-серверы намеренно stateless и бесплатны. Google держит stun.l.google.com:19302 как публичный бесплатный STUN, аналогично Cloudflare (stun.cloudflare.com:3478) и Mozilla. Стоимость своего STUN-сервера копеечная – coturn на $5-облачной VM держит десятки тысяч запросов в секунду – и большинство команд всё-таки ставят свой, чтобы не зависеть от чужого аптайма.

STUN не пересылает медиа. STUN не аутентифицирует пиров. STUN не переживает смену сети. Это однозапросный-однооветный зонд, чья единственная задача – рассказать устройству, как его видит внешний мир. Протоколы, которые делают реальную работу – TURN и ICE – построены поверх формата сообщений STUN.

TURN – релей-сервер для случая, когда ничего другого не работает

TURN – Traversal Using Relays around NAT – это запасной путь, когда прямая peer-to-peer связность невозможна. Текущая спецификация – RFC 8656, тоже февраль 2020 года, обсолетит оригинальный TURN RFC (5766, апрель 2010) и его IPv6-дополнение (RFC 6156, апрель 2011). TURN расширяет STUN: TURN-сервер – это STUN-сервер с дополнительной возможностью работать релеем медиа.

Механика простая. WebRTC-клиент подключается к TURN-серверу и просит выделить публичный адрес и порт – «allocation». TURN-сервер отвечает публичной парой (T, t), зарезервированной за этим клиентом. Всё, что приходит на (T, t) из интернета, пересылается по уже открытому клиент-серверному соединению WebRTC-клиенту. Всё, что WebRTC-клиент хочет отправить пиру, он шлёт через TURN-сервер, который пересылает это с (T, t) на адрес пира. Allocation живёт, пока клиент его обновляет (по умолчанию срок жизни 600 секунд; клиент обновляет каждые 5 минут).

Из этой механики следуют два важных факта. Первый – каждый байт медиа проходит через TURN-сервер дважды: один раз от клиента A к TURN, второй раз от TURN к клиенту B. Для звонка на 2 Мбит/с TURN-сервер пропускает 4 Мбит/с на звонок. Для 1000 одновременных релеящихся звонков – 4 Гбит/с в каждую сторону. Поэтому TURN-трафик – главная статья бюджета на инфраструктуру WebRTC. Второй – задержка, которую добавляет TURN, равна удвоенному round-trip между клиентом и TURN-сервером: обычно 20–60 мс для хорошо расположенного кластера, 100+ мс, если TURN на не том континенте.

TURN-серверы требуют аутентификации. RFC 8656 описывает схему с короткоживущими учётками: application-сервер выдаёт клиенту username (обычно Unix-timestamp плюс идентификатор пользователя) и пароль (HMAC-SHA1 от username с shared secret, который известен только app-серверу и TURN-серверу). TURN-сервер проверяет HMAC по своему shared secret и принимает клиента, никогда не видя его user-id. Это даёт stateless TURN-кластер по части аккаунтов и защищает от злоупотреблений: утёкшая учётка истекает через минуты.

TURN слушает UDP/3478, TCP/3478 и TLS поверх TCP на 5349. Большинство продакшн-инсталляций ещё открывают TLS на 443 – этот кандидат в URL помечается как «turns» – потому что 443 выглядит как HTTPS для корпоративных файрволов, которые режут всё остальное. Итог: TURN-over-TLS-on-443 – универсальный fallback, который пробивается через почти любую сеть. Цена – звонок идёт по TCP (плохо для latency) вместо UDP, но звонок, который соединился с 200 мс задержки, лучше звонка, который не соединился вообще.

Продакшн-реальность TURN в 2026-м выглядит так. Coturn (open source, изначально написан Олегом Москаленко) – дефолт, к которому тянутся все; eturnal (от ProcessOne, той же команды, что делает ejabberd) – более активно поддерживаемый форк, на который часть команд начала переезжать; Pion TURN – чистый Go-имплементейшн, популярный в Kubernetes-нативных деплоях. Облачные TURN-провайдеры – Twilio, Cloudflare Calls, Daily.co, Xirsys, Subspace – заворачивают тот же протокол, берут оплату по гигабайтам перевезённого трафика и добавляют сверху свой anycast-роутинг.

Рис. 2. TURN как медиа-релей. Каждый пакет между двумя пирами проходит через TURN-сервер дважды – именно поэтому TURN-трафик доминирует в бюджете WebRTC-инфраструктуры.

ICE – алгоритм, который выбирает путь

ICE – Interactive Connectivity Establishment – это алгоритм, который связывает STUN и TURN воедино. Текущая спецификация – RFC 8445, июль 2018 года, обсолетит RFC 5245 от 2010-го. ICE делает три вещи: собирает все возможные адреса, по которым каждый пир может быть доступен, спаривает каждый локальный адрес с каждым удалённым в список кандидатных пар и тестирует каждую пару, пока не найдёт работающий маршрут. Смысл ICE – не зашивать ни одну стратегию NAT traversal как единственно правильную, а параллельно пробовать все правдоподобные пути и оставлять тот, что заработал.

Кандидат бывает четырёх видов. Host candidate – реальный сетевой адрес устройства: интерфейс Wi-Fi, 5G, проводной Ethernet. Server-reflexive candidate – публичный адрес, узнанный от STUN-сервера (тот самый srflx выше). Peer-reflexive candidate – публичный адрес, который выяснился во время connectivity check: STUN-зонд пира показал устройству адрес, который оно ещё не видело – это случается, когда NAT-маппинг создаётся прямо во время звонка. Relay candidate – relayed transport address, выделенный TURN-сервером.

Connectivity check использует то же сообщение STUN Binding Request, но уже peer-to-peer, а не клиент-сервер. Каждую пару тестируют в порядке приоритета; формула приоритета ICE сначала ставит host, потом server-reflexive, потом peer-reflexive, потом relay. Пара становится «valid», когда одна сторона шлёт Binding Request другой и получает Binding Response с ожидаемого адреса. Первая valid-пара уходит в слот «selected pair», и звонок соединяется; ICE продолжает в фоне тестировать остальные и может переключиться на лучшую, если найдёт.

После того как пара выбрана, тот же STUN-обмен переиспользуется, чтобы держать путь живым. RFC 7675 описывает протокол consent freshness: каждые 15 секунд каждая сторона шлёт Binding Request по выбранной паре и ждёт ответ в течение 30 секунд, иначе пара объявляется мёртвой и ICE рестартует. Это и есть механизм, который переживает смену сети – новый путь пересогласуется прозрачно.

Trickle ICE – отдельная спецификация в RFC 8838 (январь 2021) – это оптимизация, которой по умолчанию пользуется почти любой современный WebRTC-стек. Вместо того чтобы дожидаться, пока соберутся все кандидаты, и потом отправлять их пиру разом, каждая сторона шлёт кандидаты в сигнальный канал по мере того, как находит. Обе стороны могут начать connectivity checks на ранних кандидатах, пока поздние ещё собираются. Для большинства звонков это сокращает время до соединения с 2–3 секунд до меньше 500 миллисекунд.

Рис. 3. Сбор ICE-кандидатов и connectivity check. Каждый пир параллельно собирает host, server-reflexive и relay-кандидаты; шлёт их по сигнальному каналу через trickle сразу после получения; спаривает каждый локальный с каждым удалённым; гоняет STUN-зонды на каждой паре, пока не найдёт работающий маршрут с самым высоким приоритетом.

Сценарий end-to-end – что происходит, когда вы нажимаете «войти»

Пользователь нажимает «войти» в браузере. Браузер открывает камеру, микрофон, создаёт RTCPeerConnection. Application-сервер заранее отдал браузеру список ICE-серверов – URL STUN и URL TURN с короткоживущими учётками. Браузер запускает ICE.

Параллельно браузер делает пять вещей. Перечисляет все сетевые интерфейсы – Wi-Fi, 5G, Ethernet – и создаёт host-кандидат на каждый. Шлёт STUN Binding Request STUN-серверу с каждого интерфейса и ждёт server-reflexive candidate. Шлёт TURN Allocate request TURN-серверу с каждого интерфейса и ждёт relay candidate. Генерирует SDP offer, в котором перечислены все кандидаты, собранные на этот момент. И отправляет offer через сигнальный канал приложения (WebSocket, HTTP long-poll, gRPC – транспорт сигналинга вне scope-а ICE) другому пиру.

Другой пир делает симметричное. Принимает offer, генерирует SDP answer со своим списком кандидатов, отправляет answer обратно. Обе стороны trickle-ом докидывают дополнительные кандидаты по мере сбора. Каждая сторона спаривает каждый локальный с каждым удалённым, считает приоритет каждой пары по формуле из RFC 8445 §6.1.2.3 (по сути 2^32 × min(G,D) + 2 × max(G,D) + (G > D ? 1 : 0)), сортирует пары по приоритету и начинает слать STUN Binding Requests на каждую пару по порядку с небольшим пейсингом (Ta, по умолчанию 50 мс).

Пара срабатывает, когда Binding Request получает Binding Response с ожидаемым transaction ID и аутентификацией. Первая сработавшая пара становится selected pair, медиа начинает течь, и пользователь слышит собеседника. ICE ещё несколько секунд продолжает тестировать другие пары; если завершается лучшая пара (host-host всегда выигрывает у host-relay), медиа молча переключается на неё.

Суммарное время от «клика» до «первого принятого кадра» на типичном звонке «дом-дом» в 2026 году: около 300–600 мс. На звонке «дом-корпоративный файрвол», где работает только TLS-over-443 TURN-кандидат: около 800–1200 мс. Разброс почти весь идёт от того, сколько занимает обнаружение, что более дешёвые кандидаты не работают.

Рабочий пример – расчёт TURN-трафика для продукта на 1000 мест

Основатель планирует инфраструктуру для продукта видеовстреч с расчётом на 1000 одновременных one-on-one звонков в пик. Средний битрейт звонка: 1,5 Мбит/с видео плюс 50 кбит/с аудио в одну сторону, итого ~1,6 Мбит/с × 2 направления = 3,2 Мбит/с на звонок. Индустриальный baseline TURN-релея для потребительского микса – 15–20 % сессий; возьмём консервативно 20 %.

Шаг 1: сколько звонков идут через TURN? 1000 × 20 % = 200 релеящихся.

Шаг 2: TURN-трафик на один релеящийся звонок. Полный медиа-стрим звонка проходит через TURN-сервер дважды – по одному разу в каждую сторону. Значит трафик на TURN = 3,2 Мбит/с × 2 = 6,4 Мбит/с на звонок. (Некоторые источники считают только одно направление, потому что TURN «посередине» – это неверно. TURN видит ingress от одного пира и egress на другого для каждого байта, оба плеча считаются.)

Шаг 3: суммарная пропускная способность TURN. 200 × 6,4 Мбит/с = 1280 Мбит/с = 1,28 Гбит/с в пике sustained.

Шаг 4: месячный egress. 1,28 Гбит/с × 86 400 с/день × 30 дней × 12,5 % коэффициента busy-hour-to-average (эвристика для видеопродуктов) ≈ 410 ТБ/месяц egress.

Шаг 5: цена на гиперскейлере. Egress AWS EC2 в 2026 году – порядка $0,085/ГБ на первые 10 ТБ, падает до $0,05/ГБ выше 150 ТБ; средневзвешенно на 410 ТБ – около $0,054/ГБ. 410 ТБ × $0,054/ГБ ≈ $22 000/месяц только за TURN egress.

Шаг 6: где живёт экономия. Тот же TURN на колокейшен-кластере bare-metal с 95-перцентилем транзита по $0,50–$1,00 за Мбит/с в месяц и пиком 1,28 Гбит/с ≈ $700–$1400/месяц за транзит. 20-кратный разрыв между гиперскейлерным egress и колокейшен-транзитом – причина, по которой любой WebRTC-продукт значимого масштаба либо держит свой TURN-кластер на bare-metal, либо договаривается с TURN-провайдером, чьё ценообразование отражает bare-metal-экономику (Cloudflare Calls, Daily.co, Subspace).

Если продукт – AI voice-агент или односторонний IoT-сценарий, где 100 % трафика обязано идти через TURN (у одной стороны нет публичного пути в принципе), та же арифметика даёт в 5 раз больше трафика и в 5 раз больше денег. Это и есть форма деплоя, которая в 2025–2026 годах превратила TURN-трафик в экзистенциальную статью бюджета AI voice-продуктов.

Типичные ошибки

Ошибка 1: полагаться на STUN-only на симметричном NAT. Многие демки используют только STUN. Они работают отлично – до того момента, как в звонок зайдёт первый пользователь с мобильного оператора (а большинство держат симметричный CGNAT), и связь молча сломается. Всегда включайте хотя бы один TURN-сервер в конфигурацию ICE. Fallback – это весь смысл архитектуры.

Ошибка 2: TURN только по UDP. Корпоративные файрволы регулярно режут UDP вообще и заворачивают всё в TCP/443. TURN-кандидат turn:host:3478?transport=udp внутри такой сети бесполезен. Всегда поднимайте и turns:host:443?transport=tcp (TLS поверх TCP на 443). Браузер выберет самый дешёвый рабочий вариант.

Ошибка 3: долгоживущие TURN-учётки. Часть туториалов вендоров до сих пор показывает TURN-учётки, вшитые в клиентский JavaScript. Любой, кто открыл devtools, вытащит их и будет годами пользоваться вашим TURN-кластером как бесплатным egress. Всегда выпускайте короткоживущие учётки server-side, подписанные shared secret, привязанные к звонку. RFC 8656 §9.2 описывает формат HMAC-SHA1.

Ошибка 4: один TURN-сервер на один континент. Задержка, которую добавляет TURN, равна удвоенному RTT между клиентом и TURN-сервером. Звонок между двумя пользователями в Сан-Паулу через TURN-сервер в Вирджинии добавляет 240 мс задержки на пустом месте. Деплойте TURN географически рядом с пользователями – хотя бы по одному кластеру на крупный континент, с anycast или geo-DNS впереди.

Ошибка 5: забыть про consent freshness. RFC 7675 предписывает поддерживать путь живым STUN Binding Request'ом каждые 15 секунд. Часть самописных стеков это пропускает – всё работает отлично, пока NAT-маппинг не отвалится по таймауту (большинство домашних роутеров реклаймят UDP-маппинги через 30–60 секунд тишины) и звонок не упадёт прямо во время разговора без всякой диагностики.

Где здесь Фора Софт

Фора Софт делает WebRTC- и конференц-софт с 2005 года: 239+ отгруженных проектов в видеоконференциях, OTT, телемедицине, e-learning, видеонаблюдении и AR/VR. NAT traversal – это та часть WebRTC, которую большинство команд недооценивают: SDP offer/answer описан в каждом туториале, камеры и микрофоны хорошо поддерживаются в браузере, SFU-варианты (mediasoup, Janus, LiveKit, Pion) понятны. Что отличает продукт, который работает в демо, от продукта, который работает у бразильского клиента на корпоративном офисном Wi-Fi, – это TURN-кластер: его расположение, история учёток, observability и дисциплина по стоимости. Мы поднимаем TURN-кластеры для клиентов на выделенном bare-metal и в гиперскейлерах, инструментируем их с per-call атрибуцией стоимости и сочетаем с мультирегиональными SFU-инсталляциями там, где этого требует топология звонка.

Главное

  • NAT даёт всем устройствам в доме делить один IP; для WebRTC это причина, по которой прямой peer-to-peer путь почти никогда не работает.
  • STUN (RFC 8489) позволяет устройству узнать свой публичный адрес; дёшево, stateless, бесплатно.
  • TURN (RFC 8656) пересылает медиа, когда прямой путь не работает; дорого, stateful, главная статья бюджета WebRTC.
  • ICE (RFC 8445) собирает кандидаты, спаривает их и гоняет STUN connectivity checks; выигрывает пара с самым высоким приоритетом, которая работает.
  • Всегда поднимайте TURN, всегда открывайте TLS-over-TCP на 443, всегда выпускайте короткоживущие учётки.
  • Закладывайте 15–20 % TURN-релея для потребительских продуктов и 100 % для AI voice и большинства IoT-форм.

Что почитать дальше

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.