Шлюзы виртуальных частных сетей, подключенные к открытым магистральным каналам интернета, сталкиваются с фундаментальной вычислительной асимметрией. Когда удаленный клиент инициирует защищенный туннель, криптографический протокол выполняет ресурсоемкие асимметричные вычисления — в частности, скалярное умножение точек на эллиптической кривой Curve25519 — для безопасного согласования сессионных ключей.
На современных серверных процессорах операция умножения на Curve25519 занимает доли миллисекунды, тогда как отправка поддельного UDP-пакета размером 148 байт по сетевому каналу 10G требует всего лишь нескольких наносекунд. Злоумышленник, генерирующий поток поддельных пакетов инициализации с подменой 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 ядра, провоцируя сбои операционной системы из-за нехватки памяти (OOM).
Реализации OpenVPN опираются на конечные автоматы OpenSSL или mbedTLS в пользовательском пространстве. Каждый входящий пакет TLS Client Hello влечет за собой разбор структур ASN.1, проверку цепочек сертификатов и валидацию открытых ключей RSA или ECDSA. Поскольку эти вызовы происходят после многократных переключений контекста между ядром и пользовательским процессом, входящий поток всего в несколько тысяч пакетов в секунду загружает процессор на 100%, приводя к обрыву соединений у всех активных клиентов.
Кроме того, простые ограничители трафика на базе типовых правил пакетных фильтров создают собственные узкие места:
- Разрастание таблиц соединений: Отслеживание сотен тысяч поддельных адресов IPv4 и IPv6 переполняет таблицы межсетевых экранов.
- Конкуренция за блокировки: Многопоточные ограничители с централизованными мьютексами парализуют работу ядер CPU из-за взаимных блокировок при синхронизации кэш-линий.
- Деградация хеширования (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-адресов, работают полностью параллельно.
3. Защитный переход к проверке Cookie MAC2
Если частота запросов с определенного IP-адреса превышает доступный лимит жетонов, WireGuard не отбрасывает пакет безвозвратно. Вместо этого включается многоуровневая защитная логика:
Штатный режим (жетоны в наличии):
Клиентский пакет [ Тип 1 | Без аутентификации | MAC1 ]
--> Ограничитель разрешает операцию
--> Скалярное умножение Curve25519
--> Отправка ответа Тип 2
Режим перегрузки (жетоны исчерпаны):
Клиентский пакет [ Тип 1 | Превышение лимита пакетов | MAC1 ]
--> Ограничитель запрещает прямые криптографические вычисления
--> Если поле MAC2 пустое: Отправка Cookie-ответа Тип 3
--> Клиент вычисляет MAC2 и повторяет отправку Тип 1 [ MAC1 + MAC2 ]
--> Сервер проверяет Cookie без сохранения состояния и завершает сессию
В моменты сильного флуда шлюз отказывается выполнять вычисления Curve25519 для непроверенных отправителей. Сервер принуждает клиента вернуть криптографическую Cookie (формируемую из IP-адреса, порта и периодически сменяемого секретного ключа). Поскольку для создания Cookie требуется лишь симметричное хеширование BLAKE2s, сервер справляется с миллионами запросов на скорости интерфейса. Легитимный клиент автоматически формирует MAC2 и восстанавливает сессию, в то время как потоки с поддельными адресами мгновенно нейтрализуются.
Фоновая очистка неактивных записей памяти
При распределенных DoS-атаках с использованием ботнетов из миллионов адресов таблица отслеживания могла бы бесконтрольно расти.
Для гарантированного удержания расхода памяти в строгих границах WireGuard запускает периодическую фоновую очистку через системные отложенные очереди задач (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(), что исключает гонки состояний при одновременном обращении из других потоков. - Адаптивный контроль лимитов: При превышении общего числа записей во время интенсивной атаки алгоритм принудительно удаляет наиболее старые записи, гарантируя стабильность оперативной памяти шлюза.
Настройка параметров сетевого стека и мониторинг
Инженеры 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
Поддержание качественной виртуальной частной сети корпоративного уровня требует предсказуемой устойчивости к распределенным атакам. Сеть VPNRice повсеместно использует многоуровневую защиту в узлах Anycast:
Защита на аппаратной скорости: Реализация механизма token bucket на уровне ядра позволяет серверам VPNRice отсекать всплески паразитного трафика до фазы асимметричных вычислений, сохраняя вычислительные мощности для передачи пользовательских данных.
Автоматический запуск Cookie-испытаний: При обнаружении перегрузок шлюзы VPNRice активируют верификацию Cookie MAC2, благодаря чему реальные пользователи продолжают работу без разрывов соединений, а флуд нейтрализуется на аппаратном уровне.
Изолированные ключи SipHash: Каждый сетевой домен узла VPNRice генерирует независимые соли хеширования, предотвращая перекрестные алгоритмические коллизии между клиентами.
Интеграция алгоритма token bucket, хеш-таблиц SipHash и неблокирующей архитектуры RCU обеспечивает шлюзам VPN надежную передачу данных и минимальные задержки при любых нагрузках.



