MAATRIX / Блог / Через мобильный интернет всё иначе: что операторская сеть делает с вашими соединениями

Через мобильный интернет всё иначе: что операторская сеть делает с вашими соединениями

MAATRIX

Приложение отлично работает у всех, кто тестировал его по Wi-Fi и на кабеле в офисе, а в проде часть пользователей с мобильных телефонов жалуется на разрывы соединений, зависшие вебсокеты и звонки, которые не устанавливаются. Часто причина не в коде и не в сервере, а в том, что мобильная сеть — не «тот же интернет, только по воздуху». У неё своя архитектура на уровне оператора, которая иначе распределяет IP-адреса, иначе ведёт себя во времени и иногда сама трогает трафик по дороге. Разбираем, что происходит между телефоном и сервером в 3G/4G/5G-сети и что из этого стоит закладывать в архитектуру бэкенда.

CGNAT: почему у мобильного клиента нет своего IP

На домашнем проводном интернете абонент почти всегда получает адрес, который так или иначе закреплён за одним устройством или квартирой. На мобильной сети иначе: оператор обслуживает сотни тысяч абонентов через ограниченный пул IPv4-адресов и делает это через CGNAT — Carrier-Grade NAT на пограничном оборудовании сети, а не на устройстве абонента. За одним внешним IP оператора одновременно «прячутся» тысячи телефонов, каждому из которых выделяется свой диапазон портов.

Отсюда два следствия, которые касаются серверной части напрямую:

  • С сервера вы видите один и тот же внешний IP у множества разных пользователей. Любая логика, которая привязывает лимиты или бан-листы к IP-адресу, на мобильном трафике начинает бить по случайным людям — забанили одного абусера, под раздачу попала вся группа абонентов за тем же внешним адресом оператора. Подробнее о том, откуда берётся эта путаница и как её диагностировать, — в статье про двойной NAT у клиента.
  • Входящие соединения к телефону оператор в общем случае не пропускает. Обращение снаружи придёт на внешний IP оператора, а он не может понять, какому из тысяч абонентов за этим адресом адресован конкретный пакет. Проброс портов и любые схемы, ждущие входящего соединения к мобильному клиенту, на практике не работают — та же проблема с другой стороны разобрана в статье про VPN через 4G-модем.

Конкретная реализация CGNAT (глубина NAT, размер диапазона портов, время жизни трансляции) у каждого оператора своя и не документируется публично — рассчитывать стоит не на конкретные цифры, а на сам факт: прямого маршрута снаружи к мобильному устройству нет и не будет.

Что это ломает: P2P, WebRTC и входящие соединения

Прямое следствие CGNAT — любая архитектура на прямом P2P-соединении между двумя клиентами на мобильной сети работает нестабильно в зависимости от того, как повезло с типом NAT у конкретного оператора в конкретный момент.

Для WebRTC это значит, что STUN-согласование (когда оба клиента узнают свой внешний адрес и пробуют достучаться друг до друга напрямую) на мобильных сетях срабатывает заметно реже, чем на обычном домашнем интернете — двойной NAT (NAT роутера у одного собеседника плюс CGNAT оператора у другого) делает прямой путь маловероятным. Рабочий сценарий для мобильной аудитории — закладывать TURN-relay как основной путь, а не резервный: TURN-сервер выступает посредником, к которому оба участника подключаются исходящим соединением. Это дороже по трафику и добавляет хоп задержки, но предсказуемо работает для доли аудитории, которой прямой P2P-путь недоступен в принципе, а не только «когда повезёт с NAT».

То же касается любого протокола, где сервер должен инициировать соединение к клиенту: push-уведомления через SSE или long-polling с постоянным исходящим от клиента соединением работают предсказуемо, а схема, где сервер сам «стучится» к мобильному клиенту по IP и порту — нет. Практический вывод: для мобильной аудитории соединение всегда инициирует клиент, а доставка данных «от сервера к телефону» — это либо платформенный push (APNs, FCM), либо постоянное исходящее соединение от клиента (WebSocket, SSE), но никогда не входящее соединение к клиенту.

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

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

Арендовать сервер

Задержка и джиттер радиоканала

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

Переменчивость — ключевое отличие. На кабеле задержка от пакета к пакету скачет на единицы миллисекунд, на мобильной сети разброс может быть кратно больше: сигнал слабее у края зоны покрытия, канал делится между всеми абонентами в соте, устройство постоянно переключается между режимами энергосбережения (следующий раздел). Это классический джиттер — не то, насколько далеко сервер, а то, насколько предсказуемо к нему доходят пакеты. Подробный разбор механики джиттера, почему он опаснее среднего пинга для realtime-трафика и как его измерять mtr и iperf3 — в статье джиттер важнее среднего пинга. Всё сказанное там применимо к мобильной сети напрямую, только исходный уровень джиттера обычно выше, а стабилизировать его на своей стороне сервера нельзя — можно только заложить в приложение запас на то, что он будет.

Хендоверы между вышками: обрывы, которые нужно пережить

Когда абонент перемещается — идёт по улице, едет в метро или в машине, — телефон в какой-то момент переключается с одной базовой станции на другую, потому что сигнал новой вышки становится лучше сигнала текущей. Это переключение — хендовер (handover), и сеть старается провести его без разрыва, заранее готовя соединение на новой станции.

На практике идеально бесшовно получается не всегда. В момент хендовера возможна короткая пауза в передаче данных — от долей секунды до нескольких секунд в худшем случае (тоннель, зона плохого покрытия, резкая смена соты в движении), — а в отдельных случаях у устройства может обновиться сама IP-сессия, если хендовер проходит между разными сегментами опорной сети оператора. Для TCP-соединения долгая пауза с точки зрения сервера выглядит как зависшее или разорванное соединение; для UDP-потока — как всплеск потерь и джиттера.

Для серверной части это не «нестабильная сеть у клиента, которую надо чинить», а регулярный сценарий, который приложение обязано пережить, а не просто уронить:

  • Не считать разрыв TCP-соединения или таймаут запроса поводом сбросить состояние сессии на сервере — пауза в секунды на мобильной сети это норма.
  • Клиент должен прозрачно переустанавливать соединение (реконнект вебсокета, повторная авторизация сессии) без потери контекста работы пользователя.
  • Для длительных операций закладывать возобновление с середины (Range-запросы для скачивания, chunked upload с докачкой), а не рассчитывать, что одно TCP-соединение проживёт всю операцию целиком.

Спящий радиомодуль и задержка на «пробуждение»

Радиомодуль — самый прожорливый по энергии компонент телефона, и устройства вместе с сетью совместно борются за то, чтобы он не держал соединение активным без нужды. Между сессиями активной передачи модуль переходит в режимы с пониженным энергопотреблением — «дремлет», пока нет данных для передачи, и должен заново «договориться» с сетью, прежде чем отправить или принять следующий пакет.

Для приложения это выглядит как дополнительная, не всегда предсказуемая задержка перед первым пакетом после паузы в активности: если клиент какое-то время ничего не отправлял (пользователь читал экран, приложение было в фоне), следующий запрос может уйти с заметной добавочной задержкой на «побудку» радиомодуля. Точная величина зависит от модели устройства, настроек оператора и типа сети и заранее не предсказуема, но сам эффект стоит закладывать как систематический, а не как случайный шум.

Практические следствия:

  • Первый запрос после паузы почти всегда медленнее последующих — не повод считать, что деградировал сервер.
  • Слишком частые фоновые keep-alive контрпродуктивны. Heartbeat каждые несколько секунд не даёт радиомодулю уйти в энергосберегающий режим — сажает батарею клиента без реальной пользы и грузит сервер. Держите интервал keep-alive настолько редким, насколько позволяет логика, и полагайтесь на TCP keepalive/таймауты вебсокета, а не на частый собственный пинг.
  • Таймауты на сервере должны быть терпимее к мобильному клиенту, чем к внутреннему вызову между микросервисами в одном дата-центре: то, что там — авария, для мобильного клиента после паузы — рядовая ситуация.

Операторские прокси: трафик меняется по дороге

Часть операторов пропускает трафик через промежуточные прокси и оптимизаторы, которые вмешиваются в содержимое соединения, а не просто маршрутизируют пакеты. Общая идея — снизить нагрузку на радиоканал и сэкономить трафик абонента: за счёт пережатия изображений на лету, кеширования статичного контента ближе к абоненту или иной модификации HTTP-ответов на пути. Конкретный набор техник и то, какие типы контента затрагиваются, у каждого оператора свои и не документируются публично — важен сам факт, что «отправлено сервером» и «получено клиентом» на мобильной сети не всегда совпадает байт в байт, если соединение идёт по обычному HTTP.

Что стоит учитывать на серверной стороне:

  • HTTPS закрывает вопрос вмешательства в контент. Зашифрованное соединение оператор в общем случае модифицировать на лету не может (максимум видит метаданные, кто с кем соединяется) — для трафика, где важна точность содержимого (изображения без потери качества, файлы с проверкой контрольной суммы, API-ответы с точным Content-Length), это снимает вопрос независимо от того, практикует ли конкретный оператор оптимизацию трафика.
  • Не полагайтесь на то, что заголовки кеширования долетят в исходном виде без шифрования — на HTTP-трафике прокси на пути может переинтерпретировать Cache-Control и ETag по-своему.
  • Если приложение само сжимает изображения под мобильную аудиторию, не удивляйтесь двойному пережатию на части устройств — это взаимодействие с чужой оптимизацией на пути, а не баг вашего пайплайна, и лечится тем же HTTPS.

Как проектировать бэкенд под мобильную аудиторию

Из перечисленного складывается набор принципов для архитектуры бэкенда, если заметная доля клиентов приходит с мобильных сетей.

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

POST /api/orders
Idempotency-Key: 8f14e45f-ceea-467e-9e21-3a3d1c9b6c4a

{"item_id": 42, "qty": 1}

Сервер при повторном запросе с уже виденным Idempotency-Key не создаёт заказ заново, а возвращает результат первой попытки. Это снимает главный риск ретраев на нестабильной сети: клиент отправил запрос, соединение прервалось на хендовере до получения ответа, и он не знает, дошёл ли запрос — но может повторить его безопасно, не создав дубликат.

Сами ретраи на клиенте стоит делать с экспоненциальной задержкой и джиттером в паузе между попытками, а не синхронно и сразу — иначе массовый разрыв соединений (например, у абонентов одной соты после короткого сбоя оператора) превращается в синхронный всплеск повторных запросов, который сам может уложить бэкенд не хуже DDoS-атаки. Разбор именно такого инцидента на стороне бэкенда — в статье ретраи без джиттера превратили чужой сбой в наш DDoS.

Не полагайтесь на входящие P2P-соединения к мобильным клиентам. CGNAT делает это ненадёжным по определению — проектируйте протокол так, чтобы соединение всегда инициировал клиент, а для P2P-сценариев (звонки, файлообмен) закладывайте TURN-relay как основной путь для мобильной аудитории, а не запасной вариант на случай неудачи STUN.

Не привязывайте лимиты и защиту от абуза жёстко к IP-адресу. Тысячи разных пользователей за одним CGNAT-адресом оператора — норма для мобильного трафика. Rate limiting должен комбинировать IP с идентификатором сессии, устройства или пользователя, а не банить по голому адресу.

Тестируйте реальный мобильный сценарий отдельно от Wi-Fi и проводного. Эмуляция задержки и потерь через tc qdisc netem на стенде — полезный первый шаг для проверки самой retry-логики:

tc qdisc add dev eth0 root netem delay 80ms 30ms loss 2% reorder 5%

Но она не воспроизводит ни CGNAT, ни хендоверы, ни сон радиомодуля — а именно эти три вещи чаще всего ломают приложения, прекрасно прошедшие тестирование на симулированной задержке. Прежде чем считать мобильный сценарий покрытым, стоит прогнать ключевые пользовательские сценарии (авторизация, длительная загрузка, realtime-соединение) с реального SIM-устройства в движении — пешком по улице или в транспорте, где хендоверы гарантированно случатся, — а не только на стационарном телефоне рядом с роутером в офисе, где мобильная сеть по факту ведёт себя почти как Wi-Fi.

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

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

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли пробросить порт на мобильном интернете, если попросить оператора?

В большинстве случаев нет — CGNAT работает на уровне оператора, и обычные тарифы не дают доступа к настройке трансляции портов на его пограничном шлюзе. Отдельные операторы предлагают платную опцию «белый IP» для мобильного интернета, но это скорее исключение, чем правило.

Правда ли, что 5G решает проблему CGNAT и джиттера?

Частично. 5G снижает базовую задержку радиоканала и повышает пропускную способность, но CGNAT — решение про нехватку IPv4-адресов у оператора, а не про поколение радиосети, и применяется одинаково и на 4G, и на 5G. Джиттер тоже никуда не девается — просто его абсолютные значения на 5G обычно ниже при прочих равных условиях покрытия.

Стоит ли делать отдельную версию API специально для мобильных клиентов?

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

Как понять на сервере, что проблема именно в мобильной сети клиента, а не в сервере?

Прямого способа узнать тип сети клиента нет, если он сам её не передаёт. Косвенный признак — короткие паузы в передаче данных, за которыми следует нормальное продолжение сессии без явной ошибки, и кластеризация проблем вокруг одного CGNAT-адреса оператора у разных, судя по остальным признакам, пользователей.

Обязателен ли TURN-сервер, если WebRTC и так работает у тестировщиков в офисе?

Именно тестирование в офисе на Wi-Fi и вводит в заблуждение — там NAT обычно один и предсказуемый. На мобильной аудитории доля соединений без прямого P2P-пути из-за CGNAT может быть заметной, и закладывать TURN-relay как рабочий, а не аварийный путь стоит заранее, а не после первых жалоб пользователей с мобильных сетей.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →