Высоконагруженные шлюзы VPN часто поддерживают входящие соединения по протоколу TCP параллельно с основными туннелями UDP. В корпоративных инфраструктурах TCP служит резервным каналом при фильтрации трафика UDP, а также применяется для плоскости управления и защищенных туннелей TLS. Публичные узлы с открытым портом TCP 443 регулярно подвергаются распределенным атакам класса TCP SYN flood.
Современные сетевые адаптеры способны обрабатывать миллионы пакетов в секунду, однако стандартная подсистема отслеживания соединений Linux (conntrack) становится уязвимым местом. Если межсетевой экран выделяет память под запись состояния для каждого входящего пакета до проверки подлинности клиента, таблица переполняется за доли секунды. Надежным решением является отказ от сохранения состояния на этапе начального рукопожатия с помощью механизма Linux nftables SYNPROXY.
Понимание работы SYNPROXY требует детального рассмотрения пути прохождения пакетов в подсистеме Netfilter.
Угроза переполнения таблицы отслеживания соединений:
Входящий SYN Flood ──> Сетевой адаптер (NIC) ──> Netfilter PREROUTING ──> Выделение памяти conntrack (struct nf_conn)
│
ПЕРЕПОЛНЕНИЕ (nf_conntrack: table full)
│
СБОЙ СВЯЗИ (Сброс всех сессий)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────
Фильтрация без сохранения состояния через nftables SYNPROXY:
Входящий SYN Flood ──> PREROUTING (notrack) ──> INPUT (SYNPROXY) ──> Ответ SYN-ACK без состояния (Syncookie)
│
(Память conntrack не выделяется)
│
Подлинный клиент шлет ACK ─────────────────────────────────────> Проверка Syncookie ──> Создание conntrack ──> Демон VPN
Узкое место conntrack: Ограничения стандартных syncookie сокета
Среди системных инженеров распространено мнение, что активация параметра net.ipv4.tcp_syncookies = 1 полностью решает проблему атак SYN flood. На практике этот параметр защищает лишь локальную очередь прослушивания сокета (listen(backlog)), оставляя подсистему отслеживания соединений nf_conntrack беззащитной.
В сетевом стеке Linux подсистема Netfilter перехватывает трафик на этапе NF_INET_PRE_ROUTING, задолго до передачи пакета в стек протокола (NF_INET_LOCAL_IN). При получении пакета с флагом SYN подсистема nf_conntrack сразу выделяет объект памяти struct nf_conn и добавляет запись в глобальную хеш-таблицу в статусе SYN_RECV:
Прием пакета ──> nf_conntrack_in() ──> init_conntrack() ──> kmem_cache_alloc(nf_conntrack_cachep)
Каждая запись занимает от 320 до 384 байт памяти ядра. При стандартном лимите nf_conntrack_max в 262 144 записи атака мощностью 300 000 пакетов SYN в секунду исчерпывает таблицу менее чем за 900 миллисекунд.
Последствия переполнения таблицы:
- Ядро фиксирует ошибку
net_ratelimit: nf_conntrack: table full, dropping packet. - Работающие туннели и новые легитимные сессии принудительно сбрасываются.
- Нагрузка на процессор резко возрастает из-за обработки очередей softirq.
Поскольку проверка сокетных syncookie выполняется в функции tcp_v4_rcv() уже после сбоя выделения памяти в nf_conntrack, она не успевает сработать. Для защиты шлюза валидация рукопожатия должна происходить до этапа отслеживания состояния.
Внутренняя архитектура Netfilter SYNPROXY
Компонент synproxy, реализованный в коде ядра net/netfilter/nf_synproxy_core.c и интегрированный в nftables, действует как прокси-сервер TCP без сохранения состояния на уровне ядра. Он делит установление сессии на две фазы: внешнее рукопожатие без состояния с клиентом и последующее внутреннее рукопожатие с локальным приложением.
Внешний клиент nftables SYNPROXY Локальный демон VPN
│ │ │
│─── 1. TCP SYN [MSS, WScale, SACK] ─────>│ (Без сохранения состояния в памяти) │
│ │ │
│<── 2. Ответ SYN-ACK [Cookie] ───────────│ (Криптографический хеш 4-tuple и ISN) │
│ │ │
│─── 3. TCP ACK [Cookie + 1] ────────────>│ │
│ │─── 4. Локальный TCP SYN ─────────────>│
│ │<── 5. Локальный SYN-ACK [ISN демона] ─│
│ │─── 6. Локальный ACK ─────────────────>│
│ │ │
│═════════════════════════════════════════╪═══════════════════════════════════════│
│ Фаза передачи данных и коррекция смещения номеров последовательности │
│─── 7. Трафик TLS / Данные ─────────────>│─── 8. Модификация SEQ/ACK (дельта) ───>│
│<── 10. Модификация SEQ/ACK (дельта) ────│<── 9. Ответный трафик ────────────────│
Алгоритм обработки состоит из четырех последовательных этапов:
Этап 1: Исключение из отслеживания
Входящие пакеты SYN на порт VPN помечаются директивой notrack в таблице prerouting. Это предотвращает создание структуры struct nf_conn.
Этап 2: Генерация Syncookie без состояния
Пакет поступает в цепочку INPUT на правило SYNPROXY. Вместо отправки в очередь сокета SYNPROXY вычисляет 32-битный начальный номер последовательности (ISN) на основе криптографического хеша:
- IP-адресов источника и назначения
- Портов источника и назначения
- Начального номера последовательности клиента
- Секретного ключа ядра, обновляемого каждые 60 секунд
- Закодированных параметров TCP (MSS, Window Scaling, поддержка SACK)
SYNPROXY генерирует пакет SYN-ACK с вычисленным значением и сразу отправляет его клиенту. Ядро не создает структур состояния и таймеров. Если пакет был сгенерирован ботнетом с поддельным адресом, процесс завершается без затрат оперативной памяти.
Этап 3: Проверка подтверждения ACK и создание записи
Подлинный клиент при получении SYN-ACK отправляет ответный пакет ACK со значением Cookie + 1. SYNPROXY перехватывает этот пакет и проверяет корректность криптографического хеша.
После подтверждения подлинности соединения:
- В
nf_conntrackсоздается полноценный объектstruct nf_conn. - Состояние переводится напрямую в статус
ESTABLISHED. - Восстанавливаются согласованные параметры TCP.
Этап 4: Локальное рукопожатие и трансляция номеров
Для взаимодействия с локальным демоном VPN, ожидающим стандартное рукопожатие, SYNPROXY синтезирует локальный пакет SYN в адрес локального сокета.
После ответа демона пакетом SYN-ACK со своим сгенерированным номером (ISN_server) SYNPROXY фиксирует математическую разницу:
$$\Delta = ext{ISN}_{ ext{server}} - ext{Cookie}$$
В дальнейшем при передаче полезной нагрузки SYNPROXY на лету корректирует заголовки пакетов:
- Для входящего трафика: увеличивает номер последовательности на величину $\Delta$.
- Для исходящего трафика: уменьшает номер подтверждения на величину $\Delta$.
Трансляция выполняется целиком внутри ядра с максимальной пропускной способностью и абсолютно прозрачна для приложения.
Практическая реализация: Настройка правил nftables SYNPROXY
Для внедрения защиты создается набор правил nftables с разделением приоритетов цепочек.
Рабочая конфигурация для защиты порта 443:
#!/usr/sbin/nft -f
flush ruleset
table inet vpn_filter {
synproxy synproxy_vpn {
mss 1460
wscale 7
timestamp sack-perm
}
chain raw_prerouting {
type filter hook prerouting priority raw; policy accept;
tcp dport 443 tcp flags syn notrack
}
chain filter_input {
type filter hook input priority filter; policy drop;
iif "lo" accept
ct state established,related accept
ct state invalid drop
tcp dport 443 synproxy config synproxy_vpn
tcp dport 443 ct state new accept
}
chain filter_forward {
type filter hook forward priority filter; policy drop;
}
chain filter_output {
type filter hook output priority filter; policy accept;
}
}
Применение правил в системе:
sudo nft -f /etc/nftables.conf
Проверка состояния активных таблиц:
sudo nft list table inet vpn_filter
Тонкая настройка ядра и мониторинг метрик
Для корректной работы SYNPROXY необходимо задать сетевые параметры ядра в файле /etc/sysctl.d/99-synproxy.conf:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_timestamps = 1
net.netfilter.nf_conntrack_tcp_loose = 0
net.netfilter.nf_conntrack_max = 2097152
net.netfilter.nf_conntrack_buckets = 524288
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 10
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
Применение параметров:
sudo sysctl --system
Мониторинг работы подсистемы
Статистика фильтрации доступна через интерфейс файловой системы /proc:
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/net/stat/synproxy
Основные метрики:
entries: Количество активных соединений под управлением SYNPROXY.syn_received: Число обработанных входящих пакетов SYN без создания состояния.cookie_invalid: Количество отклоненных поддельных пакетов ACK.cookie_valid: Успешно подтвержденные сессии, переданные в conntrack.
Роль механизма в сетевой архитектуре RiceGuard
Комплексная сетевая безопасность требует надежной защиты каждого используемого протокола. Хотя основа протокола RiceGuard построена на UDP с криптографической аутентификацией сессий, шлюзовая инфраструктура предоставляет интерфейсы TCP для работы в сложных сетевых условиях и защиты DNS-запросов через HTTPS.
Развертывание nftables SYNPROXY на границе периметра позволяет шлюзам RiceGuard выдерживать мощные всплески паразитного трафика без исчерпания оперативной памяти, сохраняя вычислительные ресурсы для шифрования данных пользователей.



