Skip to content

Latest commit

 

History

History
84 lines (60 loc) · 5.46 KB

File metadata and controls

84 lines (60 loc) · 5.46 KB

Lean-guard

eBPF (XDP + TC-BPF + sock_ops) L3/L4 flood defense for self-hosted game servers. On a single machine whose public IP is directly exposed, it passes UDP game packets only from source IPs that have completed the TCP login handshake, and drops or rate-limits the rest. The goal is not survival but preserving the latency and QoS of legitimate players even while a flood is in progress. The scope is L3/L4 defense at the booter/stresser level (L7 application vulnerabilities and upstream link saturation are out of scope).

Headline results (x86 VM, 500K pps peak, n=10)

Attack Defense RTT p99 (µs) Loss (%) core-0 CPU (%)
c3-spoof nftables 9988 ± 71 87.4 100.0
c3-spoof iptables-legacy 6217 ± 66 32.3 100.0
c3-spoof lean (native XDP) 1829 ± 131 0.00 1.6
c1-spoof nftables 6113 ± 182 33.1 100.0
c1-spoof lean (native XDP) 2808 ± 149 0.79 85.5
  • Firewall p99 degrades linearly with load (+8 to +16 µs/Kpps, p≪0.001), but lean stays statistically independent of load, with a slope near 0 and R² near 0.
  • All 16 metric×attack combinations have ICC=0 (no measurement variance between cycles, which means very high reproducibility).
  • Full numbers, regressions, and figures are in results/analysis-cycles-1-10/ (FINDINGS.md, CONNTRACK_BIMODALITY.md).

Layout

src/        eBPF dataplane (the product itself): guard.c, maps.h, Makefile
scripts/    operations + measurement scripts (flat layout, lib/common.sh is the node/IP/IF SSOT)
report/     final report PDF (EN) + figures/ (fig1-4, table CSVs)
results/    analysis-cycles-1-10/ (aggregation, regression, figures) + summaries/ (per-cycle CSV)

scripts/ depends on relative-path source and REPO_ROOT computation, so the flat layout must be kept as is.

  • Operations (deployment): attach_lean.sh, detach_all.sh, lean_guard.py (userspace control), setup_victim.sh, lib/common.sh
  • Measurement (reproduction): run_sweep.sh, run_final.sh, collect_metrics.sh, verify_results.sh, summarize_results.py, make_result_figures.py, attack generators (attack_pktgen.sh, syn_flood*, pktgen_mt.sh), firewall comparison (ipt_legacy.sh, nft_rules.sh), and so on
  • Raw run payloads (hundreds of MB per cycle) are not included in this distribution.

Build (as root on the victim machine)

Kernel 6.6+ is recommended (TC uses the tcx link), cgroup v2 with CONFIG_CGROUP_BPF (needed for sock_ops), and net.core.bpf_jit_enable=1. Dependencies: clang llvm libbpf-dev linux-headers-$(uname -r) bpftool.

sudo ./scripts/setup_victim.sh        # one-time: prepare the environment (governor/core isolation/headers/jit) and build guard.o
make -C src                           # auto-detects arch (x86_64/aarch64), normal profile (10k maps)
make -C src MAP_PROFILE=high          # 100k maps. min|normal|high|max = 1k|10k|100k|1M (fixed at build time)
make -C src verify                    # (optional) dry load check, does not attach

Deployment (attach, observe, detach)

Attach all three hooks (XDP, TC, sock_ops) together. $IF is the victim NIC, check it with ip link.

sudo ./scripts/attach_lean.sh --iface eno1 --xdp-mode native   # native|generic|auto
python3 ./scripts/lean_guard.py stats        # drop/pass/blacklist/conn_cap counters
python3 ./scripts/lean_guard.py events        # ringbuf tail
python3 ./scripts/lean_guard.py dump-all      # full snapshot (JSON)
sudo ./scripts/detach_all.sh --iface eno1     # reverse order, idempotent

Runtime tuning is limited to the 8 fields of config_map plus manual map entries (changing defense logic means a rebuild, map size is set at build time, mode and iface are set at attach time).

python3 ./scripts/lean_guard.py config set max_conn_per_ip=8   # concurrent-connection cap (0=off)
python3 ./scripts/lean_guard.py config get

The config_map fields are max_tokens (100), refill_rate (50/s), violation_threshold (10), max_conn_per_ip (0), block_duration_s (300), whitelist_ttl_s (300), and login_port/game_port (25565).

Reproducing the measurements

⚠️ Attacks must only be run on an isolated network that has no external uplink. Nodes, IPs, and interfaces are resolved from the --victim label in scripts/lib/common.sh.

sudo ./scripts/run_sweep.sh --victim victim1 --hirate   # attack×defense×pps sweep
sudo ./scripts/run_final.sh                             # repeated (cycle) batch run
./scripts/verify_results.sh                             # check output consistency
python3 ./scripts/summarize_results.py                  # headline summary
python3 ./scripts/make_result_figures.py                # generate result figures (fig3/fig4 etc.)

Outputs land in results/<victim>/<defense>/<pps>/<attack>/run<n>/ (victim ∈ {victim0, victim1, victim-demo}; defense ∈ {none, ipt-legacy, nft, lean}; attack ∈ {c1, c3} × {single, spoof}). The provenance of each run is guaranteed by the commit hash in meta.json.

License and origin

Because the eBPF dataplane declares SEC("license")="GPL", this repository is distributed under GPL-2.0.

This distribution repository was curated and derived from commit c6e5e5a (2026-06-15) of the development repository pi-guard. The ADRs (decision records), the full design documents (runbook, ARCHITECTURE, and test-plan, which is the measurement SSOT), and the raw measurement data are kept in the development repository.