Коли активний мережевий інтерфейс раптово втрачає зв'язок — наприклад, під час закриття кришки ноутбука, перемикання смартфона між базовими станціями або аварії на магістральному оптоволокні — додатки цілком покладаються на таймери транспортного рівня. У розподілених системах, пулах підключень до баз даних і зашифрованих тунелях VPN використання стандартних тайм-аутів операційної системи нерідко призводить до тривалого зависання сокетів на 15–30 хвилин.

Операційна система контролює життєздатність з'єднання за допомогою двох незалежних механізмів: зондувальних пакетів TCP Keepalive під час простою каналу та експоненційного таймера повторного надсилання (Retransmission Timeout — RTO) за наявності непідтверджених даних. Через архітектурні межі початкових специфікацій TCP ці процеси функціонують ізольовано, створюючи критичну сліпу зону. Щоб усунути цей недолік, організація IETF затвердила стандарт RFC 5482, який запровадив опцію користувацького тайм-ауту (TCP_USER_TIMEOUT).

Архітектурне обмеження: чому TCP Keepalive безсилий під час активного трафіку

Поширеною помилкою серед мережевих інженерів є думка, що активація прапорця SO_KEEPALIVE гарантує швидке виявлення обриву зв'язку. Насправді, згідно зі специфікацією RFC 1122, механізм TCP Keepalive має жорстке обмеження: зонди відправляються лише тоді, коли сокет повністю вільний, тобто всі надіслані байти вже успішно підтверджені протилежною стороною (snd_una == snd_nxt).

                ┌─────────────────────────────────┐
                │        Активний сокет TCP       │
                └────────────────┬────────────────┘
                                 │
                   Є непідтверджені байти в польоті?
                                 │
                 ┌───────────────┴───────────────┐
                 │ ТАК                           │ НІ
                 ▼                               ▼
    [ Таймер повторного надсилання RTO ] [ Таймер TCP Keepalive активний ]
    - Ігнорує прапорець SO_KEEPALIVE    - Надсилає порожні зонди ACK
    - Кількість спроб: 15 (tcp_retries2)- Час простою: 7200 секунд
    - Тривалість зависання: 13–30 хв   - Зонди: 9 спроб по 75 секунд
    - Блокує системні потоки додатків   - Загальний час: ~2 год 11 хв

Якщо додаток відправив дані в сокет, а фізична лінія або віртуальний туннель у цю ж секунду обірвалися, пакети залишаються заблокованими у вихідній черзі. Оскільки в буфері присутні байти, що очікують на підтвердження ACK, стек TCP деактивує механізм Keepalive і вмикає таймер повторного надсилання.

За замовчуванням у ядрі Linux (net.ipv4.tcp_retries2 = 15) стек застосовує серію експоненційних затримок, яка розпочинається приблизно з 200 мілісекунд і подвоюється після кожного невдалого кроку:

$$\text{RTO}{i} = \min\left(\text{RTO}{i-1} \times 2, 120\text{ с}\right)$$

За 15 послідовних спроб загальний час очікування накопичується до приблизно 924 секунд — понад 15 хвилин — перш ніж ядро остаточно визнає сокет недоступним і згенерує помилку ETIMEDOUT. Протягом цього інтервалу:

  1. Потоки додатків заморожуються: Синхронні процеси, що очікують на завершення системних викликів читання чи запису, блокуються, вичерпуючи системні ресурси.
  2. Пули з'єднань деградують: Проксі-сервери та клієнти баз даних марнують запити, спрямовуючи їх у мертві сокети-привиди.
  3. Затримується резервне перемикання: У віртуальних мережах клієнтські додатки продовжують спрямовувати трафік у пошкоджений інтерфейс замість миттєвого перезапуску сесії.

Механізм роботи стандарту RFC 5482 та опції TCP_USER_TIMEOUT

Специфікація IETF RFC 5482 ліквідує цю системну прогалину, інтегруючи параметр користувацького тайм-ауту безпосередньо в транспортний шар. Користувацький тайм-аут визначає максимальний проміжок часу, протягом якого передані дані можуть залишатися без підтвердження (або сесія під час простою залишатися без відповіді на зонди), після чого з'єднання примусово розривається.

У ядрі Linux (починаючи з версії 2.6.37) ця можливість реалізована через сокетну опцію TCP_USER_TIMEOUT із заголовкового файлу <netinet/tcp.h>.

Реалізація в ядрі Linux: inet_csk(sk)->icsk_user_timeout

Коли додаток конфігурує TCP_USER_TIMEOUT, ядро фіксує значення ліміту (у мілісекундах) у внутрішній структурі сокета:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int set_tcp_user_timeout(int sockfd, unsigned int timeout_ms)
{
    return setsockopt(sockfd, IPPROTO_TCP, TCP_USER_TIMEOUT, 
                      &timeout_ms, sizeof(timeout_ms));
}

Підсистема таймерів запису ядра (tcp_write_timer()) звіряє цей параметр під час кожного циклу повторної передачі за допомогою функції retransmits_timed_out():

static bool retransmits_timed_out(struct sock *sk, unsigned int boundary, 
                                  unsigned int timeout)
{
    unsigned int linear_backoff_thresh, start_ts;
    
    if (!inet_csk(sk)->icsk_user_timeout)
        return false;
        
    start_ts = tcp_sk(sk)->retrans_stamp;
    if (tcp_time_stamp(tcp_sk(sk)) - start_ts >= inet_csk(sk)->icsk_user_timeout)
        return true; /* Миттєве закриття сокета */
        
    return false;
}

Якщо значення icsk_user_timeout активовано, ядро не очікує вичерпання лічильника спроб. Щойно інтервал від моменту надсилання першого непідтвердженого пакета перевищує встановлений поріг, ядро зупиняє марні повторні передачі, очищає структуру сокета та негайно сповіщає додаток помилкою ETIMEDOUT.

Узгодження TCP_USER_TIMEOUT із зондуванням Keepalive

Хоча TCP_USER_TIMEOUT ефективно запобігає зависанню під час надсилання даних, сам по собі він не формує вихідних пакетів, коли канал повністю мовчить. Для забезпечення захисту від будь-яких мережевих аномалій системні інженери комбінують TCP_USER_TIMEOUT із чутливими параметрами Keepalive.

Разом вони утворюють взаємно узгоджену систему:

  1. Збій під час простою: Модуль Keepalive випускає зонд після короткого періоду тиші (наприклад, 10 секунд). Щойно зонд надходить у фізичну лінію, сокет переходить зі стану спокою в стан очікування підтвердження.
  2. Перехоплення контролю опцією User Timeout: Оскільки зондувальний пакет перебуває в дорозі, контроль бере TCP_USER_TIMEOUT. Якщо відповідь ACK не надходить у межах відведеного вікна (наприклад, 5 секунд), ядро негайно перериває з'єднання.
Параметр За замовчуванням у Linux Оптимізована конфігурація Зв'язка за стандартом RFC 5482
SO_KEEPALIVE Вимкнено (0) Увімкнено (1) Увімкнено (1)
TCP_KEEPIDLE 7200 секунд (2 год) 30 секунд 10 секунд
TCP_KEEPINTVL 75 секунд 5 секунд 3 секунди
TCP_KEEPCNT 9 зондів 3 зонди 3 зонди
TCP_USER_TIMEOUT 0 (Вимкнено) 0 (Вимкнено) 9000 мс (9 с)
Виявлення обриву (під навантаженням) 13–30 хвилин 13–30 хвилин 9,0 секунд
Виявлення обриву (під час простою) 7875 секунд 45 секунд 12,0 секунд

Порівняльна таблиця підтверджує: впровадження TCP_USER_TIMEOUT скорочує виявлення аварії каналу з пів години до 9 секунд, незалежно від того, відбувалося інтенсивне передавання файлів чи з'єднання перебувало в режимі очікування.

Вплив на зашифровані тунелі VPN та віртуальні адаптери TUN

Зависання з'єднань завдає відчутної шкоди у віртуальних приватних мережах VPN. В архітектурах із віртуальними адаптерами (tun0 або wg0) процес інкапсуляції створює дворівневу залежність між внутрішніми прикладними потоками та зовнішнім магістральним тунелем.

[ Сокет додатка HTTP / RPC ] ── (Внутрішній потік TCP)
              │
              ▼
[ Віртуальний адаптер: tun0 ] ─ (Інкапсуляція та шифрування)
              │
              ▼
[ Зовнішній тунель UDP ] ────── (Фізичний інтерфейс Wi-Fi / LTE)

Сценарій виникнення трафікового глухого кута

Коли клієнт втрачає з'єднання під час руху:

  1. Зовнішній магістральний тунель UDP фіксує втрату сигналу або непроходження зондів Dead Peer Detection (DPD).
  2. Проте внутрішні сокети TCP прикладного рівня не мають інформації про падіння фізичного рівня без спеціальних сигналів маршрутизації.
  3. Якщо додаток надсилає байти у віртуальний адаптер tun0, внутрішній стек TCP марно намагатиметься передати їх протягом 15 хвилин.

Реалізація миттєвого перемикання в архітектурі RiceGuard

У сучасній захищеній мережі RiceGuard клієнтські компоненти усувають завислі стани шляхом синхронізації життєвого циклу внутрішніх сокетів із активною сесією шифрування.

Поєднуючи маршрутизацію Core L3 VPN із оптимізованими лімітами сокетів, RiceGuard встановлює чіткі межі User Timeout на всіх шлюзах. У разі обриву фізичної магістралі:

  • Внутрішні сокети TCP коректно закриваються за лічені секунди, повертаючи додаткам зрозумілі коди помилок.
  • Прикладні клієнти та браузери негайно запускають логіку повторного підключення через резервні тунелі без тривалого очікування.
  • Системна пам'ять і файлові дескриптори звільняються миттєво, захищаючи високонавантажені шлюзи від вичерпання ресурсів.

Практичне налаштування та моніторинг у середовищі Linux

Інженери можуть перевіряти та налаштовувати роботу TCP_USER_TIMEOUT за допомогою програмного коду та системних утиліт Linux.

Налаштування тайм-ауту сокетів у коді додатків

У мові Go конфігурація TCP_USER_TIMEOUT задається через функцію Control структури net.Dialer:

package main

import (
    "net"
    "syscall"
    "golang.org/x/sys/unix"
)

func createResilientDialer(timeoutMs int) *net.Dialer {
    return &net.Dialer{
        Control: func(network, address string, c syscall.RawConn) error {
            return c.Control(func(fd uintptr) {
                // Активація TCP_USER_TIMEOUT
                _ = unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, 
                                       unix.TCP_USER_TIMEOUT, timeoutMs)
                // Активація SO_KEEPALIVE
                _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, 
                                       unix.SO_KEEPALIVE, 1)
            })
        },
    }
}

Моніторинг сокетів у реальному часі за допомогою команди ss

Утиліта ss у Linux дає змогу зчитувати поточні параметри таймерів сокетів безпосередньо з ядра:

# Перевірка параметрів таймерів активних сокетів на порту 443
ss -tin '( sport = :443 or dport = :443 )'

Ключові діагностичні поля:

  • timer:(retrans,...): Стан таймера повторного надсилання та час, що минув.
  • timeout:<ms>: Значення TCP_USER_TIMEOUT, встановлене для даного сокета.
  • rto:<ms>: Розрахований поточний тайм-аут повторного надсилання RTO.

Наявність параметра timeout підтверджує, що операційна система розірве з'єднання точно після закінчення вказаного проміжку часу, незважаючи на загальносистемне налаштування tcp_retries2.

Підсумок: Фундамент для побудови відмовостійких мереж

Використання лише механізму TCP Keepalive не здатне захистити інфраструктуру від раптових обривів під час активного передавання даних. Запровадивши жорстке обмеження на очікування підтверджень, стандарт IETF RFC 5482 усунув розрив між зондуванням простою та тривалими циклами невдалих спроб передавання.

Для сучасних зашифрованих тунелів VPN та мобільних пристроїв інтеграція TCP_USER_TIMEOUT перетворює вразливі з'єднання на надійні канали з миттєвим відновленням, забезпечуючи стабільну роботу інфраструктури за будь-яких умов.