# SYN Flood: Anatomy of the Classic TCP Attack > Knowledge Base · Rampart attack fundamentals · Related: [kernel-tuning.md](../practice/kernel-tuning.md), [defense-levels.md](../defense-levels.md) ## English ## 1. The TCP handshake, briefly Every TCP connection starts with a three-way handshake: 1. **SYN** — the client sends a packet with the SYN flag and its initial sequence number (ISN). 2. **SYN-ACK** — the server replies with SYN+ACK and its own ISN. 3. **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. ```mermaid 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
retransmit timers burning CPU L->>K: SYN (legitimate) K--)L: dropped — backlog overflow
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. ```mermaid 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
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`, value `2` = 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. ```mermaid flowchart TD A[SYN packets arrive at NIC] --> B{XDP program:
per-src-IP SYN throttle} B -- "over N SYNs/sec from one IP
→ temporary ban entry in eBPF map" --> X[XDP_DROP
~50-100 ns/packet] B --> C{Invalid flags?
SYN+FIN, SYN+RST} C -- yes --> X C -- no --> D[Kernel stack] D --> E{Backlog full?} E -- "yes → SYN cookies kick in
(stateless, no memory spent)" --> F[Cookie-verified connections proceed] E -- no --> G[Normal handshake] F --> H[Userspace engine:
rate limit, PoW challenge] G --> H style X fill:#f5d0d0 style F fill:#d0e8d0 ``` Layer responsibilities: 1. **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_buff` allocation. This handles single-IP floods entirely and blunts distributed ones. 2. **Kernel sysctls (second line)** — `tcp_syncookies=1`, enlarged `tcp_max_syn_backlog` and `somaxconn`, reduced `tcp_synack_retries`. Full list and rationale: [kernel-tuning.md](../practice/kernel-tuning.md). 3. **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-соединение начинается с трёхстороннего рукопожатия: 1. **SYN** — клиент шлёт пакет с флагом SYN и своим начальным номером последовательности (ISN). 2. **SYN-ACK** — сервер отвечает SYN+ACK со своим ISN. 3. **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 дропаются — сервис становится недоступен. ```mermaid 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 забит полуоткрытыми записями,
таймеры ретрансляций жгут CPU L->>K: SYN (легитимный) K--)L: дроп — переполнение backlog
ТОЧКА ФИЛЬТРАЦИИ: именно этого надо не допустить ``` Важный момент: жертва не видит никакой ошибки — соединения просто отваливаются по таймауту. Со стороны пользователя порт «лежит», хотя машина может быть почти простаивает. ## 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. ```mermaid 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: ТОЧКА ФИЛЬТРАЦИИ: аллокация происходит только после
криптографического доказательства способности к 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. ```mermaid flowchart TD A[SYN-пакеты приходят на NIC] --> B{XDP-программа:
per-src-IP SYN throttle} B -- "больше N SYN/сек с одного IP
→ временный бан в eBPF map" --> X[XDP_DROP
~50-100 нс/пакет] B --> C{Невалидные флаги?
SYN+FIN, SYN+RST} C -- да --> X C -- нет --> D[Стек ядра] D --> E{Backlog переполнен?} E -- "да → включаются SYN cookies
(stateless, память не тратится)" --> F[Соединения с верным cookie проходят дальше] E -- нет --> G[Обычный хендшейк] F --> H[Userspace-движок:
rate limit, PoW-challenge] G --> H style X fill:#f5d0d0 style F fill:#d0e8d0 ``` Ответственность слоёв: 1. **XDP SYN throttle (первая линия)** — token bucket или fixed-window счётчик на source IP в eBPF LRU map. Больше N SYN/сек с одного адреса → дроп в драйвере, до выделения `sk_buff`. Это полностью закрывает одно-IP флуды и ослабляет распределённые. 2. **Sysctls ядра (вторая линия)** — `tcp_syncookies=1`, увеличенные `tcp_max_syn_backlog` и `somaxconn`, сниженный `tcp_synack_retries`. Полный список и обоснование: [kernel-tuning.md](../practice/kernel-tuning.md). 3. **Userspace-движок (третья линия)** — соединения, прошедшие хендшейк, упираются в per-IP rate limit и proof-of-work challenge до запуска любой прикладной логики.