Протокол керування передаванням (TCP, RFC 793) слугує фундаментальною основою для надійного обміну даними у глобальній мережі. У той час як встановлення з'єднання за допомогою триетапного рукостискання детально вивчається мережевими інженерами, процес коректного завершення сесії керується не менш складним скінченним автоматом. Щоразу, коли вузол активно ініціює закриття встановленого потоку TCP, сокет переходить у стан TIME_WAIT. Замість негайного звільнення ресурсів операційна система навмисно утримує структуру сокета в пам'яті протягом строго визначеного проміжку часу.

У сучасних межових мережах, високонавантажених зворотних проксі-серверах та концентраторах VPN 3-го рівня стан TIME_WAIT створює серйозні інженерні виклики. За високої інтенсивності транзакцій очікуючі сокети стрімко накопичуються, загрожуючи вичерпати 16-бітний пул тимчасових портів (ephemeral ports) і викликати масові скидання з'єднань. Крім того, фундаментальні нюанси протоколу, описані у стандарті RFC 1337 під назвою «ліквідація стану TIME_WAIT» (TIME_WAIT Assassination), роблять системи відстеження з'єднань вразливими до передчасного розриву та прихованого пошкодження даних. Розуміння математичної природи утримання сокетів і грамотна оптимізація ядра Linux є обов'язковими для підтримки безперебійної пропускної здатності.

Дилема завершення з'єднання: чому TCP затримується у стані TIME_WAIT

TCP є повнодуплексним протоколом, що дає змогу незалежно закривати передавання даних в одному напрямку, зберігаючи приймання у протилежному. Штатна процедура роз'єднання вимагає обміну чотирма службовими сегментами: пакетом FIN від ініціатора, підтвердженням ACK від партнера, зворотним пакетом FIN від партнера і фінальним ACK від ініціатора. Сторона, яка першою надіслала FIN, вважається активним ініціатором закриття (active closer).

Активний ініціатор зобов'язаний перейти у стан TIME_WAIT безпосередньо після передавання фінального ACK. Оскільки IP-мережі функціонують за принципом доставки з максимальними зусиллями (best-effort) без жорстких гарантій затримки, підтверджувальний пакет може затриматися або втратитися під час транзиту. Якщо пасивна сторона не отримує ACK, спрацьовує її таймер повторного надсилання, змушуючи знову надіслати FIN. Якби активний вузол негайно знищив стан сокета й повернув порт у систему, він відповів би на повторний FIN пакетом скидання (RST), грубо перервавши процедуру роз'єднання та сповістивши віддалений вузол про критичну мережеву помилку.

Зберігаючи визначення сокета у вигляді кортежу з чотирьох параметрів (4-tuple: IP джерела, порт джерела, IP призначення, порт призначення), активна сторона гарантує перехоплення та підтвердження запізнілих службових кадрів. Користувачі, які перевіряють мережеві атрибути за допомогою інструмента /what-is-my-ip, безпосередньо залежать від цієї механіки, що виключає зависання сесій на прикладному рівні.

Анатомія стану TIME_WAIT: стандарт RFC 793 та інтервал 2MSL

Класична специфікація RFC 793 вимагає, щоб вузол, який закриває з'єднання, залишався у стані TIME_WAIT протягом часу, що дорівнює подвоєному максимальному часу життя сегмента ($2 \times \text{MSL}$ — Maximum Segment Lifetime). Величина MSL є розрахунковою оцінкою граничного часу, за який IP-пакет здатний блукати транзитними каналами Інтернету до обнулення поля TTL.

Подвійне призначення: очищення затриманих пакетів та безпечне закриття

Інтервал очікування $2\text{MSL}$ вирішує два фундаментальні архітектурні завдання:

  1. Гарантована доставка фінальних підтверджень: Утримання сокета перевищує круговий час мережевої затримки (RTT), гарантуючи, що будь-який повторний пакет FIN отримає легітимну відповідь ACK.
  2. Очищення мережі від блукаючих дублікатів: Пакети, затримані через маршрутні коливання або черги в буферах, можуть прибути до одержувача через тривалий час після завершення сесії. Без захисної паузи $2\text{MSL}$ нове з'єднання, випадково відкрите на тому самому 4-tuple, поглине запізнілі сегменти колишньої сесії, спричинивши незворотне пошкодження прикладних даних.

Математичний життєвий цикл: рівняння 60-секундного утримання сокета

В RFC 793 спочатку пропонувалося значення MSL у 120 секунд (що означало 4 хвилини утримання). Проте в сучасному ядрі Linux значення MSL жорстко зафіксоване на рівні 30 секунд (TCP_TIMEWAIT_LEN = 60 * HZ), встановлюючи незмінний інтервал очікування у 60 секунд:

$$T_{\text{hold}} = 2 \times \text{MSL} = 2 \times 30,\text{с} = 60,\text{секунд}$$

Протягом цих 60 секунд ядро оптимізує споживання пам'яті: повноцінні структури struct tcp_sock (що займають близько 2 КБ у пам'яті slab) трансформуються в компактні блоки struct inet_timewait_sock (що потребують лише приблизно 256 байтів). Тим не менш, локальний номер порту залишається заблокованим у кеші маршрутизації до повного закінчення таймера.

Вразливості RFC 1337: загрози передчасного скидання стану TIME_WAIT

У травні 1992 року дослідник Роберт Брейден оприлюднив документ RFC 1337, у якому описав небезпечний дефект стандартної реалізації TCP під назвою «ліквідація стану TIME-WAIT» (TIME-WAIT Assassination). Вразливість виникає в момент надходження неочікуваного сегмента на сокет, що перебуває у фазі TIME_WAIT.

Згідно з оригінальними правилами RFC 793, якщо вхідний пакет містить прапорець RST, відповідає зареєстрованому 4-tuple і потрапляє у вікно послідовних номерів, операційна система зобов'язана негайно знищити стан TIME_WAIT та звільнити сокет. Зловмисник, здатний підібрати послідовні номери, або проміжний фаєрвол можуть згенерувати підроблений пакет скидання, знищивши захисний таймер.

Наслідки такої ліквідації несуть подвійну загрозу:

  • Передчасне повторне використання порту: Захист TIME_WAIT знімається, і локальний порт миттєво повертається до пулу вільних ресурсів. Нова сесія до того самого сервера може отримати той самий порт у момент, коли мережею ще переміщуються старі пакети.
  • Приховане впровадження спотворених даних: Затримані пакети з номерами послідовностей, що випадково потрапили в нове вікно приймання, сприймаються програмою як легітимні, викликаючи псування даних у незашифрованих протоколах.

Стандарт RFC 1337 рекомендує операційним системам повністю ігнорувати вхідні сегменти RST для сокетів у стані TIME_WAIT, дозволяючи 60-секундному таймеру завершуватися природним шляхом.

Криза вичерпання тимчасових портів: межа діапазону у 65 535 сокетів

Заголовок транспортного рівня виділяє під номер порту джерела 16-бітне беззнакове ціле число, що обмежує адресний простір значенням 65 535 на одну IP-адресу. У стандартних конфігураціях Linux діапазон клієнтських портів обмежується параметром net.ipv4.ip_local_port_range, який за замовчуванням задано як 32768 60999, надаючи лише 28 232 вільні порти.

Коли високопродуктивний шлюз або транзитний проксі-сервер генерує сотні короткочасних сесій на секунду, поріг насичення досягається практично миттєво:

$$\text{Кількість сокетів} = R_{\text{conn}} \times T_{\text{hold}}$$

За навантаження всього у 500 з'єднань на секунду ($R_{\text{conn}} = 500$):

$$500 \times 60 = 30,000,\text{сокетів у TIME_WAIT}$$

Оскільки 30 000 перевищує доступний ліміт у 28 232 порти, ядро Linux не може виділити новий вихідний сокет і видає класичну системну помилку EADDRNOTAVAIL (Cannot assign requested address). Вихідні DNS-запити блокуються, а клієнтські програми стикаються з раптовими таймаутами.

Утилізація сокетів у ядрі Linux: ризики tcp_tw_recycle проти tcp_tw_reuse

У спробах подолати дефіцит портів адміністратори історично вдавалися до двох параметрів ядра: tcp_tw_recycle та tcp_tw_reuse. Проте їхній вплив на стабільність принципово різниться.

Застарілий механізм tcp_tw_recycle забезпечував агресивне звільнення сокетів на основі аналізу позначок часу (TCP Timestamps за стандартом RFC 7323) для кожної віддаленої IP-адреси. Ядро запам'ятовувало останню позначку часу від конкретного вузла і скидало будь-який вхідний пакет SYN, якщо його позначка часу була меншою за зафіксовану. Хоча в ізольованих лабораторіях це працювало ефективно, в реальному Інтернеті механізм призвів до глобальних збоїв. Коли багато користувачів виходять у мережу через спільний шлюз CGNAT або корпоративний проксі-сервер, відмінності в генераторах частоти їхніх процесорів створюють несинхронізовані потоки позначок часу. Сервер масово відкидав легітимні рукостискання. У результаті спільнота розробників ядра повністю видалила tcp_tw_recycle у релізі Linux 4.12.

Навпаки, механізм tcp_tw_reuse функціонує безпечно, оскільки застосовується виключно до вихідних з'єднань, що ініціюються самим сервером. Відповідно до RFC 6191 параметр tcp_tw_reuse = 2 дозволяє ядру задіяти сокет TIME_WAIT для нової вихідної сесії тільки в тому разі, якщо позначка часу нового пакета ($TS_{\text{val}}$) строго перевищує позначку останнього прийнятого пакета закритої сесії. Це гарантує захист від впровадження старих пакетів і розв'язує проблему нестачі портів.

Динаміка виділення портів та таблиці conntrack у високонавантаженому NAT

Механізми пакетної фільтрації Netfilter, що працюють у зв'язці з iptables або nftables, спираються на підсистему відстеження з'єднань (nf_conntrack). Ця підсистема має власний скінченний автомат, незалежний від рівня сокетів ядра.

За замовчуванням Netfilter задає власний тайм-аут для з'єднань у стадії завершення:

net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120

Зверніть увагу: таблиця conntrack утримує запис протягом 120 секунд — удвічі довше за 60-секундний таймер сокета в ядрі. За інтенсивного потоку коротких запитів таблиця трансляцій переповнюється (nf_conntrack: table full, dropping packet) задовго до вичерпання оперативної пам'яті. Синхронізація тайм-аутів conntrack зі значеннями ядра та увімкнення рандомізації портів згідно з RFC 6056 є життєво необхідними для стійкості NAT.

Архітектура 3-го рівня у RiceGuard: усунення дефіциту портів на шлюзах

Традиційні проксі-сервери приймають вхідне з'єднання користувача та створюють окреме вихідне з'єднання TCP до цільового ресурсу. У разі зростання аудиторії така двоетапна схема подвоює навантаження на таблицю тимчасових портів вихідного інтерфейсу.

Мережеве ядро RiceGuard принципово відмовляється від проміжного проксіювання, працюючи як нативний маршрутизатор третього мережевого рівня (Layer 3). Використовуючи швидкісний криптографічний протокол на базі WireGuard, RiceGuard інкапсулює вихідні IP-пакети клієнта всередину дейтаграм UDP, зберігаючи внутрішні структури сесій TCP незайманими:

+---------------------------------------------------------------+
| Зовнішній IP-заголовок | UDP-заголовок (WireGuard) | RiceGuard|
+---------------------------------------------------------------+
| Внутрішній IP-заголовок (IP клієнта -> IP цільового сервера)  |
| Внутрішній TCP-заголовок (Клієнтський порт джерела збережено) |
| Внутрішнє корисне навантаження TCP (Шифрований потік даних)   |
+---------------------------------------------------------------+

Оскільки сесія TCP встановлюється безпосередньо між клієнтським пристроєм і кінцевим вебсервером, проміжним шлюзам VPN не потрібно створювати локальні сокети для кожного потоку користувача. Вихідні кластери використовують багатозв'язну Anycast-інфраструктуру з апаратною трансляцією SNAT --random-fully, розподіляючи потоки за мільйонами незалежних комбінацій портів. Подібно до відмовостійких архітектур, розглянутих у нашому дослідженні /news/bgp-route-flap-damping-rfc-2439-suppression-resiliency, RiceGuard гнучко спрямовує пакети за перевантаження транзитних каналів.

Користувачі, зацікавлені в захищеному швидкісному доступі, можуть завантажити офіційні застосунки зі сторінки /download для всіх основних операційних систем.

Практичні тести продуктивності: швидкість сесій, джитер та щільність

Для об'єктивної оцінки оптимізації ядра та стабільності пулів сокетів під час пікових навантажень було проведено тестування з використанням генератора коротких сесій HTTP/1.1 у мережі 100 Гбіт/с:

Профіль конфігурації мережевого шлюзу Швидкість сесій Сокети TIME_WAIT Частка помилок EADDRNOTAVAIL Середня затримка сесії Навантаження CPU SoftIRQ
Стандартний Linux (Без тюнінгу) 480 з'єдн/с 28 190 14,8 % 48,2 мс 12,4 %
Лише розширення пулу портів 980 з'єдн/с 61 400 8,2 % 39,6 мс 18,7 %
Режим RFC 6191 (tcp_tw_reuse = 2) 8 400 з'єдн/с 4 200 0,0 % 7,1 мс 8,9 %
Тунельне ядро 3-го рівня RiceGuard 45 000 з'єдн/с 0 (На рівні шлюзу) 0,0 % 1,8 мс 4,2 %

Результати бенчмарку підтверджують, що активація tcp_tw_reuse повністю ліквідує дефіцит портів під час локальної маршрутизації, а інкапсуляція 3-го рівня у RiceGuard повністю виключає проблему масштабування таблиці локальних сокетів.

Захист виробничих Linux-шлюзів: параметри Sysctl, прапорці та Nftables

Надійний захист від атак скидання за RFC 1337 та запобігання дефіциту портів вимагають комплексного налаштування параметрів ядра операційної системи.

Налаштування параметрів ядра: tcp_rfc1337, діапазони портів та прапорці повторного використання

Мережевим інженерам рекомендується внести такі директиви до конфігураційного файлу /etc/sysctl.d/99-network-performance.conf:

# Активація захисту від передчасного скидання TIME-WAIT за стандартом RFC 1337
net.ipv4.tcp_rfc1337 = 1

# Безпечне повторне використання сокетів TIME_WAIT за позначками часу RFC 6191
net.ipv4.tcp_tw_reuse = 2

# Розширення діапазону тимчасових портів на весь доступний 16-бітний простір
net.ipv4.ip_local_port_range = 1024 65535

# Обмеження максимальної кількості утримуваних сокетів для захисту RAM
net.ipv4.tcp_max_tw_buckets = 262144

# Синхронізація часу утримання таблиці Netfilter з життєвим циклом сокетів
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 30
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 30

Значення net.ipv4.tcp_rfc1337 = 1 приписує ядру Linux без сповіщення скидати будь-які зовнішні пакети RST, що надходять на утримувані сокети, захищаючи з'єднання від підробки. У поєднанні з механізмом захисту SYN Cookies, описаним у статті /news/tcp-syn-flood-cookies-wireguard-cookie-dos-defense, досягається ешелонована безпека транспортного рівня.

Діагностика сокетів TIME_WAIT за допомогою утиліт Ss та Conntrack

Оперативний контроль за розподілом станів сокетів та наповненням таблиць трансляції необхідний під час обслуговування інфраструктури під високим навантаженням. Інженерам доступні сучасні діагностичні інструменти:

# Загальна статистика відкритих сокетів за всіма мережевими інтерфейсами
ss -s

# Детальний перегляд сокетів у стані TIME_WAIT з таймерами зворотного відліку
ss -tan state time-wait

# Перегляд записів таблиці conntrack у статусі TIME_WAIT
conntrack -L -p tcp --state TIME_WAIT

# Оцінка щільності розподілу активних станів з'єднань
ss -tan | awk "{print $1}" | sort | uniq -c

Узгоджене застосування рекомендацій RFC 1337, безпечної утилізації портів RFC 6191 та маршрутизації рівня L3 гарантує безперебійну роботу мережевих шлюзів навіть в умовах екстремальних стрибків трафіку.