Майже рік тому ми реалізували 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 — найскладніша команда, і водночас та, що потрібна для реальних застосувань: VoIP, ігровий трафік, DNS-over-UDP, QUIC.
Як це працює:
Клієнт відкриває TCP-з'єднання керування з SOCKS5-сервером.
Через це TCP-з'єднання клієнт надсилає запит UDP ASSOCIATE.
Сервер виділяє окремий UDP-сокет на ефемерному порту та повертає адресу й номер порту.
Клієнт відправляє UDP-датаграми на цей relay-порт. Сервер пересилає їх до адресата та повертає відповіді.
Асоціація живе, поки TCP-з'єднання керування не закриється або не спрацює таймаут сесії.
Кожна UDP-асоціація займає окремий ефемерний порт на сервері. Ось ключова деталь.
А ось пастка. UDP — протокол без з'єднання. Ніякого FIN, ніякого RST. Як зрозуміти, що клієнт закінчив, і звільнити порт?
Чекати закриття TCP-з'єднання керування — сигнал за RFC. Але клієнти можуть тримати його відкритим нескінченно.
Idle timeout. Якщо датаграми не приходять X секунд — вбиваємо асоціацію. Але якщо таймаут завеликий, сокети накопичуються.
Ми виставили щедрі таймаути й не обмежили кількість асоціацій на одного клієнта. Сокети накопичились.
У iProxy.online ми керуємо інфраструктурою мобільних проксі у 100+ країнах і 600+ мобільних операторах, перетворюючи реальні Android-пристрої на проксі-сервери. Якщо ви новачок у цій темі, наш гайд про побудову мережі 4G-проксі детально описує архітектуру. Наші бекенд-сервери SOCKS5 обробляють десятки тисяч одночасних з'єднань.
Приблизно рік тому ми вирішили впровадити повну підтримку UDP ASSOCIATE — стати по-справжньому RFC-сумісними та відкрити юзкейси, які більшість конкурентів не тягне: проксіювання VoIP, ігрового трафіку, QUIC-протоколів. Реалізація була коректною. Тести пройшли. Викатили.
Проксі-інфраструктура такого масштабу — це коли кожне рішення щодо протоколу має реальні наслідки. У iProxy.online кожен Android-пристрій працює як окремий мобільний проксі сервер — SOCKS5, HTTP і тепер повний UDP — з керуванням через єдиний дашборд або Telegram-бот. Хочете побачити, як система працює, перш ніж читати, як ми її зламали? Створити мобільний проксі — безкоштовний 48-годинний тріал, без прив'язки картки.
За кілька днів після деплою на продакшн-серверах почалися дивні, на перший погляд не пов'язані між собою симптоми:
Збій був не поступовим — а обривним. Все працювало нормально годинами, а потім кілька сервісів падали одночасно на одному хості. Ми відкотили UDP ASSOCIATE і почали розбиратися.
Перший інстинкт виявився хибним. Перевірили DDoS-атаки, витоки пам'яті, навантаження на диск, CPU — все в нормі. Аплікаційні хелсчеки горіли зеленим аж до моменту, коли все вмирало.
Прорив прийшов від низькорівневих системних метрик, на які більшість команд навіть не дивиться:
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-асоціація з'їдає один. Сотні одночасних асоціацій плюс повільне очищення — і пул вичерпано.
Коли ефемерні порти закінчуються, будь-що на сервері, що намагається відкрити новий UDP-сокет, — ламається. Включно з DNS: кожен вихідний DNS-запит потребує ефемерного порту, щоб відправити запит на порт 53. Резолвінг імен помирає, і раптом все на сервері зламано з причин, які не мають жодного стосунку до DNS.
Каскад:
Виділення портів → кожна UDP-асоціація викликає bind() з портом 0, просячи ядро віддати наступний вільний ефемерний порт.
Накопичення портів → порт залишається відкритим, поки TCP не закриється або не спрацює idle timeout. Щедрі таймаути означають, що порти накопичуються швидше, ніж звільняються.
Вичерпання пулу → тисячі асоціацій тримають по порту, весь пул висихає. bind() починає повертати EADDRINUSE для кожного нового сокета.
Системний збій → DNS-запити не проходять (systemd-resolved теж потребує ефемерних портів). WireGuard-хендшейки ламаються. NTP ламається. Syslog-over-UDP мовчки помирає. Потім збій DNS спричиняє вторинний каскад — все, що резолвить hostname, перестає працювати, включно з хелсчеками, підключеннями до БД і моніторинговими агентами. Сервер виглядає «мертвим», хоча CPU, пам'ять і диск — в нормі.
HTTP-хелсчеки проходили — ендпоінт слухав. 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 для коректності, а управління ресурсами проектуйте самі.
Метрики ядра бачать те, що аплікаційні — ні. Хелсчеки, Prometheus-скрейпери, HTTP-пінги — все казало, що сервери здорові. Ядро знало краще. Якщо ваш моніторинг не включає статистику сокетів і лічильники ICMP, у вас є сліпа зона для цілого класу збоїв через вичерпання ресурсів. Ми зіткнулися з аналогічним розривом у спостережуваності при виявленні прихованих збоїв TLS 1.3 на нашому парку Android-пристроїв — інша корінна причина, але урок той самий.
UDP потребує явного управління життєвим циклом. У TCP є вбудований lifecycle — з'єднання відкриваються, передають дані й закриваються визначеним хендшейком. Порти перевикористовуються після TIME_WAIT. У UDP цього немає. Сокет висить відкритим, поки щось явно його не закриє. У relay-архітектурах ви мусите будувати свій lifecycle, інакше — необмежене споживання ресурсів.
Це не тільки проксі-проблема. Будь-яка інфраструктура, що виділяє UDP-сокети за запитами користувачів, може впертися в той самий обрив:
Якщо ви запускаєте щось із цього на масштабі — або керуєте фермою мобільних проксі — перевірте свій sockstat сьогодні. Можливо, ви ближче до обриву, ніж думаєте.
Ми реалізували SOCKS5 UDP ASSOCIATE коректно — відповідно до RFC, протестували, задеплоїли. І він спричинив системний збій, якого наш стандартний моніторинг не побачив.
Висновок, який ми тепер вважаємо жорстким правилом: будь-яка фіча, що виділяє ресурси на рівні ядра — порти, файлові дескриптори, conntrack-записи — потребує явного управління життєвим циклом і бюджетування ресурсів з першого дня. Не як фікс після першого падіння.
Порти — як кисень. Не помічаєш, поки не закінчаться.
Це реальний продакшн-інцидент від iProxy.online. Ми будуємо інфраструктуру мобільних проксі, що перетворює Android-телефони на SOCKS5 та HTTP проксі-сервери, працюючи у 100+ країнах і 600+ операторів. Повна підтримка UDP ASSOCIATE включена — тепер із правильним управлінням ресурсами. Спробувати iProxy →
SOCKS5 UDP ASSOCIATE — одна з трьох команд, визначених у RFC 1928. Вона дозволяє клієнтам ретранслювати UDP-датаграми через SOCKS5 проксі, виділяючи окремий UDP-сокет для кожної асоціації. На відміну від CONNECT (TCP-тунелювання), UDP ASSOCIATE працює з безз'єднувальним трафіком — DNS, VoIP, ігри, QUIC-протоколи.
Linux використовує діапазон 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 relay.
Кожен вихідний 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 проксі. Після інциденту, описаного в цій статті, ми додали per-client ліміти та агресивні idle timeout, щоб запобігти вичерпанню портів, зберігаючи UDP relay повністю функціональним для VoIP, ігор і QUIC-трафіку.
Незалежно від того, чи ви запускаєте TURN-сервери, ігрову інфраструктуру або мережу мобільних проксі — управління ефемерними портами має значення. iProxy.online дає вам продакшн-готовий SOCKS5 з UDP ASSOCIATE, дашборд і API-керування, та налаштування за п'ять хвилин на будь-якому Android-телефоні. Спробувати тріал на 48 годин — протестуйте на реальному навантаженні.