- 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)
354 lines
23 KiB
Markdown
354 lines
23 KiB
Markdown
# 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. Пробный легитимный клиент должен работать на протяжении каждой фазы — это ваш детектор ложных срабатываний.
|