guard/docs/kb/practice/stress-testing.md
loki5512344 15f474486a
feat!: universal redesign — drop Minecraft stack, single-crate architecture
- 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)
2026-08-24 01:50:22 +02:00

354 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Stress Testing Your Own Infrastructure
> Knowledge Base · Practice
>
> A defense system that has never been attacked is a hypothesis, not a defense. This article covers the standard load-generation tools (hping3, iperf3, wrk/k6), a safe testing methodology, and the metrics that actually tell you whether your protection works. It ends with a real case study from this project.
## English
## 0. Ethics and legality — read first
- **Test only infrastructure you own or have explicit written permission to test.** Launching flood traffic against third-party systems is a crime in most jurisdictions (unauthorized impairment of computer systems), regardless of intent or duration.
- **Never test from cloud providers against targets outside their network without authorization** — most providers prohibit it in ToS and will terminate your account.
- **Use staging environments and loopback/bridge networks.** Everything in this article can be done on a single machine or a private two-node lab.
- Coordinate timing with your team: an unplanned "test" against production is indistinguishable from an outage caused by an attack.
- If you want to study attack traffic safely, generate it yourself against your own stub services — which is exactly what the tools below do.
## 1. The tools and what each one measures
| Tool | Attack/load type | Layer | What you learn |
|---|---|---|---|
| **hping3** | SYN flood, UDP flood, ICMP flood, flag-abuse | L3/L4 | How kernel + early filters behave under packet floods |
| **iperf3** | Bulk TCP/UDP throughput | L4 | Bandwidth ceiling of NIC/tunnel/kernel path |
| **wrk** | HTTP keep-alive requests | L7 | Request rate and latency of an HTTP endpoint under sustained load |
| **k6** | Scripted HTTP/API scenarios (JS) | L7 | Realistic user flows, thresholds, gradual ramp-up |
| **tcpkali** | Raw TCP connections/sec | L4/L7 | Connection-rate limits of a custom TCP service |
## 2. hping3 — L3/L4 floods
```bash
# Install
sudo apt install hping3
# SYN flood at full speed toward YOUR OWN server
# (run from a second machine or VM, not the target itself)
sudo hping3 -S --flood -p 443 TARGET_IP
# SYN flood with randomized source addresses — exercises
# anti-spoofing, syncookies, per-prefix throttling
sudo hping3 -S --flood -p 443 --rand-source TARGET_IP
# UDP flood (bandwidth-style)
sudo hping3 --udp --flood -p 53 TARGET_IP
# Invalid flag combination (SYN+FIN) — should be dropped by any sane filter
sudo hping3 -S -F -p 443 TARGET_IP
# Slower, controlled rate instead of --flood (packets per second)
sudo hping3 -S -p 443 --interval u1000 TARGET_IP # ~1000 pps
```
Observe the effect while it runs:
```bash
watch -n1 'ss -s' # socket summary: timewait/synrecv counts
watch -n1 'cat /proc/net/netstat | grep -A1 TcpExt:' # SYN cookies sent, etc.
nstat -az | grep -i syn # SynCookiesSent, ListenDrops counters
ip -s link # interface-level drops
```
`netstat -s` / `nstat` output worth knowing:
- `SYNs to LISTEN sockets dropped` — accept/SYN backlog overflowed
- `SYN cookies sent` — syncookie mode engaged (see kernel-tuning.md)
## 3. iperf3 — bandwidth
```bash
# Server side (target machine)
iperf3 -s
# Client side: TCP upload for 30 seconds
iperf3 -c TARGET_IP -t 30
# Parallel streams to saturate path
iperf3 -c TARGET_IP -t 30 -P 8
# UDP at fixed rate (e.g., 500 Mbit) with loss/jitter report
iperf3 -c TARGET_IP -u -b 500M -t 30
# Reverse direction (server sends to client)
iperf3 -c TARGET_IP -t 30 -R
```
What to look for: achieved throughput vs link capacity, UDP `lost/datagrams` ratio (packet loss under load), retransmits on TCP runs (`sender retransmits`). If a "protected" path shows far lower throughput than the bare one, your filter may be over-dropping legitimate bulk traffic.
## 4. wrk / k6 — L7 load
```bash
# wrk: 200 connections, 8 threads, 30 seconds against your own endpoint
wrk -t8 -c200 -d30s http://TARGET_IP/
# With a latency distribution summary
wrk -t8 -c200 -d30s --latency http://TARGET_IP/
# Custom request via Lua script file (POST example): wrk -s script.lua ...
```
```bash
# k6: scripted scenario with ramping virtual users
k6 run --vus 50 --duration 60s script.js
```
Minimal k6 script (`script.js`):
```javascript
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 100 }, // ramp to 100 VUs
{ duration: '60s', target: 100 },
{ duration: '30s', target: 0 }, // ramp down
],
};
export default function () {
const res = http.get('http://TARGET_IP/');
check(res, { 'status is 200': (r) => r.status === 200 });
}
```
L7 generators answer a different question than hping3: not "does the pipe survive" but "does the application logic stay correct and fast when many clients hammer valid-looking requests".
## 5. Methodology
### Environment rules
1. **Staging or isolated lab first.** Two VMs/VDS on the same internal network, or Docker bridge networks — never production.
2. **Load source separate from target.** Generating flood on the same box as the defense skews every number (shared CPU, shared softirq).
3. **Baseline before defense**: measure how the unprotected service dies. Only then can you prove the defense changed anything.
4. **One variable at a time**, minimum 3 runs, take the median; warm up ~10 s before counting.
5. **Record the environment**: kernel version, CPU model/count, RAM, NIC type (virtio vs physical), queue settings from nic-tuning.md.
### Metrics that matter
| Metric | Command / source | Healthy signal |
|---|---|---|
| Packet rate (pps) | `sar -n DEV 1`, `ethtool -S` | Matches generator's intended rate |
| Bitrate | `sar -n DEV 1`, `iftop` | Within link budget |
| CPU softirq % | `mpstat -P ALL 1` (`%soft`) | Spread across cores, not pegged on one |
| Interface drops | `ip -s link` | Grows during attack *only* if that's your drop policy |
| Socket states | `ss -s` | No runaway TIME_WAIT/SYN-RECV accumulation |
| Kernel drop reasons | `nstat -az`, `dropwatch` | Drops attributable to intended filters |
| App-level allowed/blocked | Your service metrics (e.g., Prometheus `/metrics`) | Block ratio rises under attack; legit clients still pass |
| Legit-client latency | Separate probe client measuring RTT | Stays within SLO during the attack |
The last two rows are the crucial ones: a defense that blocks everything including real users has failed.
### Interpreting results
- **Blocked ratio high + legit RTT stable** → defense works.
- **Everything blocked** → filter too aggressive (check thresholds).
- **Nothing blocked, CPU melts** → attack traffic bypasses your layers; check whether it reaches userspace at all (`mpstat`, XDP drop counters).
- **Generator saturates first** → your numbers measure the generator, not the target. Scale out sources or lower rates.
## 6. Case study: edge-only loopback test on a small VDS (2026-08-04)
Full report: `docs/research/load-test-report.md`; scripts: `deploy/test/stress/`.
Setup: single 2 vCPU / 3.8 GB VDS, Ubuntu 22.04, Docker bridge `172.30.0.0/24`. One container ran the Rampart edge process plus a stub echo backend; another container acted as attacker holding 100 additional source IPs (`172.30.0.101–200`) and generating handshake-shaped L7 floods indistinguishable from real clients at the protocol level. XDP was disabled and PoW disabled — deliberately, so the test measured pure L7 rate limiting + reputation.
Phases:
- **Phase A (limits off)**: raw throughput measurement — ~121.5k handshakes in 30 s (~4.0k conn/s), all proxied; edge CPU peaked ~179% (both cores). Without limits, the same flood also throttled legitimate probe clients (2 of 5 succeeded).
- **Phase B (default per-IP limit 5 pps/IP, burst 10, reputation ban)**: identical flood — **119,376 blocked vs 528 allowed ≈ 99.6% blocked** at max ~32% CPU. All 5 legitimate probe clients passed during the attack with RTT 2.2–5.8 ms; abusive IPs got blacklisted after repeated violations.
- **Phase C (hping3 SYN flood, `--rand-source`)**: no impact on the L7 edge — without XDP, raw SYN handling belongs entirely to the kernel (syncookies/backlog). Confirms the layer separation discussed in defense-levels.md.
- **Phase D (300 kept-open connections)**: all proxied, negligible CPU — steady-state connection holding was limited by the backend stub, not the edge.
Lessons transferable to any project:
1. Always run both phases: **defense off** (find raw ceiling) and **defense on** (prove block ratio + legit availability). Phase B alone would hide the fact that unlimited mode harms real clients.
2. Masked L7 floods (valid protocol payloads from many IPs) are the honest adversary; simple SYN tests only validate the kernel layer.
3. A tiny 2-vCPU box handled the attack at 32% CPU once per-IP limiting engaged — good early-layer filtering buys enormous headroom.
4. Keep a legit probe client running throughout every phase; it is your false-positive alarm.
---
---
## Русский
# Стресс-тестирование собственной инфраструктуры
> Knowledge Base · Практика
>
> Система защиты, которую никогда не атаковали, — это гипотеза, а не защита. Статья охватывает стандартные инструменты генерации нагрузки (hping3, iperf3, wrk/k6), безопасную методику тестирования и метрики, которые реально показывают, работает ли защита. В конце — реальный кейс этого проекта.
## 0. Этика и законность — прочтите сначала
- **Тестируйте только инфраструктуру, которой владеете или на тестирование которой есть явное письменное разрешение.** Запуск флуд-трафика в чужие системы — уголовное преступление в большинстве юрисдикций (несанкционированное нарушение работы компьютерных систем), независимо от намерений и длительности.
- **Никогда не тестируйте из облачных провайдеров против целей вне их сети без разрешения** — большинство провайдеров запрещают это в ToS и блокируют аккаунт.
- **Используйте staging и loopback/bridge-сети.** Всё описанное ниже выполняется на одной машине или в приватной лаборатории из двух узлов.
- Согласуйте время с командой: незапланированный «тест» продакшена неотличим от аварии, вызванной атакой.
- Если хотите безопасно изучать атакующий трафик — генерируйте его сами против собственных заглушек; ровно это и делают инструменты ниже.
## 1. Инструменты и что каждый измеряет
| Инструмент | Тип атаки/нагрузки | Уровень | Что вы узнаёте |
|---|---|---|---|
| **hping3** | SYN flood, UDP flood, ICMP flood, злоупотребление флагами | L3/L4 | Как ведут себя ядро и ранние фильтры под пакетным флудом |
| **iperf3** | Объёмная пропускная способность TCP/UDP | L4 | Потолок пропускной способности пути NIC/туннель/ядро |
| **wrk** | HTTP-запросы поверх keep-alive | L7 | Частота запросов и задержки HTTP-эндпоинта под устойчивой нагрузкой |
| **k6** | Скриптовые HTTP/API-сценарии (JS) | L7 | Реалистичные пользовательские сценарии, пороги, плавный разгон |
| **tcpkali** | Сырые TCP-соединения/сек | L4/L7 | Предел частоты соединений кастомного TCP-сервиса |
## 2. hping3 — флуды L3/L4
```bash
# Установка
sudo apt install hping3
# SYN flood на полной скорости в СВОЙ сервер
# (запускать со второй машины или ВМ, не с самой цели)
sudo hping3 -S --flood -p 443 TARGET_IP
# SYN flood со случайными адресами источника — проверяет
# анти-спуфинг, syncookies, троттлинг по префиксам
sudo hping3 -S --flood -p 443 --rand-source TARGET_IP
# UDP flood (полосно-затратный)
sudo hping3 --udp --flood -p 53 TARGET_IP
# Неверная комбинация флагов (SYN+FIN) — любой адекватный фильтр должен дропнуть
sudo hping3 -S -F -p 443 TARGET_IP
# Медленнее, управляемая скорость вместо --flood (пакетов в секунду)
sudo hping3 -S -p 443 --interval u1000 TARGET_IP # ~1000 pps
```
Наблюдайте за эффектом во время запуска:
```bash
watch -n1 'ss -s' # сводка сокетов: счётчики timewait/synrecv
watch -n1 'cat /proc/net/netstat | grep -A1 TcpExt:' # отправленные SYN cookies и пр.
nstat -az | grep -i syn # счётчики SynCookiesSent, ListenDrops
ip -s link # дропы на уровне интерфейса
```
Полезные строки вывода `netstat -s` / `nstat`:
- `SYNs to LISTEN sockets dropped` — переполнение accept/SYN backlog'а
- `SYN cookies sent` — включился режим syncookies (см. kernel-tuning.md)
## 3. iperf3 — полоса пропускания
```bash
# Серверная сторона (целевая машина)
iperf3 -s
# Клиентская сторона: TCP-загрузка 30 секунд
iperf3 -c TARGET_IP -t 30
# Параллельные потоки для насыщения канала
iperf3 -c TARGET_IP -t 30 -P 8
# UDP на фиксированной скорости (например, 500 Мбит) с отчётом о потерях/джиттере
iperf3 -c TARGET_IP -u -b 500M -t 30
# Обратное направление (сервер шлёт клиенту)
iperf3 -c TARGET_IP -t 30 -R
```
На что смотреть: достигнутая полоса против ёмкости линка, отношение `lost/datagrams` по UDP (потери под нагрузкой), ретрансмиты в TCP-прогонах (`sender retransmits`). Если «защищённый» путь показывает заметно меньшую полосу, чем голый, ваш фильтр может перерезать и легитимный объёмный трафик.
## 4. wrk / k6 — нагрузка L7
```bash
# wrk: 200 соединений, 8 потоков, 30 секунд против своего эндпоинта
wrk -t8 -c200 -d30s http://TARGET_IP/
# С разбивкой задержек
wrk -t8 -c200 -d30s --latency http://TARGET_IP/
# Кастомный запрос через Lua-скрипт (пример POST): wrk -s script.lua ...
```
```bash
# k6: скриптовый сценарий с плавным числом виртуальных пользователей
k6 run --vus 50 --duration 60s script.js
```
Минимальный скрипт k6 (`script.js`):
```javascript
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 100 }, // разгон до 100 VU
{ duration: '60s', target: 100 },
{ duration: '30s', target: 0 }, // спуск
],
};
export default function () {
const res = http.get('http://TARGET_IP/');
check(res, { 'status is 200': (r) => r.status === 200 });
}
```
L7-генераторы отвечают на другой вопрос, нежели hping3: не «выживет ли труба», а «остаётся ли логика приложения корректной и быстрой, когда множество клиентов долбят похожими на настоящие запросами».
## 5. Методика
### Правила окружения
1. **Сначала staging или изолированная лаборатория.** Две ВМ/VDS в одной внутренней сети или docker bridge-сети — никогда продакшен.
2. **Источник нагрузки отдельно от цели.** Генерация флуда на той же машине, где защита, искажает все цифры (общий CPU, общий softirq).
3. **Базлайн до защиты**: замерьте, как умирает незащищённый сервис. Только так можно доказать, что защита что-то изменила.
4. **По одной переменной за раз**, минимум 3 прогона, берём медиану; прогрев ~10 с до начала подсчёта.
5. **Фиксируйте окружение**: версия ядра, модель/число CPU, RAM, тип NIC (virtio против физического), настройки очередей из nic-tuning.md.
### Метрики, которые имеют значение
| Метрика | Команда / источник | Здоровый сигнал |
|---|---|---|
| Частота пакетов (pps) | `sar -n DEV 1`, `ethtool -S` | Совпадает с задуманной скоростью генератора |
| Битрейт | `sar -n DEV 1`, `iftop` | В пределах бюджета линка |
| CPU softirq % | `mpstat -P ALL 1` (`%soft`) | Распределено по ядрам, не упирается в одно |
| Дропы интерфейса | `ip -s link` | Растут при атаке *только* если это ваша политика дропа |
| Состояния сокетов | `ss -s` | Нет неконтролируемого накопления TIME_WAIT/SYN-RECV |
| Причины дропов в ядре | `nstat -az`, `dropwatch` | Дропы объясняются задуманными фильтрами |
| Allowed/blocked на уровне приложения | Метрики сервиса (например, Prometheus `/metrics`) | Доля блокировки растёт под атакой; легитимные клиенты проходят |
| Задержка легитимного клиента | Отдельный пробный клиент, меряющий RTT | Остается в рамках SLO во время атаки |
Последние две строки — решающие: защита, блокирующая всех подряд, включая реальных пользователей, провалилась.
### Интерпретация результатов
- **Высокая доля блокировки + стабильный RTT легитимных** → защита работает.
- **Заблокировано всё** → фильтр слишком агрессивен (проверьте пороги).
- **Ничего не заблокировано, CPU плавится** → атакующий трафик обходит ваши слои; проверьте, доходит ли он вообще до userspace (`mpstat`, счётчики XDP-дропов).
- **Генератор насыщается первым** → вы меряете генератор, а не цель. Масштабируйте источники или снизьте скорость.
## 6. Кейс: edge-only тест на loopback небольшой VDS (2026-08-04)
Полный отчёт: `docs/research/load-test-report.md`; скрипты: `deploy/test/stress/`.
Окружение: одна VDS 2 vCPU / 3.8 ГБ, Ubuntu 22.04, docker bridge `172.30.0.0/24`. Один контейнер запускал процесс Rampart edge плюс stub echo-бэкенд; второй контейнер выступал атакующим с сотней дополнительных IP-источников (`172.30.0.101–200`) и генерировал маскированные под протокол L7-флуды, неотличимые от реальных клиентов на уровне протокола. XDP был выключен и PoW выключен — сознательно, чтобы тест измерял чистый L7: rate limiting + репутацию.
Фазы:
- **Фаза A (лимиты сняты)**: измерение сырой пропускной способности — ~121,5 тыс. хендшейков за 30 с (~4.0 тыс. conn/s), всё проксировано; пик CPU edge ~179% (оба ядра). Без лимитов тот же флуд «душит» и легитимных пробных клиентов (успешны 2 из 5).
- **Фаза B (дефолтный лимит 5 pps/IP, burst 10, репутационный бан)**: идентичный флуд — **119 376 заблокировано против 528 пропущенных ≈ 99,6% blocked** при пике CPU ~32%. Все 5 легитимных пробных клиентов прошли во время атаки с RTT 2.2–5.8 мс; злоупотребляющие IP уходили в блэклист после повторных нарушений.
- **Фаза C (SYN flood через hping3, `--rand-source`)**: влияния на L7 edge нет — без XDP сырая обработка SYN целиком принадлежит ядру (syncookies/backlog). Подтверждает разделение слоёв из defense-levels.md.
- **Фаза D (300 удерживаемых соединений)**: все проксированы, CPU незначителен — удержание соединений в установившемся режиме ограничивал stub-бэкенд, а не edge.
Выводы, переносимые на любой проект:
1. Всегда гоняйте обе фазы: **защита выключена** (находим сырой потолок) и **защита включена** (доказываем долю блокировки и доступность легитимных клиентов). Одна лишь фаза B скрыла бы тот факт, что режим без лимитов вредит реальным клиентам.
2. Маскированные L7-флуды (валидные полезные нагрузки протокола с множества IP) — честный противник; простые SYN-тесты проверяют только слой ядра.
3. Маленькая VDS на 2 vCPU отбивала атаку при 32% CPU, как только включился per-IP лимит — хорошее раннее отсечение покупает огромный запас прочности.
4. Пробный легитимный клиент должен работать на протяжении каждой фазы — это ваш детектор ложных срабатываний.