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

На сучасних серверних процесорах обчислення множення на Curve25519 триває частки мілісекунди, тоді як відправлення сфальсифікованого UDP-пакета розміром 148 байтів через мережевий порт 10 Гбіт/с потребує лише кількох наносекунд. Зловмисник, який генерує лавину підроблених пакетів ініціалізації з фальшивими IP-адресами відправника, здатен миттєво перевантажити ядра процесора, викликаючи вичерпання ресурсів і позбавляючи легітимних клієнтів можливості завершити рукостискання.

Щоб запобігти загрозам криптографічної відмови в обслуговуванні (DoS) та вичерпання пам'яті, розробники WireGuard впровадили спеціалізований алгоритм token bucket («відро з жетонами») безпосередньо в мережевий тракт ядра Linux (drivers/net/wireguard/ratelimiter.c). Обмежуючи частоту обробки неавтентифікованих рукостискань для кожної вхідної IP-адреси до запуску важких криптографічних функцій, WireGuard підтримує стабільне оброблення трафіку на апаратній швидкості портів навіть під час потужних об'ємних атак.

+-----------------------------------------------------------------------------+
|             КОНВЕЄР ОБМЕЖЕННЯ РУКОСТИСКАНЬ У ЯДРІ WIREGUARD                 |
|                                                                             |
| Вхідний UDP-пакет (Тип 1: Ініціалізація рукостискання)                      |
| [ Пройдено перевірку MAC1 через BLAKE2s ]                                   |
|                     |                                                       |
|                     v                                                       |
|        wg_ratelimiter_allow_ip(net, ip_addr)                                |
|                     |                                                       |
|                     v                                                       |
|        Вхід у критичну секцію rcu_read_lock()                               |
|        Обчислення кошика через SipHash: siphash(&ip, secret_key)            |
|                     |                                                       |
|                     v                                                       |
|        Пошук у кошику hlist таблиці table_v4 або table_v6                   |
|                     |                                                       |
|         +-----------+-----------+                                           |
|         |                       |                                           |
|         v                       v                                           |
|    [ Запис знайдено ]      [ Запис відсутній ]                              |
|         |                       |                                           |
|         |                       v                                           |
|         |                Виділення структури ratelimiter_entry              |
|         |                Вставка в кошик (атомарний спінлок)                |
|         |                       |                                           |
|         +----------->+<---------+                                           |
|                      |                                                      |
|                      v                                                      |
|         Відновлення жетонів на основі витраченого часу                      |
|         (Місткість: 20 жетонів, Поповнення: 1 жетон кожні 100 мс)            |
|                      |                                                      |
|         +------------+------------+                                         |
|         |                         |                                         |
|         v                         v                                         |
|   [ Жетонів >= 1 ]          [ Жетони вичерпано (< 1) ]                      |
|         |                         |                                         |
|         v                         v                                         |
|   Списання 1 жетона         Відмова в прямому обчисленні криптографії       |
|   rcu_read_unlock()         Вимога Cookie-челенджу MAC2 (cookie.c)          |
|   Виконання скалярного      Скидання пакета за відсутності валідної Cookie  |
|   множення Curve25519                     |                                 |
|                                           v                                 |
|                                     rcu_read_unlock()                       |
+-----------------------------------------------------------------------------+

Асиметрія рукостискань у класичних архітектурах VPN

Традиційні протоколи тунелювання організують встановлення сесій через багатокрокові узгодження, які перевантажують сервер фіксацією проміжних станів. У стандартному протоколі IPsec IKEv2 (RFC 7296) сервер виділяє структуру стану відразу після отримання запиту IKE_SA_INIT, утримуючи випадкові числа та криптографічні пропозиції в пам'яті до завершення автентифікації. Потужний потік запитів швидко вичерпує пам'ять slab ядра, провокуючи аварійне завершення роботи системи через брак пам'яті.

Реалізації OpenVPN спираються на автомати станів OpenSSL або mbedTLS у просторі користувача. Кожен вхідний пакет TLS Client Hello тягне за собою розбір структур ASN.1, перевірку сертифікатів та валідацію відкритих ключів RSA або ECDSA. Оскільки ці дії відбуваються після багаторазових перемикань контексту між ядром та користувацьким процесом, потік у кілька тисяч пакетів на секунду повністю завантажує процесор, спричиняючи втрату зв'язку для всіх активних сесій.

Крім того, типові обмеження трафіка на базі класичних правил пакетних фільтрів створюють власні вузькі місця:

  1. Розростання таблиць з'єднань: Відстеження сотень тисяч підроблених адрес IPv4 та IPv6 переповнює пам'ять міжмережевих екранів.
  2. Конкуренція за блокування: Багатопотокові обмежувачі з централізованими м'ютексами втрачають продуктивність через постійну синхронізацію кеш-ліній між ядрами CPU.
  3. Деградація хешування (Hash-DoS): Застосування функцій хешування без секретного ключа дозволяє зловмиснику згенерувати набір IP-адрес, що потрапляють в один ланцюжок колізій, знижуючи швидкість пошуку з $O(1)$ до $O(N)$.

Архітектура ratelimiter.c: алгоритм Token Bucket у ядрі

WireGuard долає ці обмеження завдяки окремому компоненту в drivers/net/wireguard/ratelimiter.c. Під час ініціалізації драйвер створює дві ізольовані хеш-таблиці: table_v4 для протоколу IPv4 та table_v6 для IPv6.

struct ratelimiter_entry {
    u64 last_time_ns;
    u32 tokens;
    spinlock_t lock;
    struct hlist_node hash;
    struct rcu_head rcu;
    union {
        __be32 ip;
        struct in6_addr ip6;
    };
};

Кожен запис у таблиці зберігає параметри персонального відра з жетонами для певної віддаленої IP-адреси:

  • Гранична місткість (PACKETS_BURST): Встановлена на рівні 20 жетонів, що забезпечує легітимним клієнтам можливість короткочасного сплеску при повторних спробах або зміні стільникових мереж.
  • Швидкість відновлення (PACKETS_PER_SECOND): Додає 10 жетонів на секунду (1 жетон кожні 100 мілісекунд), що обчислюється динамічно за системним наносекундним таймером ядра (ktime_get_coarse_boottime_ns()).
  • Мінімальний обсяг пам'яті: Компактна структура дозволяє підтримувати десятки тисяч вузлів із мінімальними витратами оперативної пам'яті.

1. Захист від колізій за допомогою SipHash

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

static inline u32 entry_hash(const struct ratelimiter_entry *entry)
{
    return siphash_1u32((__force u32)entry->ip, &table_key);
}

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

2. Неблокуючий пошук RCU та локальні спінлоки

Для підтримання високої пропускної здатності пошук у хеш-таблицях виконується всередині захищених критичних секцій Read-Copy Update (RCU):

bool wg_ratelimiter_allow_ip(struct ratelimiter *limiter, struct sk_buff *skb)
{
    struct ratelimiter_entry *entry;
    u32 hash;

    rcu_read_lock();
    hash = entry_hash_from_skb(skb);
    hlist_for_each_entry_rcu(entry, &limiter->table_v4[hash & limiter->mask], hash) {
        if (entry->ip == ip) {
            bool allowed = deduct_token(entry);
            rcu_read_unlock();
            return allowed;
        }
    }
    rcu_read_unlock();
    return allocate_and_insert_entry(limiter, ip);
}

У звичайному режимі ядра процесора знаходять потрібну IP-адресу без захоплення загальносистемних блокувань. Після отримання вказівника на структуру ratelimiter_entry потік захоплює виключно локальний спінлок цього окремого запису (entry->lock) для списання жетона. Потоки, які обробляють пакети з інших адрес, працюють абсолютно паралельно.

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

Звичайний стан (жетони є в наявності):
Клієнтський пакет [ Тип 1 | Без автентифікації | MAC1 ]
    --> Обмежувач дає дозвіл
    --> Скалярне множення Curve25519
    --> Відправлення відповіді Тип 2

Стан перевантаження (жетони вичерпано):
Клієнтський пакет [ Тип 1 | Перевищення ліміту частоти | MAC1 ]
    --> Заборона прямих криптографічних обчислень
    --> Якщо поле MAC2 порожнє: Відправлення Cookie Тип 3
    --> Клієнт обчислює MAC2 і повторно надсилає Тип 1 [ MAC1 + MAC2 ]
    --> Сервер без збереження стану перевіряє Cookie та завершує сесію

Під час сильного флуду шлюз відмовляється від важких обчислень Curve25519 для неперевірених відправників. Сервер вимагає надати криптографічну Cookie (яка формується з IP-адреси, порту та періодично оновлюваного секретного ключа). Оскільки створення та перевірка Cookie вимагає лише симетричного хешування BLAKE2s, сервер витримує мільйони запитів на апаратній швидкості. Легітимні клієнти розв'язують челендж без розриву з'єднання, тоді як паразитичний трафік миттєво відсікається.

Фонове очищення неактивних записів пам'яті

Під час розподілених DoS-атак за участю мільйонів підроблених адрес таблиця відстеження могла б безконтрольно розростатися.

Щоб підтримувати стабільність системної пам'яті, WireGuard використовує відкладені черги завдань Linux (schedule_delayed_work) для періодичного очищення застарілих елементів:

static void cleanup_entries(struct work_struct *work)
{
    struct ratelimiter_entry *entry;
    struct hlist_node *n;
    u64 now = ktime_get_coarse_boottime_ns();

    spin_lock_bh(&table_lock);
    for (int i = 0; i < HASH_SIZE; ++i) {
        hlist_for_each_entry_safe(entry, n, &table[i], hash) {
            if (now - entry->last_time_ns > EXPIRATION_INTERVAL_NS) {
                hlist_del_rcu(&entry->hash);
                kfree_rcu(entry, rcu);
            }
        }
    }
    spin_unlock_bh(&table_lock);
}

Фонова служба регулярно виконує такі операції:

  • Видалення за таймаутом: Будь-який запис, що не отримував нових пакетів протягом вікна застарівання (кілька секунд), вилучається з хеш-таблиці через hlist_del_rcu().
  • Безпечне звільнення пам'яті через RCU: Фізична деалокація пам'яті делегується kfree_rcu(), яка спрацьовує лише після проходження всіма ядрами процесора стану спокою, що унеможливлює помилки use-after-free.
  • Адаптивне обмеження обсягу: Якщо кількість записів наближається до встановленого ліміту під час потужної атаки, система витісняє найстаріші записи, захищаючи пам'ять сервера від переповнення.

Налаштування параметрів мережевого стека та моніторинг

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

Перевірка стану буферів сокета UDP на порту WireGuard:

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

Аналіз розподілу програмних переривань (softirq) між ядрами процесора:

# Оцінка навантаження переривань мережевих карт
mpstat -P ALL 1 5

Коригування мережевих параметрів для підтримки високих навантажень:

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

Стійкість та висока доступність в інфраструктурі VPNRice

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

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

  2. Автоматичні челенджі Cookie: У разі виявлення сплеску підозрілого трафіку шлюзи миттєво активують перевірку Cookie MAC2, що дає змогу реальним користувачам залишатися на зв'язку без відчутних затримок.

  3. Ізольовані ключі SipHash: Кожен окремий мережевий домен у вузлах VPNRice використовує унікальну сіль хешування, унеможливлюючи спрямовані атаки колізій між клієнтами.

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