guard/deploy/test/stress/flood.py
loki5512344 0b53ed720b
feat: VDS stress test harness (edge-only) and load-test results
- deploy/test/stress/: multi-IP masked handshake flood (flood.py), legit client
  during attack (legit.py), full orchestration (run-stress.sh), edge Dockerfile
  with socat stub backend and high/defense configs
- docs/research/load-test-report.md: v3 VDS loopback results (2026-08-04)
- README.md: performance table update (4k conn/s raw, 99.6% blocked in defense,
  legit clients RTT 2-6ms during attack)
- docs/testing.md: multi-IP masked stress-test procedure
2026-08-04 19:16:27 +02:00

151 lines
4.6 KiB
Python
Raw 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.

#!/usr/bin/env python3
"""Много-IP Minecraft stress generator, маскирующийся под обычный трафик.
Отправляет валидные MC handshake с рандомными hostname из разных source IP
(--ips-start..--ips-end должны быть назначены на eth0 контейнера).
Modes:
connect - TCP connect + close (conn/s flood)
handshake - валидный MC handshake + ждём ответ backend (маскировка под клиента)
status - status-ping handshake (state 1)
slowloris - connect + 1 байт + hold
keepalive - connect + handshake + держим соединение
"""
import argparse
import random
import socket
import struct
import threading
import time
from datetime import datetime
HOSTS = [
"play.example.com",
"mc.example.com",
"lobby.example.com",
"hub.example.com",
"survival.example.com",
"skyblock.example.com",
"bedwars.example.com",
"minigames.example.com",
"vip.example.com",
]
PROTOCOL = 767
def pack_varint(value):
buf = []
while True:
byte = value & 0x7F
value >>= 7
if value:
byte |= 0x80
buf.append(byte)
if not value:
break
return bytes(buf)
def make_handshake(state=2, host=None):
host = host or random.choice(HOSTS)
pkt = bytearray()
pkt.extend(pack_varint(0)) # packet ID 0x00
pkt.extend(pack_varint(PROTOCOL))
pkt.extend(pack_varint(len(host)))
pkt.extend(host.encode())
pkt.extend(struct.pack(">H", 25565))
pkt.extend(pack_varint(state))
return pack_varint(len(pkt)) + bytes(pkt)
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--target", default="172.30.0.2")
p.add_argument("--port", type=int, default=25565)
p.add_argument(
"--mode",
choices=["connect", "handshake", "status", "slowloris", "keepalive"],
default="handshake",
)
p.add_argument("--duration", type=int, default=30)
p.add_argument("--threads", type=int, default=100)
p.add_argument("--ips-start", type=int, default=101)
p.add_argument("--ips-end", type=int, default=200)
p.add_argument("--ips-base", default="172.30.0.")
p.add_argument("--timeout", type=float, default=5.0)
return p.parse_args()
def main():
args = parse_args()
src_ips = [f"{args.ips_base}{i}" for i in range(args.ips_start, args.ips_end + 1)]
stop = threading.Event()
stats = {"sent": 0, "lock": threading.Lock()}
def worker():
while not stop.is_set():
src = random.choice(src_ips)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
s.bind((src, 0))
s.settimeout(args.timeout)
s.connect((args.target, args.port))
if args.mode == "handshake":
s.sendall(make_handshake())
try:
s.recv(1)
except socket.timeout:
pass
elif args.mode == "status":
s.sendall(make_handshake(state=1))
try:
s.recv(1)
except socket.timeout:
pass
elif args.mode == "slowloris":
s.sendall(b"\x01")
time.sleep(30)
elif args.mode == "keepalive":
s.sendall(make_handshake())
time.sleep(30)
with stats["lock"]:
stats["sent"] += 1
s.close()
except OSError:
pass
except Exception:
pass
threads = [
threading.Thread(target=worker, daemon=True) for _ in range(args.threads)
]
for t in threads:
t.start()
t0 = time.time()
try:
while time.time() - t0 < args.duration:
time.sleep(1)
with stats["lock"]:
cur = stats["sent"]
print(
f" [{datetime.now():%H:%M:%S}] src_ips={len(src_ips)} sent={cur:8d} rate={cur / max(time.time() - t0, 1):9.1f}/s"
)
except KeyboardInterrupt:
pass
finally:
stop.set()
time.sleep(0.5)
elapsed = max(time.time() - t0, 0.001)
with stats["lock"]:
total = stats["sent"]
print(
f"DONE total={total} avg={total / elapsed:.1f}/s elapsed={elapsed:.1f}s src_ips={len(src_ips)}"
)
if __name__ == "__main__":
main()