- remove Java plugins (velocity/paper), dashboard, all MC-specific code
(handshake, death_code, varint, hostname-HMAC); available in history pre-v0.2
- merge crates/* into one package with src/bin/{rampart,rampart-manager,rampart-cli}
- ProtocolHandler trait + registry (no implementations yet), universal PoW kept
- XDP: universal L3/L4 filter (xdp/core/) + pluggable hook API (xdp/hooks/),
fix IPv6 saddr bug; clang build verified
- docs: bilingual knowledge base (docs/kb/: attacks x4, defense-levels,
practice x3), rewrite README/architecture for universal concept
- TODO.md v4.0: <=300-line module limit, competitor benchmark section (ref/)
- deploy/CI/docs cleanup: no MC references, new binary names
cargo build/clippy(-D warnings)/test green (55 tests)
16 KiB
SYN Flood: Anatomy of the Classic TCP Attack
Knowledge Base · Rampart attack fundamentals · Related: kernel-tuning.md, defense-levels.md
English
1. The TCP handshake, briefly
Every TCP connection starts with a three-way handshake:
- SYN — the client sends a packet with the SYN flag and its initial sequence number (ISN).
- SYN-ACK — the server replies with SYN+ACK and its own ISN.
- ACK — the client confirms; the connection moves to
ESTABLISHED.
Between steps 1 and 3, the connection is half-open: the server has already allocated memory for it in a special queue called the SYN backlog (tcp_max_syn_backlog), waiting for the final ACK. A half-open connection lives until tcp_synack_retries retransmissions are exhausted (default ~1 minute).
This asymmetry is the whole problem: the attacker spends one packet per half-open connection, the server spends memory + a timer + a potential retransmission.
2. How the attack works
The attacker floods the target with SYN packets and never sends the final ACK (or spoofs an unreachable source IP, so SYN-ACKs go nowhere). The backlog fills up with dead half-open connections. When it is full, legitimate SYNs are dropped — the service becomes unreachable.
sequenceDiagram
participant B as Attacker / botnet
participant K as Kernel (listen socket)
participant L as Legitimate client
Note over K: SYN backlog capacity = tcp_max_syn_backlog
B->>K: SYN #1 (never completes)
K-->>B: SYN-ACK (retransmits up to tcp_synack_retries)
B->>K: SYN #2..#N (no ACK ever)
K-->>B: SYN-ACK...
Note over K: backlog full of half-open entries<br/>retransmit timers burning CPU
L->>K: SYN (legitimate)
K--)L: dropped — backlog overflow<br/>FILTERING POINT: this is what we must prevent
Key point: the victim never sees an error — connections simply time out. From the user's perspective the port is "down" even though the machine may be nearly idle.
3. Attack variants
| Variant | Source addresses | Difficulty | Why it works |
|---|---|---|---|
| Single-IP flood | One real IP | Trivial | Only viable against hosts without per-source limiting; one IP can still open tens of thousands of half-open sockets |
| Distributed (botnet) | Many real IPs | Low | Each bot opens a few hundred half-open connections; per-IP limits are diluted across thousands of sources |
| Spoofed source IPs | Random / unreachable IPs | Medium | SYN-ACK goes to a third party; the attacker pays 1 packet per connection, the server pays memory + retransmissions; per-IP limiting is useless because each "source" appears once |
Spoofed floods are the most dangerous variant: rate-limiting by source IP cannot help when every packet has a fresh fake address.
4. SYN cookies: stateless handshake
SYN cookies move the connection state out of server memory and into the wire. Instead of allocating a backlog entry, the server encodes everything it needs to know into the sequence number of its own SYN-ACK:
SYN-ACK seq = MD5/IP-hash(secret, src_ip, src_port, dst_ip, dst_port) ← top 24 bits
+ timestamp mod 2^6 ← middle 5 bits (rotating)
+ MSS encoding ← low 3 bits
When the final ACK arrives, the server recomputes the expected value from the ACK number. If it matches, the connection was legitimately completed — and only then does the kernel allocate a socket. No backlog entry was ever consumed by the attacker.
sequenceDiagram
participant B as Attacker (no ACK)
participant K as Kernel with syncookies
participant L as Legitimate client
B->>K: SYN
K-->>B: SYN-ACK with encoded seq (state NOT stored)
Note over B: attacker ignores it — nothing happened on the server
L->>K: SYN
K-->>L: SYN-ACK with encoded seq (state NOT stored)
L->>K: ACK (seq+1 matches cookie)
Note over K: cookie verified → NOW allocate socket
K-->>L: ESTABLISHED
rect rgb(230, 240, 255)
Note over K: FILTERING POINT: allocation happens only after<br/>cryptographic proof of round-trip capability
end
Trade-offs
SYN cookies are not free:
- TCP options are lost — window scale, SACK, timestamps cannot be negotiated because there are no bits left in the sequence number (the Linux workaround stores a small MSS code only). Legitimate clients get degraded connections while cookies are active.
- No retransmission bookkeeping — the kernel does not remember outstanding SYN-ACKs, so behavior under packet loss differs slightly from the normal path.
- CPU cost — a hash computation per SYN instead of a table insert; negligible normally, measurable at millions of pps.
- They activate only under backlog pressure (
net.ipv4.tcp_syncookies = 1, value2= always on), so they are a fallback, not the first line.
5. Defense layering in Rampart
The correct order of defense is cheapest-first: kill the flood before the kernel ever touches its backlog.
flowchart TD
A[SYN packets arrive at NIC] --> B{XDP program:<br/>per-src-IP SYN throttle}
B -- "over N SYNs/sec from one IP<br/>→ temporary ban entry in eBPF map" --> X[XDP_DROP<br/>~50-100 ns/packet]
B --> C{Invalid flags?<br/>SYN+FIN, SYN+RST}
C -- yes --> X
C -- no --> D[Kernel stack]
D --> E{Backlog full?}
E -- "yes → SYN cookies kick in<br/>(stateless, no memory spent)" --> F[Cookie-verified connections proceed]
E -- no --> G[Normal handshake]
F --> H[Userspace engine:<br/>rate limit, PoW challenge]
G --> H
style X fill:#f5d0d0
style F fill:#d0e8d0
Layer responsibilities:
- XDP SYN throttle (first line) — a token-bucket or fixed-window counter per source IP in an eBPF LRU map. More than N SYNs/sec from one address → drop in the driver, before
sk_buffallocation. This handles single-IP floods entirely and blunts distributed ones. - Kernel sysctls (second line) —
tcp_syncookies=1, enlargedtcp_max_syn_backlogandsomaxconn, reducedtcp_synack_retries. Full list and rationale: kernel-tuning.md. - Userspace engine (third line) — connections that complete the handshake face per-IP connection rate limiting and a proof-of-work challenge before any application logic runs.
Русский
1. TCP-хендшейк вкратце
Каждое TCP-соединение начинается с трёхстороннего рукопожатия:
- SYN — клиент шлёт пакет с флагом SYN и своим начальным номером последовательности (ISN).
- SYN-ACK — сервер отвечает SYN+ACK со своим ISN.
- ACK — клиент подтверждает; соединение переходит в
ESTABLISHED.
Между шагами 1 и 3 соединение является полуоткрытым (half-open): сервер уже выделил под него память в специальной очереди — SYN backlog (tcp_max_syn_backlog) — и ждёт финальный ACK. Полуоткрытое соединение живёт до исчерпания ретрансмиссий tcp_synack_retries (по умолчанию около минуты).
В этой асимметрии и вся проблема: атакующий тратит один пакет на каждое полуоткрытое соединение, сервер — память + таймер + потенциальную ретрансмиccию.
2. Как работает атака
Атакующий заваливает цель SYN-пакетами и никогда не отправляет финальный ACK (или подделывает недостижимый исходный IP — тогда SYN-ACK уходят в никуда). Backlog заполняется мёртвыми полуоткрытыми соединениями. Когда он переполнен, легитимные SYN дропаются — сервис становится недоступен.
sequenceDiagram
participant B as Атакующий / ботнет
participant K as Ядро (listening socket)
participant L as Легитимный клиент
Note over K: ёмкость backlog = tcp_max_syn_backlog
B->>K: SYN #1 (никогда не завершится)
K-->>B: SYN-ACK (ретранслирует до tcp_synack_retries раз)
B->>K: SYN #2..#N (ACK не будет никогда)
K-->>B: SYN-ACK...
Note over K: backlog забит полуоткрытыми записями,<br/>таймеры ретрансляций жгут CPU
L->>K: SYN (легитимный)
K--)L: дроп — переполнение backlog<br/>ТОЧКА ФИЛЬТРАЦИИ: именно этого надо не допустить
Важный момент: жертва не видит никакой ошибки — соединения просто отваливаются по таймауту. Со стороны пользователя порт «лежит», хотя машина может быть почти простаивает.
3. Варианты атаки
| Вариант | Адреса источника | Сложность | Почему работает |
|---|---|---|---|
| Флад с одного IP | Один реальный IP | Тривиально | Работает только против хостов без per-source лимитов; один IP всё равно открывает десятки тысяч полусокетов |
| Распределённый (ботнет) | Много реальных IP | Низко | Каждый бот держит несколько сотен полуоткрытых соединений; per-IP лимиты размываются по тысячам источников |
| Поддельные source IP | Случайные / недостижимые IP | Средне | SYN-ACK уходит третьей стороне; атакующий платит 1 пакет за соединение, сервер — памятью и ретрансляциями; per-IP лимитирование бесполезно, ведь каждый «источник» появляется один раз |
Спуфинг-вариант самый опасный: ограничение по source IP бессмысленно, когда каждый пакет приходит с нового поддельного адреса.
4. SYN cookies: stateless-хендшейк
SYN cookies выносят состояние соединения из памяти сервера прямо в сеть. Вместо выделения записи в backlog сервер кодирует всю нужную информацию в номере последовательности собственного SYN-ACK:
SYN-ACK seq = hash(secret, src_ip, src_port, dst_ip, dst_port) ← старшие 24 бита
+ timestamp mod 2^6 ← средние 5 бит (ротация)
+ кодировка MSS ← младшие 3 бита
Когда приходит финальный ACK, сервер пересчитывает ожидаемое значение из номера ACK. Если совпало — соединение завершено легитимно, и только тогда ядро выделяет сокет. Атакующий не израсходовал ни одной записи backlog.
sequenceDiagram
participant B as Атакующий (без ACK)
participant K as Ядро с syncookies
participant L as Легитимный клиент
B->>K: SYN
K-->>B: SYN-ACK с закодированным seq (состояние НЕ хранится)
Note over B: атакующий игнорирует — на сервере ничего не произошло
L->>K: SYN
K-->>L: SYN-ACK с закодированным seq (состояние НЕ хранится)
L->>K: ACK (seq+1 совпадает с cookie)
Note over K: cookie верифицирован → ТОЛЬКО СЕЙЧАС выделяем сокет
K-->>L: ESTABLISHED
rect rgb(230, 240, 255)
Note over K: ТОЧКА ФИЛЬТРАЦИИ: аллокация происходит только после<br/>криптографического доказательства способности к round-trip
end
Trade-offs
SYN cookies не бесплатны:
- Потеря TCP-опций — window scale, SACK, timestamps согласовать нельзя: в номере последовательности нет свободных бит (в Linux сохраняется только небольшой код MSS). Легитимные клиенты получают ухудшенные соединения, пока cookies активны.
- Нет учёта ретрансляций — ядро не помнит отправленные SYN-ACK, поэтому поведение при потерях немного отличается от нормального пути.
- Стоимость CPU — хеш на каждый SYN вместо вставки в таблицу; обычно незаметно, но измеримо при миллионах pps.
- Cookies включаются только при давлении на backlog (
net.ipv4.tcp_syncookies = 1, значение2= всегда) — это резервный механизм, а не первая линия обороны.
5. Эшелонированная защита в Rampart
Правильный порядок защиты — от дешёвого к дорогому: убить флад до того, как ядро вообще тронет свой backlog.
flowchart TD
A[SYN-пакеты приходят на NIC] --> B{XDP-программа:<br/>per-src-IP SYN throttle}
B -- "больше N SYN/сек с одного IP<br/>→ временный бан в eBPF map" --> X[XDP_DROP<br/>~50-100 нс/пакет]
B --> C{Невалидные флаги?<br/>SYN+FIN, SYN+RST}
C -- да --> X
C -- нет --> D[Стек ядра]
D --> E{Backlog переполнен?}
E -- "да → включаются SYN cookies<br/>(stateless, память не тратится)" --> F[Соединения с верным cookie проходят дальше]
E -- нет --> G[Обычный хендшейк]
F --> H[Userspace-движок:<br/>rate limit, PoW-challenge]
G --> H
style X fill:#f5d0d0
style F fill:#d0e8d0
Ответственность слоёв:
- XDP SYN throttle (первая линия) — token bucket или fixed-window счётчик на source IP в eBPF LRU map. Больше N SYN/сек с одного адреса → дроп в драйвере, до выделения
sk_buff. Это полностью закрывает одно-IP флуды и ослабляет распределённые. - Sysctls ядра (вторая линия) —
tcp_syncookies=1, увеличенныеtcp_max_syn_backlogиsomaxconn, сниженныйtcp_synack_retries. Полный список и обоснование: kernel-tuning.md. - Userspace-движок (третья линия) — соединения, прошедшие хендшейк, упираются в per-IP rate limit и proof-of-work challenge до запуска любой прикладной логики.