Почти год назад мы реализовали фичу строго по RFC — и положили продакшен. Откатили, разобрались, нашли причину. Спойлер: дело было не в DNS. Мы просто забыли, что порты — ресурс конечный.
Нужны мобильные прокси?Создайте прокси прямо сейчас!
Мы добавили полноценную поддержку SOCKS5 UDP ASSOCIATE (RFC 1928) на прокси-серверах iProxy.online. Через пару дней DNS перестал резолвить, туннели начали падать, а случайные сервисы уходили в таймаут. Откатили и начали копать. Корневая причина — исчерпание эфемерных портов на Linux: тысячи UDP-ассоциаций держали по выделенному порту, полностью осушив системный пул. Починка свелась к двум параметрам конфигурации. Найти проблему заняло куда больше времени.
SOCKS5, описанный в RFC 1928, поддерживает три команды: CONNECT (TCP-туннелирование), BIND (приём входящих TCP-соединений) и UDP ASSOCIATE (ретрансляция UDP-датаграмм). Большинство провайдеров прокси реализуют только CONNECT. UDP ASSOCIATE — самая сложная команда и единственная, которая нужна для real-time приложений: VoIP, игры, DNS-over-UDP, QUIC.
Как это работает:
Каждая UDP-ассоциация занимает отдельный эфемерный порт на сервере. Это ключевая деталь.
И вот ловушка. UDP — протокол без соединения. Нет FIN, нет RST. Как узнать, что клиент закончил работу, и освободить порт?
Мы выставили щедрые таймауты и не ограничили количество ассоциаций на клиента. Итог: сокеты копились.
В iProxy.online мы управляем инфраструктурой мобильных прокси в 100+ странах и через 600+ мобильных операторов. Реальные Android-телефоны превращаются в прокси-серверы. Если ты только разбираешься в теме, в нашем гайде по созданию 4G прокси-сети подробно описана архитектура. Наши бэкенд SOCKS5-серверы обрабатывают десятки тысяч одновременных соединений.
Около года назад мы решили реализовать полноценный UDP ASSOCIATE — стать по-настоящему RFC-совместимыми и открыть юзкейсы, которые конкуренты не поддерживают: проксирование VoIP, игрового трафика, QUIC-протоколов. Реализация была корректной. Тесты прошли. Выкатили в прод.
Прокси-инфраструктура такого масштаба — это когда каждое решение по протоколу имеет реальные последствия. В iProxy.online каждое Android-устройство работает как независимый мобильный прокси-сервер — SOCKS5, HTTP и теперь полный UDP — с управлением через единый дашборд или Telegram-бот. Хочешь посмотреть, как всё устроено, прежде чем читать, как мы это сломали? Создай мобильный прокси бесплатно на 48 часов — без привязки карты.
Через пару дней на продакшен-серверах начали проявляться странные, казалось бы, не связанные друг с другом симптомы:
Отказ был не плавным, а обрывным. Всё работало нормально часами, а потом несколько сервисов падали одновременно на одном хосте. Мы откатили UDP ASSOCIATE и начали разбирать.
Первый инстинкт оказался неверным. Проверили DDoS, утечки памяти, нагрузку на диск, CPU — всё в норме. Application-level health checks были зелёными ровно до момента, когда всё умирало.
Прорыв дали метрики уровня ядра, которые большинство команд даже не мониторит:
node_sockstat_UDP_inuse рос в десятки тысяч. На здоровом сервере этот показатель — пара сотен. У нас перевалило за 20K.
Счётчики ICMP Type 3 Code 3 (port unreachable) резко выросли. Это ядро говорит: «Не могу выделить исходный порт для исходящего UDP-пакета».
Ручная проверка подтвердила:
ss -u state all | wc -l
# 28 431
cat /proc/net/sockstat
# UDP: inuse 28419
Диапазон эфемерных портов Linux по умолчанию — 32768–60999, примерно 28 000 портов. Мы использовали почти все.
Арифметика простая. У Linux конечный пул эфемерных портов — около 28K по умолчанию. Каждый UDP ASSOCIATE съедает один. Сотни одновременных ассоциаций плюс медленная очистка — пул исчерпан.
Когда эфемерные порты кончаются, всё на сервере, что пытается открыть новый UDP-сокет, падает. Включая DNS — каждый исходящий DNS-запрос требует эфемерного порта, чтобы отправить пакет на порт 53. Резолвинг имён умирает, и вдруг всё на сервере сломано по причинам, которые к DNS не имеют никакого отношения.
Каскад:
Выделение портов → каждый UDP ASSOCIATE вызывает bind() с портом 0, запрашивая у ядра следующий доступный эфемерный порт.
Накопление портов → порт остаётся занятым до закрытия TCP-соединения или срабатывания idle timeout. Щедрые таймауты — порты копятся быстрее, чем освобождаются.
Исчерпание пула → тысячи ассоциаций держат по порту, весь пул вычерпан. bind() начинает возвращать EADDRINUSE на каждый новый сокет.
Системный отказ → DNS-запросы падают (systemd-resolved тоже нужны эфемерные порты). WireGuard-хендшейки не проходят. NTP не работает. Syslog-over-UDP умирает тихо. Затем отказ DNS вызывает вторичный каскад — всё, что резолвит хостнеймы, перестаёт работать: health checks, подключения к базам, агенты мониторинга. Сервер выглядит «лежащим», хотя CPU, память и диск в норме.
HTTP health checks проходили — эндпоинт слушал. CPU, память, диск, пропускная способность — всё штатно. Метрики на уровне процессов — ничего необычного. SOCKS5-процесс был здоров. Исчерпался пул портов ядра, а ни один стандартный дашборд Grafana это не отслеживает.
Единственные метрики, которые сработали, мы добавили почти «на всякий случай»: счётчики сокетов на уровне ядра и частота ICMP-ошибок.
Починили двумя настройками. Дебаг занял куда больше времени, чем сам фикс.
1. Резко сократили idle timeout. Снизили таймаут бездействия UDP-ассоциаций с минут до секунд. Если через relay-порт за короткий интервал не прошло ни одной датаграммы — ассоциация рвётся, порт освобождается. Большинство легитимных UDP-сессий (DNS-запросы, NTP) завершаются за доли секунды. Долгоживущие сессии (VoIP, игры) поддерживают ассоциацию регулярным трафиком и не страдают.
2. Лимиты на одновременные ассоциации на клиента. Ограничили, сколько UDP-ассоциаций один клиент может держать одновременно. Это не даёт одному пользователю — или кривому клиенту — монополизировать пул портов. Лимит достаточно щедрый для реального использования, но останавливает неконтролируемое накопление.
Вместе эти меры вернули UDP_inuse с 28K до нескольких сотен. Перевыкатили UDP ASSOCIATE с новыми лимитами — с тех пор стабильно.
Сам фикс был простым — сложнее было построить поддержку SOCKS5 UDP ASSOCIATE, которая выдерживает десятки тысяч одновременных сессий и не жрёт системные ресурсы. Нужен мобильный прокси с полноценным UDP relay и правильным управлением жизненным циклом? iProxy.online работает на реальных Android-устройствах через 600+ операторов, с ротацией IP, поштучным управлением устройствами и тарифами от $6/мес. Начать бесплатный 48-часовой триал →
Если ты эксплуатируешь что-то, что массово открывает UDP-сокеты — SOCKS5 прокси, игровые серверы, VoIP-инфраструктуру, QUIC-балансировщики — добавь в стек мониторинга:
node_sockstat_UDP_inuse (node_exporter). Открытые UDP-сокеты в реальном времени. Норма — пара сотен. Если используешь Prometheus, метрика уже есть — нужна только панель и алерт. Рекомендуем порог — 5 000.node_netstat_Icmp_OutDestUnreachs (ICMP Type 3 Code 3, port unreachable). Всплески означают, что ядро отвечает на UDP-пакеты, прилетающие на порты, которые никто не слушает. Единицы в минуту — шум. Тысячи в секунду — пожар.ss -u state all | wc -l — быстрая проверка руками во время инцидента.cat /proc/net/sockstat — классический однострочник без зависимостей.Эти метрики не входят в стандартные дашборды. А должны. Подробнее о том, как поддерживать прокси-инфраструктуру в здоровом состоянии — в нашем гайде по оптимизации скорости и стабильности прокси.
Порты — конечный ресурс, планируй их расход. Мы бюджетируем CPU, RAM, диск, полосу. Эфемерные порты не бюджетирует никто. На сервере с десятками тысяч соединений ~28K портов — это немного. Расширить диапазон можно через sysctl -w net.ipv4.ip_local_port_range="1024 65535", но даже 64K конечны, если каждая ассоциация держит порт неопределённо долго.
RFC говорит ЧТО, а не КАК. RFC 1928 написан в 1996 году, когда «нагруженный» сервер обрабатывал сотни соединений. Механика протокола описана идеально. Про управление жизненным циклом портов, лимиты ресурсов или graceful degradation — ни слова. Если реализуешь любой протокол на масштабе — читай RFC для корректности, а управление ресурсами проектируй сам.
Метрики ядра ловят то, что пропускают метрики приложений. Health checks, Prometheus-скрейперы, HTTP-пинги — все говорили, что серверы здоровы. Ядро знало лучше. Если в мониторинге нет статистики сокетов и ICMP-счётчиков — у тебя слепая зона для целого класса отказов из-за исчерпания ресурсов. Мы столкнулись с похожей проблемой наблюдаемости при обнаружении скрытых сбоев TLS 1.3 на нашем флоте Android-устройств — другая причина, тот же урок.
UDP требует явного управления жизненным циклом. У TCP есть встроенный lifecycle — соединения открываются, передают данные и закрываются через определённый хендшейк. Порты переиспользуются после TIME_WAIT. У UDP ничего этого нет. Сокет висит открытым, пока что-то явно его не закроет. В relay-архитектурах нужно строить свой lifecycle, иначе потребление ресурсов растёт неограниченно.
Это не только проблема прокси. Любая инфраструктура, выделяющая UDP-сокеты по запросам пользователей, может упереться в тот же обрыв:
Если ты эксплуатируешь что-то из этого на масштабе — или управляешь фермой мобильных прокси — проверь sockstat сегодня. Возможно, ты ближе к обрыву, чем думаешь.
Мы реализовали SOCKS5 UDP ASSOCIATE корректно — по RFC, с тестами, задеплоили. И получили системный отказ, невидимый для стандартного мониторинга.
Вывод, который мы теперь считаем железным правилом: любая фича, выделяющая ресурсы на уровне ядра — порты, файловые дескрипторы, записи conntrack — требует явного управления жизненным циклом и бюджетирования ресурсов с первого дня. Не как фикс после первого аутейджа.
Порты как кислород. Не замечаешь, пока не кончатся.
Это реальный production-инцидент iProxy.online. Мы строим инфраструктуру мобильных прокси, превращая Android-телефоны в SOCKS5 и HTTP прокси-серверы в 100+ странах через 600+ операторов. Полная поддержка UDP ASSOCIATE — теперь с правильным управлением ресурсами. Попробовать iProxy →
SOCKS5 UDP ASSOCIATE — одна из трёх команд, описанных в RFC 1928. Позволяет клиенту ретранслировать UDP-датаграммы через SOCKS5 прокси, выделяя отдельный UDP-сокет на каждую ассоциацию. В отличие от CONNECT (TCP-туннелирование), UDP ASSOCIATE обрабатывает connectionless-трафик — DNS, VoIP, игры, QUIC.
По умолчанию — диапазон 32768–60999, примерно 28 000 портов. Проверить текущий диапазон: cat /proc/sys/net/ipv4/ip_local_port_range. Расширить: sysctl -w net.ipv4.ip_local_port_range="1024 65535" — до ~64 000 портов. Но даже расширенного диапазона может не хватить при интенсивной UDP-ретрансляции.
Каждый исходящий DNS-запрос требует эфемерного порта для отправки UDP-пакета на порт 53. Когда все эфемерные порты заняты другими UDP-сокетами, ядро не может выделить новый — и DNS-резолвинг отказывает по всей системе, хотя сам DNS-сервер полностью исправен.
Отслеживай node_sockstat_UDP_inuse в Prometheus/node_exporter — количество открытых UDP-сокетов в реальном времени. Для ручной проверки: ss -u state all | wc -l или cat /proc/net/sockstat. Настрой алерты на всплески ICMP Type 3 Code 3 (port unreachable) через node_netstat_Icmp_OutDestUnreachs.
Да. iProxy.online поддерживает полноценный SOCKS5 UDP ASSOCIATE наряду с CONNECT и HTTP прокси. После инцидента, описанного в этой статье, мы добавили лимиты на клиента и агрессивные idle timeout для предотвращения исчерпания портов — при полностью рабочем UDP relay для VoIP, игр и QUIC-трафика.
Неважно, эксплуатируешь ли ты TURN-серверы, игровую инфраструктуру или сеть мобильных прокси — управление эфемерными портами критично. iProxy.online — это боевой SOCKS5 с UDP ASSOCIATE, управление через дашборд и API, настройка за 5 минут на любом Android-телефоне. Создай мобильный прокси бесплатно на 48 часов →