- 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)
23 KiB
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
# 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:
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 overflowedSYN cookies sent— syncookie mode engaged (see kernel-tuning.md)
3. iperf3 — bandwidth
# 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
# 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 ...
# k6: scripted scenario with ramping virtual users
k6 run --vus 50 --duration 60s script.js
Minimal k6 script (script.js):
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
- Staging or isolated lab first. Two VMs/VDS on the same internal network, or Docker bridge networks — never production.
- Load source separate from target. Generating flood on the same box as the defense skews every number (shared CPU, shared softirq).
- Baseline before defense: measure how the unprotected service dies. Only then can you prove the defense changed anything.
- One variable at a time, minimum 3 runs, take the median; warm up ~10 s before counting.
- 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:
- 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.
- Masked L7 floods (valid protocol payloads from many IPs) are the honest adversary; simple SYN tests only validate the kernel layer.
- A tiny 2-vCPU box handled the attack at 32% CPU once per-IP limiting engaged — good early-layer filtering buys enormous headroom.
- 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
# Установка
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
Наблюдайте за эффектом во время запуска:
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 — полоса пропускания
# Серверная сторона (целевая машина)
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
# 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 ...
# k6: скриптовый сценарий с плавным числом виртуальных пользователей
k6 run --vus 50 --duration 60s script.js
Минимальный скрипт k6 (script.js):
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. Методика
Правила окружения
- Сначала staging или изолированная лаборатория. Две ВМ/VDS в одной внутренней сети или docker bridge-сети — никогда продакшен.
- Источник нагрузки отдельно от цели. Генерация флуда на той же машине, где защита, искажает все цифры (общий CPU, общий softirq).
- Базлайн до защиты: замерьте, как умирает незащищённый сервис. Только так можно доказать, что защита что-то изменила.
- По одной переменной за раз, минимум 3 прогона, берём медиану; прогрев ~10 с до начала подсчёта.
- Фиксируйте окружение: версия ядра, модель/число 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.
Выводы, переносимые на любой проект:
- Всегда гоняйте обе фазы: защита выключена (находим сырой потолок) и защита включена (доказываем долю блокировки и доступность легитимных клиентов). Одна лишь фаза B скрыла бы тот факт, что режим без лимитов вредит реальным клиентам.
- Маскированные L7-флуды (валидные полезные нагрузки протокола с множества IP) — честный противник; простые SYN-тесты проверяют только слой ядра.
- Маленькая VDS на 2 vCPU отбивала атаку при 32% CPU, как только включился per-IP лимит — хорошее раннее отсечение покупает огромный запас прочности.
- Пробный легитимный клиент должен работать на протяжении каждой фазы — это ваш детектор ложных срабатываний.