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

У транспортному протоколі UDP без встановлення з'єднання зовнішні заголовки IP та UDP містять лише адресу віддаленого вузла та порт відправника. Оскільки клієнтські пристрої регулярно перемикаються між стільниковими вишками, шлюзами NAT та домашніми мережами, мережевий стек ядра не може покладатися на вихідні IP-адреси чи порти для ідентифікації сесії. До того ж перевірка відкритих ключів для кожного вхідного пакета вимагала б ресурсомістких асиметричних обчислень, що перевантажують системну шину пам'яті та кеш процесора.

Для вирішення цього завдання без зниження криптографічної безпеки WireGuard реалізує вбудовану в ядро неблокуючу хеш-таблицю індексів (drivers/net/wireguard/index_hashtable.c). Призначаючи криптографічно непередбачувані випадкові 32-бітні індекси рукостисканням та робочим парам ключів, протокол досягає зіставлення сесій за константний час $O(1)$ без використання блокувань обертання (spinlock) на багатоядерних процесорах.

+-----------------------------------------------------------------------------+
|              КОНВЕЄР ДИСПЕТЧЕРИЗАЦІЇ ІНДЕКСІВ WIREGUARD У ЯДРІ              |
|                                                                             |
| Вхідний інкапсульований фрейм UDP                                           |
| [ Заголовок IP | Заголовок UDP | Тип (4B) | Індекс отримувача (4B) | Шифр ] |
|                                            |                                |
|                                            v                                |
|                              Вхід у rcu_read_lock()                         |
|                                            |                                |
|                                            v                                |
|                       +-----------------------------------+                 |
|                       |   Хеш-функція index_hashtable.c   |                 |
|                       |   h = siphash_1u32(receiver_index,|                 |
|                       |                    secret_key)    |                 |
|                       +-----------------------------------+                 |
|                                            |                                |
|                                            v                                |
|                       +-----------------------------------+                 |
|                       |    Пошук у кошику та розіменування|                 |
|                       |    Ітерація за списком hlist_head |                 |
|                       +-----------------------------------+                 |
|                                            |                                |
|                         +------------------+------------------+             |
|                         |                                     |             |
|                         v                                     v             |
|                 [ Пару ключів знайдено ]             [ Індекс не знайдено ] |
|                         |                                     |             |
|                         v                                     v             |
|              Дешифрування ChaCha20-Poly1305          Безшумне скидання      |
|              Перевірка монотонного лічильника        (goto drop)            |
|              Маршрутизація Cryptokey Routing                  |             |
|                         |                                     v             |
|                         +----------------------------> rcu_read_unlock()   |
+-----------------------------------------------------------------------------+

Проблеми ідентифікації сесій у традиційних архітектурах VPN

Класичні тунельні протоколи розподіляють вхідні пакети за допомогою складних скінченних автоматів або високонавантажених запитів до баз даних. В архітектурі IPsec (RFC 4301) пошук асоціацій безпеки (SA) спирається на 32-бітний індекс параметрів безпеки (SPI). Проте стандартний IPsec вимагає перевірки трафіку одразу через дві бази: базу асоціацій безпеки (SAD) та базу політик безпеки (SPD). Зі зростанням кількості тунелів послідовна оцінка складних класифікаторів створює значну затримку передачі даних.

Історичні реалізації OpenVPN мультиплексують клієнтські з'єднання через єдиний сокет UDP у просторі користувача. Коли надходить зашифрований пакет, однопроцесний демон зчитує буфер через системний виклик POSIX і виконує пошук у словнику сесій у пам'яті користувача. У багатоядерних конфігураціях робочі потоки змагаються за блокування взаємного виключення (mutex), які захищають таблицю з'єднань. Ця конкуренція марнує процесорні цикли на перемикання контексту, суттєво обмежуючи швидкість передачі задовго до вичерпання пропускної здатності фізичного каналу.

Прості структури пошуку також вразливі до атак на алгоритмічну складність, відомих як Hash-DoS. Якщо мережевий тунель використовує неключову функцію хешування (наприклад, Jenkins hash або CRC32), зловмисник може заздалегідь згенерувати тисячі фальшивих ідентифікаторів, які спричиняють колізію в один і той самий кошик хеш-таблиці. Коли тисячі записів потрапляють в один зв'язний список, час пошуку деградує з $O(1)$ до $O(N)$. Під час масованого потоку пакетів ядро вимушене витрачати всі ресурси на послідовне сканування довгих ланцюжків списку, викликаючи вичерпання процесорного часу та скидання пакетів для легітимних клієнтів.

Архітектура index_hashtable.c: синхронізація RCU та захист SipHash

WireGuard усуває як вузькі місця блокувань, так і загрозу атак Hash-DoS завдяки ізоляції зберігання сесій у спеціалізованій ядерній хеш-таблиці. У файлі drivers/net/wireguard/index_hashtable.c структура даних визначена з чіткими гарантіями ізоляції:

struct index_hashtable {
    struct hlist_head *handshake_hash;
    struct hlist_head *keypair_hash;
    u32 mask;
    siphash_key_t key;
    spinlock_t lock;
};

Таблиця чітко розмежовує облік фази рукостискань та робочу передачу даних, запобігаючи впливу процесів узгодження нових ключів на дешифрування поточного трафіку.

1. Криптографічна генерація випадкових індексів

Під час ініціалізації рукостискання відправник генерує псевдовипадкове 32-бітне ціле число sender_index. Відповідач генерує власний випадковий 32-бітний receiver_index. Під час формування пакетів даних кожна сторона вставляє наданий партнером індекс безпосередньо в 4-байтний заголовок транспортного повідомлення зі зміщенням у 4 байти:

+---------------+---------------------+------------------------+
|  Тип (0x04)   |  Індекс отримувача  | Лічильник (8 байтів)   |
|    1 октет    |       4 октети      | Строго монотонний      |
+---------------+---------------------+------------------------+
|             Зашифровані корисні дані та тег Poly1305         |
+---------------------------------------------------------------+

Оскільки індекси вибираються рівномірно з простору адрес $2^{32}$, підбір активних сесій методом повного перебору математично неможливий. Зовнішній спостерігач не здатен вгадати чужий індекс для ін'єкції підробленого пакета.

2. Стійкий до колізій розподіл за кошиками SipHash

Для захисту від атак Hash-DoS WireGuard обчислює позицію кошика за допомогою криптографічної псевдовипадкової функції SipHash (siphash_1u32). Під час ініціалізації віртуального інтерфейсу драйвер генерує секретний 128-бітний ключ (key) з пулу ентропії ядра:

static inline struct hlist_head *
keypair_bucket(struct index_hashtable *table, u32 index)
{
    u32 hash = siphash_1u32(index, &table->key);
    return &table->keypair_hash[hash & table->mask];
}

Оскільки ключ SipHash ізольований у захищеній пам'яті ядра та є унікальним для кожного віртуального інтерфейсу, сторонній зловмисник не може вирахувати індекси, що спричинять колізію в одному кошику. Розподіл записів відповідає розподілу Пуассона, що гарантує час вибірки $O(1)$ незалежно від інтенсивності вхідного трафіку.

3. Неблокуючий шлях читання за допомогою RCU

Головним досягненням WireGuard у продуктивності є використання парадигми Read-Copy Update (RCU). Маршрутизація кожного вхідного пакета здійснюється виключно в межах критичної секції читання RCU:

struct noise_keypair *
wg_index_hashtable_lookup_keypair(struct index_hashtable *table,
                                  enum noise_keypair_side side, u32 index)
{
    struct noise_keypair *keypair;
    rcu_read_lock();
    hlist_for_each_entry_rcu(keypair, keypair_bucket(table, index), entry) {
        if (keypair->entry[side].index == index) {
            if (unlikely(!kref_get_unless_zero(&keypair->refcount)))
                keypair = NULL;
            rcu_read_unlock();
            return keypair;
        }
    }
    rcu_read_unlock();
    return NULL;
}

На шляху читання виклик rcu_read_lock() майже не споживає циклів процесора на сучасних архітектурах (він лише тимчасово вимикає витіснення потоків на локальному ядрі). Жодних глобальних блокувань чи світлофорів не задіюється. Десятки ядер процесора можуть паралельно опитувати таблицю індексів без вибивання кеш-ліній та падіння швидкості системної шини.

Лише операції модифікації — такі як додавання нової пари ключів або видалення застарілої сесії — використовують внутрішнє блокування table->lock. Оскільки оновлення ключів відбувається лише раз на кілька хвилин, операції запису становлять менше ніж 0,01% від загального обсягу мережевих транзакцій.

Життєвий цикл сесій та безпечне звільнення пам'яті

Мережеві тунелі вимагають безпечної заміни сесійних ключів (rekeying) без втрати пакетів і пошкодження пам'яті. У WireGuard кожен вузол підтримує до трьох пар ключів одночасно під час перехідних інтервалів: попередню (previous), поточну (current) та наступну (next).

Параметр стану Таблиця індексів рукостискання Таблиця індексів транспортних ключів
Пов'язана структура C struct noise_handshake struct noise_keypair
Генерація ідентифікатора Криптографічно випадковий 32-бітний токен Криптографічно випадковий 32-бітний токен
Функція запиту кошика handshake_bucket(table, index) keypair_bucket(table, index)
Синхронізація читання rcu_read_lock() / Без блокувань rcu_read_lock() / Без блокувань
Захист від колізій SipHash із секретним ключем інтерфейсу SipHash із секретним ключем інтерфейсу
Механізм звільнення пам'яті Негайне вилучення після завершення Відкладене очищення через kfree_rcu

Коли створюється нова пара ключів, ядро реєструє її receiver_index у keypair_hash. Пакети, зашифровані попереднім ключем, продовжують успішно дешифруватися за старим індексом. Після завершення перехідного періоду стара пара ключів від'єднується від хеш-таблиці за допомогою hlist_del_rcu().

Пам'ять не можна звільняти негайно, оскільки робочі потоки на сусідніх ядрах процесора можуть у цей момент завершувати розшифрування пакетів, прив'язаних до цієї сесії. WireGuard делегує це завдання механізму kfree_rcu(). Ядро Linux чекає, поки всі ядра процесора пройдуть стан спокою (quiescent state), перш ніж остаточно очистити структуру пам'яті, що повністю усуває вразливості типу Use-After-Free.

Практичний моніторинг та налаштування буферів ядра

Системні інженери та адміністратори Linux можуть оцінювати продуктивність мережевого стека WireGuard за допомогою стандартних інструментів ss та perf.

Перевірка стану буферів прийому сокета UDP та відсутності втрат пакетів:

# Моніторинг черг прийому на порту WireGuard
ss -u -a -m -p | grep 51820

Спостереження за розподілом обробки мережевих переривань на ядрах процесора:

# Аналіз навантаження на ядра процесора при обробці softirq
perf top -s comm,symbol -C 0-3

Оптимізація параметрів ядра для обробки високошвидкісних потоків даних:

# Збільшення розмірів буферів пам'яті мережевих сокетів
sysctl -w net.core.rmem_max=67108864
sysctl -w net.core.wmem_max=67108864
sysctl -w net.ipv4.udp_rmem_min=16384

Масштабованість в архітектурі мережі VPNRice

Забезпечення стабільної конфіденційності у глобальній інфраструктурі, яка підтримує сотні тисяч одночасних з'єднань, вимагає надійної роботи компонентів із мінімальними затримками. Архітектура VPNRice впроваджує неблокуючу маршрутизацію на всіх граничних вузлах:

  1. Неблокуюча комутація сесій RiceGuard: Завдяки використанню ядерних таблиць RCU вузли VPNRice обробляють мільйони зашифрованих пакетів на секунду без затримок синхронізації між процесорами, досягаючи апаратної швидкості фізичних каналів.

  2. Захист від атак на алгоритмічну складність: Кожен серверний вузол VPNRice генерує унікальні ключі підсолювання SipHash для кожного мережевого простору імен, що запобігає деградації швидкості пошуку під час спроб штучного створення колізій.

  3. Безперервна ротація ключів без втрати даних: Подвійний життєвий цикл індексів гарантує безшовне дешифрування пакетів під час ротації ключів прямої секретності (PFS) без затримок та сплесків джитера.

Поєднання 32-бітних випадкових індексів, алгоритму SipHash та неблокуючої моделі ядра RCU дає змогу сучасним мережевим шлюзам підтримувати детерміновану продуктивність під час найвищих корпоративних навантажень.