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

23 KiB
Raw Permalink Blame History

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 overflowed
  • SYN 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

  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

# Установка
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. Методика

Правила окружения

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