Шлюзы виртуальных частных сетей (VPN), работающие в открытых сетях, непрерывно обрабатывают неаутентифицированный трафик. При нормальной нагрузке криптографический туннель аутентифицирует пиры с помощью асимметричного рукопожатия. Однако при распределенных атаках типа «отказ в обслуживании» (DDoS) злоумышленники эксплуатируют асимметрию вычислительных затрат: подделка миллиона входящих UDP-пакетов инициации требует минимальных ресурсов, тогда как целевой сервер вынужден производить «тяжелые» криптографические операции и выделять структуры сессий в оперативной памяти ядра.
Традиционные протоколы, такие как OpenVPN и IPsec IKEv2, уязвимы к подобным сценариям: они создают управляющие структуры и буферы ассоциаций безопасности (SA) уже на этапе первого запроса. WireGuard полностью устраняет эту уязвимость. Реализуя строгую модель отсутствия состояния (stateless) до завершения рукопожатия и двухуровневую систему кодов аутентификации сообщений (mac1 и mac2) с ротацией криптографических cookie, протокол надежно изолирует серверные ресурсы от перегрузок.
В ядре Linux эта логика сосредоточена в файле drivers/net/wireguard/cookie.c. Исследование его криптографической структуры объясняет, каким образом скоростные туннели выдерживают высоконагруженный поток данных без утечки памяти.
Асимметрия рукопожатия и устойчивость без сохранения состояния
Шаблон Noise IK в WireGuard требует взаимной аутентификации с использованием скалярного умножения на эллиптической кривой Curve25519 (X25519). Операция на кривой требует значительно больше процессорных циклов, чем стандартная маршрутизация пакета. Если бы сервер принимал каждый пакет Type 1: Handshake Initiation и сразу вычислял общий секрет, поток пакетов со спуфингом IP быстро утилизировал бы 100% ресурсов всех процессорных ядер, лишая легитимные соединения полосы пропускания.
Архитектура WireGuard разделяет верификацию пакетов на независимые рубежи:
[ Входящий пакет инициации ]
│
▼
Проверка MAC1 (Статический ключ)
├─ Не совпадает ──► Тихий сброс (Zero Response)
└─ Корректен
│
▼
Сервер под нагрузкой? (ratelimiter.c)
├─ Нет ──► Вычисление Curve25519 ScalarMult
└─ Да
│
▼
Проверка MAC2 (Ротируемый Cookie)
├─ Корректен ──► Вычисление Curve25519 ScalarMult
└─ Не совпадает ──► Отправка Type 3: Cookie Reply (Stateless)
В штатном режиме, пока частота запросов не превышает лимитов ratelimiter.c, сервер валидирует mac1 и переходит к генерации ключей сессии. При всплеске нагрузки сервер переключается на защитный барьер, требуя обязательного наличия корректного токена mac2, сгенерированного на основе выданного сервером cookie.
Структура двухуровневой проверки MAC (mac1 и mac2)
Каждый входящий пакет инициации WireGuard содержит два 16-байтовых поля в конце структуры: mac1 (байты 116–131) и mac2 (байты 132–147). Они выполняют взаимодополняющие задачи:
- Рубеж
mac1: Вычисляется какBLAKE2s-128(key = Hash(Label-Mac1 || Responder_PublicKey), message = packet[0..115]). Поскольку ключ получен из открытого ключа сервера, только сконфигурированные клиенты могут сформировать валидный тег. Пакеты сетевых сканеров не проходят проверку и отбрасываются без ответа, предотвращая обнаружение открытых портов. - Рубеж
mac2: Вычисляется какBLAKE2s-128(key = Cookie, message = packet[0..131]). Полеmac2покрывает весь пакет, включая предварительно проверенный блокmac1. В качестве 16-байтового ключа выступает временный криптографический cookie, генерируемый сервером для подтвержденных IP-адресов.
В штатном режиме клиент передает поле mac2, заполненное шестнадцатью нулевыми байтами (0x00). Когда шлюз переходит в режим перегрузки, запросы с нулевым или недействительным mac2 не допускаются к вычислениям на CPU. Сервер не сохраняет состояние сессии, а запускает процедуру запроса-ответа без выделения памяти.
Реализация cookie.c в ядре: механизм запрос-ответ
Когда модуль ratelimiter.c фиксирует перегрузку, управление переходит к функции wg_cookie_checker_create_payload(). Ядро формирует 64-байтовый служебный пакет Type 3: Handshake Cookie Reply:
┌────────────────────────────────────────────────────────┐
│ Тип: 3 (1 байт) │ Резерв: 0 (3 байта) │
├────────────────────────────────────────────────────────┤
│ Индекс получателя: sender_index клиента (4 байта) │
├────────────────────────────────────────────────────────┤
│ Nonce XChaCha20: Случайное значение CSPRNG (24 байта) │
├────────────────────────────────────────────────────────┤
│ Зашифрованный токен Cookie: XChaCha20-Poly1305 (32 B) │
│ - 16 байт открытого cookie │
│ - 16 байт тега аутентификации Poly1305 │
└────────────────────────────────────────────────────────┘
Создание такого ответа требует минимального числа тактов процессора и не задействует динамическое выделение памяти (kmalloc = 0). Пакет возвращает клиенту его sender_index, позволяя сопоставить ответ с ожидающим рукопожатием, а полезная нагрузка cookie шифруется алгоритмом XChaCha20-Poly1305.
Ключ симметричного шифрования вычисляется детерминированно:
Cookie_Encryption_Key = BLAKE2s-256(
key = Hash(Label-Cookie || Responder_PublicKey),
message = Initiator_MAC1
)
Поскольку значение Initiator_MAC1 уже проверено сервером, оно выступает в качестве криптографической соли для конкретной попытки подключения, не позволяя перехватчикам заранее подобрать ключ расшифровки.
Ротация энтропии секрета и подтверждение владения IP
Главное достоинство протокола WireGuard — проверка подлинности IP-адреса инициатора без записи информации о сессии. Сервер рассчитывает cookie с использованием локального 32-байтового секрета (cookie_checker->secret), полученного из генератора случайных чисел ядра (get_random_bytes).
Формула жестко связывает физический адрес отправителя с выданным токеном:
Cookie = BLAKE2s-128(
key = cookie_checker->secret,
message = Initiator_IP_Address || Initiator_Port
)
Для предотвращения накопления валидных cookie атакующими сервер осуществляет ротацию секрета каждые 120 секунд (COOKIE_SECRET_MAX_AGE = 120).
Для компенсации задержек сетевой маршрутизации cookie.c хранит два поколения секретов:
secret[0]: Актуальный секрет текущей эпохи для генерации новых cookie и проверки свежих пакетов.secret[1]: Предыдущий секрет прошедшего 120-секундного интервала, гарантирующий корректную валидацию пакетов на стыке интервалов смены ключа.
При атаке с подделкой IP-адресов сервер отправляет пакеты Type 3: Cookie Reply на эти фиктивные адреса по протоколу UDP. Поскольку атакующий не контролирует транзитный маршрут сторонних IP, он не может прочитать зашифрованный cookie.
Легитимный клиент при получении ответа вызывает функцию wg_cookie_message_consume(). Клиент расшифровывает токен, сохраняет его в кэше (peer->latest_cookie), фиксирует cookie_birthdate = jiffies и незамедлительно повторно отправляет пакет Handshake Initiation с заполненным полем mac2. Сервер валидирует mac2 за константное время O(1), подтверждает легитимность адреса и переходит к криптографии Curve25519.
Параллелизм без блокировок и архитектура RiceGuard
Высокая пропускная способность cookie.c достигается благодаря структуре без блокировок. Чтение ротируемых секретов и вычисление MAC происходит внутри критической секции rcu_read_lock(). Это исключает конфликты спинлоков на приеме пакетов, позволяя всем процессорным ядрам обрабатывать mac1 и mac2 параллельно.
Входящий пакет ──► [Очередь Rx NIC] ──► Чтение RCU ──► Проверка BLAKE2s MAC ──► Воркер ядра
В инфраструктуре VPNRice архитектура RiceGuard дополняет механизм ядра интеграцией с технологиями eBPF и XDP (eXpress Data Path). Проверка двух уровней MAC выносится непосредственно на уровень драйвера сетевого адаптера до распределения структур буфера ядра sk_buff. Это позволяет нейтрализовать спуфинг-атаки на аппаратном рубеже сетевого оборудования, сохраняя минимальную задержку и непрерывную конфиденциальность пользовательского трафика.



