Під час оцінювання протоколів захищених мережевих тунелів інженери зазвичай зосереджуються на стійкості шифрування та автентичності корисного навантаження. Сучасні криптографічні схеми, як-от ChaCha20-Poly1305 (RFC 8439), математично гарантують, що сторонній спостерігач не зможе розшифрувати пакети чи змінити їхній вміст у фізичному середовищі. Проте саме по собі шифрування не усуває загрозу, притаманну природі мереж IP без встановлення з'єднання: атаки повторного відтворення пакетів (replay attacks).
Зловмиснику, який прослуховує мережевий трафік, зовсім не обов'язково зламувати криптографічний ключ. Записуючи автентичні зашифровані пакети UDP і надсилаючи їх повторно через деякий час, нападник може порушити активну сесію, спровокувати подвійне виконання операцій або викликати збої в автоматах станів прикладних протоколів. Ба більше, для потокових шифрів, таких як ChaCha20, повторне застосування вектора ініціалізації (nonce) для двох різних пакетів на одному ключі є неприпустимим, оскільки проста операція XOR розкриває гаму шифрування.
Щоб повністю ліквідувати ризики повторів і одночасно забезпечити стабільно високу швидкість передачі за умов мережевого джитера, WireGuard впроваджує архітектуру захисту на основі стандарту RFC 6479. Поєднання 64-бітних лічильників передачі та бітової карти ковзного вікна забезпечує цілковиту стійкість до повторів без втрати пакетів через природну зміну черговості їхнього прибуття.
+-----------------------------------------------------------------------------+
| МЕХАНІЗМ БІТОВОГО КОВЗНОГО ВІКНА RFC 6479 У WIREGUARD |
| |
| Відкинуто (занадто старий) Активне вікно бітової карти |
| ◄───────────────────────────┤──────────────────────────────────────────► |
| Лічильник < (T - 2048) │ від (T - 2048) до T │ |
| │ │ |
| ... [Пакет k] [Пакет k+1]...│ [Біт 0] [Біт 1] ... [Біт i] ... [Біт 2047]│ |
| │ │ │ │ │ │ |
| │ Найстаріший│ Поточний пакет │ │ |
| │ у вікні │ (Перевірка/запис)│ │ |
| └────────────┴───────────┬───────────┴──────┘ |
| │ |
| ▼ |
| Якщо Біт == 1: СКАСУВАННЯ (Повтор)|
| Якщо Біт == 0: ПРИЙОМ і запис = 1 |
| Якщо Лічильник > T: ЗСУВ ВІКНА |
+-----------------------------------------------------------------------------+
Загроза повтору Nonce та 64-бітний лічильник передачі
Шифр ChaCha20-Poly1305 потребує трьох компонентів: 256-бітного симетричного ключа, відкритого тексту та унікального вектора ініціалізації (nonce). WireGuard застосовує послідовне 64-бітне ціле число безпосередньо як nonce для ChaCha20, додаючи до пакета 16-байтний тег автентифікації Poly1305.
Унікальність вектора nonce є непохитною вимогою криптографії. Якщо відправник зашифрує два різних пакети з однаковою парою ключа та nonce:
$$C_1 = P_1 \oplus Keystream(K, N)$$ $$C_2 = P_2 \oplus Keystream(K, N)$$
Аналізатор трафіку обчислює суму XOR двох шифротекстів, повністю вилучаючи ключову гаму:
$$C_1 \oplus C_2 = P_1 \oplus P_2$$
Таке співвідношення робить вихідні дані вразливими до статистичного аналізу та дозволяє підробляти підписи Poly1305. Задля запобігання цьому WireGuard збільшує 64-бітний лічильник для кожного вихідного пакета, починаючи з нуля під час встановлення нової сесії.
Місткість 64-бітного лічильника охоплює понад $1.8 \times 10^{19}$ пакетів, тому вичерпати його під час звичайної сесії неможливо. Навіть у разі безперервної передачі на швидкості 100 Гбіт/с знадобляться десятиліття для досягнення ліміту. Втім, протокол Noise IKpsk2 кожні кілька хвилин виконує планову ротацію сесійних ключів, повертаючи лічильник до нуля під новим ефемерним ключем.
Реалії порушення порядку пакетів у мережі
Хоча передавач збільшує лічильники в суворій послідовності ($0, 1, 2, 3, \dots$), інфраструктура інтернету передає дейтаграми UDP цілком незалежно. На практиці пакети стикаються з різними затримками:
- Багатошляхова маршрутизація (ECMP): Магістральні комутатори розподіляють потоки різними фізичними лініями з неоднаковим часом проходження.
- Багатопотокова обробка мережевими платами (RSS): Адаптери скеровують вхідні кадри на різні ядра процесора, викликаючи невелику часову розбіжність.
- Флуктуації затримок у бездротових каналах: Перемикання веж зв'язку 5G або буферизація Wi-Fi спричиняють випадкові мілісекундні паузи.
Внаслідок цього пакети часто надходять до отримувача зі зміненим порядком:
$$\text{Надіслано:} \quad [1, 2, 3, 4, 5, 6]$$ $$\text{Отримано:} \quad [1, 3, 2, 5, 4, 6]$$
Якби вузол прийому застосовував суворе правило зростання (відхиляючи будь-який пакет із номером, що не перевищує найбільший раніше зареєстрований), пакет номер 2 був би відкинутий через те, що прибув після пакета 3. Втрата корисних даних через звичайну тимчасову перестановку призводить до різкого спаду пропускної здатності вкладеного протоколу TCP.
Коли TCP фіксує втрату пакета, він генерує повторні підтвердження (dupACK), активує алгоритми швидкої повторної передачі та зменшує вікно перевантаження вдвічі, що обвалює швидкість передачі. Механізм захисту від повторів повинен приймати пакети, які прибули не за чергою, але безпомилково відкидати підроблені копії.
+-----------------------------------------------------------------------------+
| МАТРИЦЯ РІШЕНЬ ДЛЯ ВХІДНИХ ПАКЕТІВ |
+--------------------------+----------------------------+---------------------+
| Лічильник пакета ($C$) | Положення щодо вікна | Дія підсистеми ядра |
+--------------------------+----------------------------+---------------------+
| $C > T_{\text{вершина}}$ | Випереджає поточне вікно | Просування вікна |
| | | Встановлення біта в 1|
+--------------------------+----------------------------+---------------------+
| $T_{\text{вершина}} - W \le$| Усередині активного | Якщо біт == 0: ПРИЙОМ|
| $C \le T_{\text{вершина}}$| діапазону вікна | Якщо біт == 1: СКИД |
+--------------------------+----------------------------+---------------------+
| $C < T_{\text{вершина}} - W$| Позаду нижньої межі | МИТТЄВИЙ СКИД |
| | | (Застарілий пакет) |
+--------------------------+----------------------------+---------------------+
Алгоритм RFC 6479: ковзне вікно без побітового зсуву пам'яті
Для розв'язання цієї проблеми в ранніх стандартах IPsec (RFC 4303) застосовувалася бітова карта ковзного вікна розміром 32 або 64 біти. У разі прибуття пакета з більшим порядковим номером уся бітова карта зсувалася в оперативній пам'яті ліворуч.
На високошвидкісних інтерфейсах із мільйонами пакетів на секунду постійний зсув масиву бітів (наприклад, 1024 чи 2048 бітів) призводить до непродуктивної витрати процесорного часу та знецінення кешу CPU. WireGuard використовує оптимізований підхід, описаний у документі RFC 6479.
Математична модель та циклічна адресація
Замість фізичного переміщення бітів у пам'яті алгоритм RFC 6479 підтримує фіксований кільцевий масив машинних слів. У WireGuard ширина вікна становить $W = 2048$ бітів, що реалізовано як масив із 32 беззнакових 64-бітних цілих чисел ($32 \times 64 = 2048$ бітів):
#define COUNTER_WINDOW_SIZE 2048
#define COUNTER_BITS_PER_WORD 64
#define COUNTER_WORDS (COUNTER_WINDOW_SIZE / COUNTER_BITS_PER_WORD) // 32 слова
struct noise_replay_counter {
u64 counter;
unsigned long backtrack[COUNTER_WORDS];
spinlock_t lock;
};
Коли надходить дейтаграма з лічильником $C$, відповідне слово визначається за допомогою операції ділення за модулем:
$$\text{Індекс слова} = \left(\frac{C}{64}\right) \pmod{32}$$ $$\text{Зсув біта} = C \pmod{64}$$
Оскільки розмірність масиву є степенем двійки, операція взяття за модулем перетворюється на швидку побітову операцію AND:
unsigned long word_index = (counter / 64) & (COUNTER_WORDS - 1);
unsigned long bit_mask = 1UL << (counter & (COUNTER_BITS_PER_WORD - 1));
Динаміка переміщення меж вікна
Отримувач розподіляє вхідні пакети за трьома сценаріями:
- Усередині активного вікна ($T - W < C \le T$): Система перевіряє зазначений біт. Якщо він дорівнює 1, пакет є копією і відкидається. Якщо він дорівнює 0, пакет приймається, а біт перемикається в одиницю.
- Вище за вершину ($C > T$): Прибув пакет із максимальним номером за поточну сесію. Межі вікна пересуваються вперед. Алгоритм скидає в 0 проміжні слова між старою та новою вершиною, оновлює верхню межу ($T = C$) та позначає потрібний біт.
- За нижньою межею ($C \le T - W$): Пакет запізнився більш ніж на 2048 позицій. Система негайно відкидає дейтаграму без додаткового навантаження на криптографічний модуль.
Такий підхід забезпечує стабільну складність $O(1)$, усуває динамічне виділення пам'яті та максимально ефективно використовує кеш процесора.
Структура кільцевого буфера в пам'яті (32 машинних слова):
Слово 0 Слово 1 Слово 2 Слово 31
[64 біти] [64 біти] [64 біти] ... [64 біти]
▲
│
└── Циклічний вказівник пересувається без переміщення байтів у пам'яті
Паралельне дешифрування та синхронізація в багатоядерних системах
У високопродуктивних серверах дешифрування мільйонів пакетів UDP на одному ядрі створює вузьке місце. У ядрі Linux WireGuard розподіляє криптографічні завдання між усіма ядрами CPU за допомогою паралельних черг padata.
Конвеєр працює злагоджено:
- Прийом пакетів: Мережевий драйвер записує пакети UDP у буфер прийому.
- Паралельна криптографія: Модуль передає дешифрування ChaCha20-Poly1305 ядрам процесора, де кожне ядро самостійно підтверджує тег автентифікації.
- Послідовний контроль повторів: Оскільки ядра завершують обробку несинхронно, оновлення ковзного вікна виконується на фінальній легкій стадії (
wg_packet_consume_data_done) за допомогою атомарних блокувань.
Такий поділ між паралельними криптографічними обчисленнями та компактним контролем номерів дейтаграм дає змогу досягати швидкостей у десятки гігабітів на секунду без ризику колізій.
Баланс між розміром вікна та мережевим джитером
Розмір вікна у 2048 бітів обрано на основі балансу між споживанням пам'яті та стійкістю до затримок:
- Мале вікно (наприклад, 64 біти) спричиняє помилкове відкидання легітимного пакета, якщо він затримався всього на 64 кадри на лінії 10 Гбіт/с.
- Завелике вікно (наприклад, 65 536 бітів) збільшує витрати оперативної пам'яті на кожне з'єднання, знижуючи швидкість роботи кешу процесора у разі обслуговування тисяч клієнтських тунелів.
Стельова місткість у 2048 бітів при стандартному розмірі MTU 1420 байтів охоплює майже 2,9 МБ даних, що повністю покриває можливі розбіжності затримок на міжнародних магістралях.
В інфраструктурі VPNRice протокол RiceGuard базується на описаних механізмах. У поєднанні з маршрутизацією третього рівня та оптимізаціями, наведеними в нашому матеріалі про Архітектури тунелювання UDP, RiceGuard надійно захищає від атак повтору та запобігає перевантаженню з'єднання.
Щоб налаштувати стабільне та захищене з'єднання для робочих станцій та мобільних пристроїв, встановіть останню версію на сторінці Завантажити VPNRice.



