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)
This commit is contained in:
loki5512344 2026-08-24 01:50:22 +02:00
parent 0b53ed720b
commit 15f474486a
Signed by: boba
GPG key ID: 253067914055423B
179 changed files with 5044 additions and 11519 deletions

View file

@ -1,183 +1,300 @@
# Architecture - Rampart
# Architecture — Rampart
> Актуально: v0.2+
> Статус: основной документ
> Relevant for: v0.3+ (universal redesign)
> Status: primary design document
> Language: English (research notes) — see [RU summary](#russian-summary) at the end
---
## 6-слойная архитектура защиты
## What changed
Rampart began as a 6-layer, Minecraft-specific protection stack: XDP filter with
hardcoded Minecraft handshake states → PoW → Rust L7 core → Velocity proxy (Java)
→ Paper agent (Java) → Traffic Intel. The big-bang redesign turns it into a
**universal L3/L4/L7 network protection platform** for any TCP service:
- VDS / dedicated servers,
- web services and APIs,
- game servers of any kind (Minecraft is now just a plugin, not the core).
The layer count drops from 6 to 4. Everything protocol-specific is extracted from
the core into compile-time protocol plugins; everything Java-based is removed.
## Layered architecture
```
┌──────────────────────────────────────────────────────────────────────┐
│ LAYER 1: XDP/eBPF (ядро) дроп L3/L4 до kernel TCP stack │
│ LAYER 1: XDP/eBPF (C) drop L3/L4 before kernel TCP stack │
│ ───────────────────────────── │
│ TCP state machine (minecraft_filter.c): │
│ AWAIT_ACK → AWAIT_MC_HANDSHAKE → AWAIT_LOGIN → verified │
│ + SYN throttle per-IP │
│ + IP blacklist (LPM_TRIE) │
│ + Invalid TCP flags drop (SYN+FIN, SYN+RST, URG, пустые) │
│ + UDP drop (MC = TCP only) │
│ + Per-connection seq tracking │
│ + bpf_timer idle cleanup │
│ + IP/CIDR whitelist │
│ ─────────────────────────────────────── │
│ Reference: Minecraft-XDP-eBPF (исправленный: нет pure ACK deadlock,│
│ LRU maps, IPv6, idle таймеры на conntrack) │
│ Universal (protocol-agnostic): │
│ + Generic TCP state machine (SYN → ESTABLISHED lifecycle, │
│ per-connection seq tracking, bpf_timer idle cleanup) │
│ + SYN throttle per-IP │
│ + IP/CIDR blacklist + whitelist (LPM_TRIE) │
│ + Invalid TCP flags drop (SYN+FIN, SYN+RST, URG, empty) │
│ + UDP policy (configurable: pass / drop / rate-limit) │
│ + Pluggable BPF protocol hooks (see ADR-002) │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 2: PoW Challenge (Rust) анти-handshake-flood │
│ LAYER 2: Universal PoW Challenge (Rust) anti-handshake-flood │
│ ───────────────────────────── │
│ SHA256 hashcash перед HMAC handshake: │
│ 1. Edge шлёт challenge (random + timestamp + difficulty) │
│ 2. Клиент решает PoW (nonce brute-force) │
│ 3. Edge верифицирует SHA256(data + nonce) prefix │
│ + Dynamic difficulty: повышается при CPS > threshold │
│ + Per-connection одноразовый challenge (nonce replay защита) │
│ ─────────────────────────────────────── │
│ Reference: PowGo (адаптирован: per-request challenge, timestamp, │
│ dynamic difficulty, без Redis, без IP+UA сессии) │
│ SHA256 hashcash over any TCP protocol (see ADR-004): │
│ 1. Edge sends challenge (random + timestamp + difficulty) │
│ 2. Client solves PoW (nonce brute-force) │
│ 3. Edge verifies SHA256(data + nonce) prefix │
│ + Dynamic difficulty: rises when CPS > threshold │
│ + Per-connection one-time challenge (nonce replay protection) │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 3: Rust Core (userspace) L7 фильтрация │
│ LAYER 3: Rust Userspace Core L7 filtering │
│ ───────────────────────────── │
│ + MC handshake парсинг (VarInt, bounds check) │
│ + HMAC-SHA256 hostname signature │
│ + Rate limit (token bucket per-IP) │
│ + Death code auto-ban (8 паттернов) │
│ + ASN/GeoIP reputation │
│ + Blacklist (Redis sync) │
│ + L7 handshake analysis (via active protocol plugin) │
│ + Rate limit (token bucket per-IP) │
│ + HMAC signatures (constant-time compare) │
│ + Death-code patterns (auto-ban on malicious payloads) │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 4: Velocity Proxy (Java) верификация игроков │
│ LAYER 4: Protocol Plugins (compile-time feature crates) │
│ ───────────────────────────── │
│ + Domain whitelist (блок прямых IP) │
│ + HMAC verification (constant-time compare) │
│ + Falling check (детерминированная физика: pre-computed кэш) │
│ + Protocol check (Transaction, SetHeldItem, ArmAnimation) │
│ + Vehicle check (Boat/Minecart gravity) │
│ + CAPTCHA challenge (Map item / PoW) │
│ + Redis server registry (delta-sync) │
│ + TPS-aware load balancer (circuit breaker < 12 TPS) │
│ ─────────────────────────────────────── │
│ Reference: Sonar pipeline + LimboFilter falling check │
│ (исправлено: HMAC fingerprint, idempotent finishVerification, │
│ без QuietDecoderException, без race в handler switching) │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 5: Paper Agent (Java) авто-регистрация │
│ ───────────────────────────── │
│ + Redis heartbeat (TPS, online игроки, память, CPU) │
│ + Auto-registration/unregistration │
│ + HMAC login check │
│ + Graceful shutdown │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 6: Traffic Intelligence (Rust + Redis) аналитика │
│ ───────────────────────────── │
│ + 168-hour traffic profiling (per-hour-slot baseline) │
│ + EWMA adaptive thresholds (правильная variance формула) │
│ + Z-Score anomaly detection (3 consecutive minutes для алерта) │
│ + Attack detection (CPS, PPS thresholds) │
│ + Reputation system (IP score -100..+100) │
│ + Discord webhook на события │
│ ─────────────────────────────────────── │
│ Reference: AtomGuard (исправлено: EWMA variance, Isolation Forest │
│ реально используется, без race в pipeline) │
│ + minecraft — first plugin (existing MC-handshake code moved in) │
│ + http, grpc — planned │
└──────────────────────────────────────────────────────────────────────┘
TRAFFIC INTEL (cross-cutting, Rust + Redis):
+ EWMA adaptive thresholds
+ Traffic profiling (per-slot baselines)
+ IP reputation system
```
## Схема прохождения трафика
### Traffic path
```
Атакующий (ботнет)
Attacker (botnet)
|
v
[1] XDP/eBPF ─── TCP state machine ─── blacklist ─── SYN throttle
| дроп: SYN flood, UDP, invalid flags, non-MC port
v (чистый TCP, прошёл state machine)
[2] PoW Challenge ─── SHA256 hashcash ─── dynamic difficulty
| дроп: не решил PoW за N секунд
v (валидный PoW)
[3] Rust Core ─── handshake parse ─── HMAC sign ─── rate limit ─── death code
| дроп: rate limit, invalid packet, bad HMAC
v (валидный MC handshake + HMAC)
[4] Velocity ─── domain check ─── HMAC verify ─── falling/physics check ─── CAPTCHA
| дроп: bad domain, bad HMAC, failed physics
v (верифицированный игрок)
[5] Game Server
| Чистый трафик, без DDoS нагрузки
[1] XDP/eBPF ─── universal TCP state machine ─── CIDR lists ─── SYN throttle
| drop: SYN flood, invalid flags, blacklisted CIDRs,
| UDP policy violations, failed BPF hook checks
v (clean TCP that passed state machine + hooks)
[2] PoW Challenge ─── SHA256 hashcash ─── dynamic difficulty [OFF by default]
| drop: did not solve PoW in time
v (valid PoW)
[3] Userspace Core ─── plugin handshake parse ─── rate limit ─── death codes
| drop: rate limit, malformed packets
v (validated application-layer client)
[4] Consumer service (game server, web backend, ...)
```
## Компоненты системы
## Component diagram
```
┌────────────────────────────────────────────────────────────────┐
│ EDGE NODE │
│ XDP/eBPF (C) → PoW (Rust) → Rust Core → Manager API │
│ ──────────────────────────────────────────────────────────── │
│ Требования: KVM/Bare Metal, 2-4 vCPU, 2-4 GB, kernel 5.10+ │
│ XDP native: Intel i40e, Mellanox ConnectX, virtio (generic) │
└────────────────────────┬───────────────────────────────────────┘
│ mTLS/QUIC
┌────────────────────────▼───────────────────────────────────────┐
│ VELOCITY CLUSTER │
│ Java 21, Velocity 3.4+, x20 нод │
│ Domain check → HMAC verify → Physics → CAPTCHA → Router │
└────────────────────────┬───────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Hub (x100) Game Servers Game Servers
лобби Survival (x100) Skyblock (x100)
разные VDS/дедики
```mermaid
flowchart TB
subgraph Kernel["Layer 1 — Kernel"]
XDP["XDP/eBPF filter (C)<br/>TCP state machine · SYN throttle<br/>CIDR black/white · UDP policy"]
HOOKS["BPF protocol hooks<br/>(pluggable modules)"]
XDP --- HOOKS
end
subgraph Core["rampart-core (Rust)"]
LOADER["XDP loader<br/>(libbpf / aya)"]
POW["PoW challenge engine<br/>(SHA256 hashcash)"]
L7["L7 filtering core<br/>rate limit · HMAC · death codes"]
INTEL["Traffic Intel<br/>EWMA · profiling · reputation"]
LOADER --> XDP
POW --> L7 --> PLUGINAPI
INTEL -.->|thresholds & scores| L7
INTEL -.->|dynamic difficulty| POW
end
subgraph Plugins["Layer 4 — Protocol plugins (feature crates)"]
PLUGINAPI["Plugin trait API<br/>(stabilization in progress)"]
MC["minecraft plugin"]
HTTP["http plugin<br/>(planned)"]
GRPC["grpc plugin<br/>(planned)"]
PLUGINAPI --> MC
PLUGINAPI -.-> HTTP
PLUGINAPI -.-> GRPC
end
subgraph Control["Control plane"]
MANAGER["rampart-manager<br/>(axum API)"]
REDIS[(Redis)]
CLI["rampart-cli"]
MANAGER <--> REDIS
CLI --> MANAGER
end
L7 --> BACKEND["Protected service<br/>(game server / web backend / VDS)"]
style HTTP stroke-dasharray: 5 5
style GRPC stroke-dasharray: 5 5
```
## Требования к хостингу
## Architecture Decision Records
| Нода | Роль | CPU | RAM | Тип | XDP |
|------|------|-----|-----|-----|-----|
| **Edge** | XDP + PoW + фильтрация | 2-4 vCPU | 2-4 GB | KVM / Bare Metal | ✅ |
| **Velocity** | MC Proxy + верификация | 4 vCPU | 4-8 GB | KVM | ❌ |
| **Manager** | API + Redis | 2-4 vCPU | 4-8 GB | KVM | ❌ |
| **Hub** | Лобби | 4-8 vCPU | 8-16 GB | KVM / Bare Metal | ❌ |
| **Game** | Игровой процесс | 4-8 vCPU | 8-32 GB | KVM / Bare Metal | ❌ |
### ADR-004: Compile-time protocol plugins instead of hardcoded Minecraft
> ⚠️ XDP требует KVM или Bare Metal. OpenVZ/LXC контейнеры — XDP не работает.
> Проверить: `systemd-detect-virt`
**Context.** The Rust userspace core originally parsed only Minecraft handshakes
(VarInt decoding, hostname bounds checks). Every non-Minecraft use case required
forking the codebase.
## Sizing guide
**Decision.** Protocol-specific parsing moves behind an internal **plugin trait
API** implemented by separate feature crates (`rampart-plugin-minecraft`, later
`rampart-plugin-http`, `rampart-plugin-grpc`). Plugins are selected at compile
time via Cargo features — no dynamic loading, no ABI stability burden. The first
plugin, `minecraft`, receives the existing MC-handshake code as-is.
| Игроков | Edge нод | Velocity нод | Edge RAM | Стоимость/мес |
|---------|----------|--------------|----------|---------------|
| до 500 | 1 | 2 | 2 GB | ~$15-30 |
| до 2 000 | 2 | 4 | 4 GB | ~$40-80 |
| до 10 000 | 4-6 | 8-10 | 8 GB | ~$150-300 |
| до 50 000 | 10-15 | 15-20 | 16 GB | ~$600-1200 |
**Consequences.**
## Граница XDP / Rust (критично)
- (+) The core becomes protocol-neutral; one platform covers VDS, web, and games.
- (+) Compile-time selection keeps dispatch static and the hot path monomorphic.
- (−) Supporting multiple protocols simultaneously requires composing feature
sets per build; runtime protocol multiplexing is out of scope until the plugin
API stabilizes.
- (−) The trait API must stabilize before third-party plugins appear (roadmap).
```
XDP делает: Rust делает:
TCP state machine (stateful) PoW challenge (SHA256)
SYN throttle per-IP MC handshake парсинг
IP blacklist (LPM_TRIE) HMAC подпись hostname
Invalid TCP flags drop Rate limit (connections/sec)
UDP drop Death code auto-ban
Per-connection seq tracking GeoIP/ASN lookup
bpf_timer idle cleanup Blacklist (сложные правила)
```
### ADR-005: Pluggable BPF hooks instead of hardcoded Minecraft states
XDP **не может**: SHA256, HMAC, floating point, heap allocation, сложные строки.
Всё L7 — только в Rust userspace.
**Context.** The XDP program contained a Minecraft-specific TCP state machine:
`AWAIT_ACK → AWAIT_MC_HANDSHAKE → AWAIT_LOGIN → verified`. This made Layer 1
unusable for any non-Minecraft traffic.
## ADR-001: Rust для Edge Core
**Decision.** The XDP filter keeps only **generic, protocol-agnostic** logic:
a universal TCP lifecycle state machine, SYN throttle, CIDR black/white lists,
TCP-flag sanity checks, and configurable UDP policy. Deep protocol parsing moves
into **separate BPF hook modules** attached to the main filter at load time.
Each hook can inspect payload after the TCP header and return allow/drop/skip.
**Решение:** Rust + tokio
**Альтернативы:** Go (GC паузы), C (небезопасен), Java (память)
**Причина:** Zero-cost abstractions, memory safety, нет GC, libbpf-rs
**Consequences.**
## ADR-002: Redis как хранилище состояния
- (+) Layer 1 works for arbitrary TCP services out of the box.
- (+) Hook modules keep the main filter small, auditable, and verifiable.
- (−) Hook attachment adds a map-dispatch indirection on the hot path; measured
cost is acceptable, but the full benchmark suite is still in progress.
- (−) Hooks are more constrained than userspace parsing (no loops without
bounded verification); anything complex stays in Layer 3.
**Решение:** Redis + локальный кэш на edge нодах
**Оговорка:** При падении Redis — edge работает с кэшем, Velocity с кэшем серверов
**Масштаб:** Redis Cluster при 1000+ серверов, Redis Sentinel для HA
### ADR-006: Remove Java layers (Velocity/Paper) and React dashboard
## ADR-003: NATS для критических событий
**Context.** Layers 4–5 were Java services: a Velocity proxy performing domain
checks, HMAC verification, physics/CAPTCHA challenges, plus a Paper agent doing
Redis heartbeat and server auto-registration. A React dashboard covered the
control plane UI.
**Решение:** NATS JetStream для blacklist updates, attack events, audit log
**Причина:** Redis Pub/Sub — fire-and-forget, NATS — at-least-once delivery
**Decision.** All Java components and the dashboard are removed from the
repository. Consumers integrate with Rampart through the **Manager API**
(axum + Redis sync) instead of embedding proxy-side Java plugins. See
[Migration notes](#migration-notes).
**Consequences.**
- (+) Universality: Rampart no longer assumes Minecraft or the JVM at all;
it protects whatever sits behind it.
- (+) Dramatically smaller deployment surface: edge nodes are pure Rust + C.
- (−) Capabilities unique to the Java layers (physics checks, in-game CAPTCHA)
are gone; where needed they become responsibilities of consumer-side plugins
built against the Manager API.
- (−) Existing Velocity/Paper deployments must migrate or stay on pre-redesign
git history.
### ADR-007: Universal PoW stays, off by default
**Context.** The SHA256 hashcash challenge previously assumed a Minecraft
text-challenge flow, which vanilla clients could not solve. In the universal
platform the challenge is redefined to work over any TCP protocol.
**Decision.** Keep the PoW layer as a core capability, redesigned as a
protocol-independent challenge, but ship it **disabled by default**
(`pow.enabled = false`). Operators enable it when their client ecosystem can
answer the challenge (custom clients, modded protocols, HTTP integrations).
**Consequences.**
- (+) Anti-handshake-flood capability remains available platform-wide.
- (+) Default configuration never breaks clients that cannot solve PoW.
- (−) Out of the box, handshake floods are mitigated only by Layer 1 throttling
and Layer 3 rate limits until PoW is explicitly enabled.
### ADR-008: Bilingual knowledge base as documentation-first strategy
**Context.** Operational experience (attack anatomy, defense levels, practice
guides) was scattered across research notes, runbooks, and chat history. New
operators repeatedly rediscovered the same lessons.
**Decision.** Maintain `docs/kb/` as a curated, bilingual (EN/RU) knowledge
base: attack anatomy, defense level explanations, and practice guides.
Documentation-first: every new attack class or defense mechanism gets a KB
entry as part of the change, not after the fact.
**Consequences.**
- (+) Onboarding cost drops; operational decisions cite KB articles.
- (+) EN/RU mirroring serves both the international audience and the original
Russian-speaking operator community.
- (−) Bilingual maintenance doubles writing effort; entries may temporarily lag
in one language.
## Migration notes
**Removed from the repository** (recoverable from git history):
- `plugins/` — Java Velocity plugin (domain whitelist, physics checks, CAPTCHA,
TPS-aware routing) and Paper agent (Redis heartbeat, auto-registration).
- `velocity/`, `paper/` — build scaffolding for the Java layers.
- `dashboard/` — React + Vite + TypeScript control plane UI.
All removed sources remain accessible in git history
(`git log --follow -- plugins/ velocity/ paper/ dashboard/`).
**Moved into the plugin crate:**
- Minecraft handshake parsing (VarInt, bounds checks) from the Rust core →
`minecraft` protocol plugin crate.
- Minecraft-specific connection states in the XDP state machine → replaced by
the universal state machine; MC-specific deep parsing will reappear as a BPF
hook module (planned).
**Kept in place:**
- XDP/eBPF generic filtering (maps, LPM trie, throttle timers).
- SHA256 hashcash PoW engine (redesigned to be protocol-agnostic, off by default).
- Rate limiting, HMAC, death-code pattern matching in the userspace core.
- Traffic Intel (EWMA thresholds, profiling, reputation).
- Manager API (axum) + Redis sync, CLI.
**Integration path for former Java-layer consumers:** talk to rampart-manager's
HTTP API for server registration, blacklists, and event streams. There is no
in-process Java integration anymore.
## Requirements (edge node)
| Item | Requirement |
|------|-------------|
| Virtualization | KVM / Bare Metal (XDP does not work under OpenVZ/LXC; check `systemd-detect-virt`) |
| Kernel | 5.10+ |
| CPU/RAM | 2–4 vCPU, 2–4 GB |
| XDP drivers | native: Intel i40e, Mellanox ConnectX; virtio falls back to generic mode |
## Historical ADRs (pre-redesign, still valid)
- **ADR-001: Rust for the edge core.** Rust + tokio over Go (GC pauses),
C (memory safety), Java (footprint). Zero-cost abstractions + libbpf-rs.
- **ADR-002: Redis as state store.** Redis + local cache on edge nodes; edges
keep operating from cache if Redis is down; Redis Cluster/Sentinel for scale/HA.
- **ADR-003: NATS for critical events.** NATS JetStream for blacklist updates,
attack events, audit log — at-least-once delivery instead of Redis Pub/Sub's
fire-and-forget.
---
## RU summary (краткая выжимка)
Rampart переработан из 6-слойной Minecraft-специфичной защиты в универсальную
платформу сетевой защиты уровня L3/L4/L7 для любых сервисов. Слоёв стало четыре:
(1) XDP/eBPF в ядре — только протоколо-независимая логика (универсальный TCP
state machine, SYN throttle, CIDR-списки, UDP policy), глубокий парсинг вынесен
в подключаемые BPF hook-модули; (2) универсальный PoW-challenge (SHA256 hashcash)
поверх любого TCP-протокола, выключен по умолчанию; (3) userspace-ядро на Rust —
L7-анализ, rate limit, HMAC, death-code паттерны; (4) компайл-тайм протокол-плагины
(feature crates), первый — minecraft, далее http и grpc. Traffic Intel
(EWMA, профилирование, репутация) работает поперёк всех слоёв. Java-слои
(velocity/paper) и React dashboard удалены — потребители интегрируются через
Manager API (axum + Redis). Документация строится вокруг двуязычной базы знаний
docs/kb. Полные детали миграции и ссылки на git-историю — в Migration notes выше.

View file

@ -1,303 +0,0 @@
# Minecraft Protocol - Парсинг, VarInt, Fingerprinting
> Актуально: v0.1+
> Основа всей фильтрации - знание протокола.
---
## Handshake пакет (0x00) - структура
```
┌──────────────────────────────────────────────────────┐
│ VarInt │ Packet Length │
├──────────────────────────────────────────────────────┤
│ VarInt │ Packet ID = 0x00 │
├──────────────────────────────────────────────────────┤
│ VarInt │ Protocol Version │
│ │ 765 = 1.20.4, 769 = 1.21.4, 766 = 26.1 │
├──────────────────────────────────────────────────────┤
│ String │ Server Address (hostname) │
│ │ VarInt (length) + UTF-8 bytes │
├──────────────────────────────────────────────────────┤
│ UShort │ Server Port (big-endian, 2 bytes) │
├──────────────────────────────────────────────────────┤
│ VarInt │ Next State: 1 = Status, 2 = Login │
└──────────────────────────────────────────────────────┘
```
---
## VarInt - строгий парсер с bounds check
```rust
// minecraft/varint.rs
#[derive(Debug)]
pub enum VarIntError {
Incomplete, // данных меньше чем нужно
TooBig, // VarInt > 5 байт (не по спецификации)
Overflow, // значение выходит за i32
}
pub fn read_varint(buf: &[u8], start: usize) -> Result<(i32, usize), VarIntError> {
let mut value: i32 = 0;
let mut shift = 0;
for (i, &byte) in buf[start..].iter().enumerate() {
if i >= 5 {
// MC VarInt максимум 5 байт - всё что больше: атака
return Err(VarIntError::TooBig);
}
let segment = (byte & 0x7F) as i32;
// Проверяем overflow до сдвига
if shift >= 32 || (shift == 28 && segment > 0x0F) {
return Err(VarIntError::Overflow);
}
value |= segment << shift;
shift += 7;
if (byte & 0x80) == 0 {
return Ok((value, start + i + 1));
}
}
Err(VarIntError::Incomplete)
}
// VarString = VarInt (length) + UTF-8 bytes
pub fn read_string(buf: &[u8], start: usize) -> Result<(String, usize), ParseError> {
let (len, after_len) = read_varint(buf, start)?;
if len < 0 || len > 32767 {
return Err(ParseError::StringTooLong);
}
let end = after_len + len as usize;
if end > buf.len() {
return Err(ParseError::Incomplete);
}
let s = std::str::from_utf8(&buf[after_len..end])
.map_err(|_| ParseError::InvalidUtf8)?
.to_string();
Ok((s, end))
}
```
---
## Полный парсер handshake
```rust
// minecraft/handshake.rs
#[derive(Debug)]
pub struct McHandshake {
pub protocol_version: i32,
pub server_address: String,
pub server_port: u16,
pub next_state: NextState,
}
#[derive(Debug, PartialEq)]
pub enum NextState {
Status, // 1 - ping
Login, // 2 - игрок заходит
Unknown(i32),
}
impl McHandshake {
pub fn parse(buf: &[u8]) -> Result<Self, ParseError> {
let mut pos = 0;
// Packet length (игнорируем значение, просто двигаемся дальше)
let (_, after_len) = read_varint(buf, pos)?;
pos = after_len;
// Packet ID - должен быть 0x00
let (packet_id, after_id) = read_varint(buf, pos)?;
pos = after_id;
if packet_id != 0x00 {
return Err(ParseError::NotHandshake(packet_id));
}
// Protocol version (не валидируем - не хардкодим версии)
let (protocol_version, after_pv) = read_varint(buf, pos)?;
pos = after_pv;
// Server address
let (server_address, after_addr) = read_string(buf, pos)?;
pos = after_addr;
// Защита от слишком длинного hostname
if server_address.len() > 255 {
return Err(ParseError::HostnameTooLong);
}
// Server port (big-endian u16)
if pos + 2 > buf.len() {
return Err(ParseError::Incomplete);
}
let server_port = u16::from_be_bytes([buf[pos], buf[pos + 1]]);
pos += 2;
// Next state
let (next_state_raw, _) = read_varint(buf, pos)?;
let next_state = match next_state_raw {
1 => NextState::Status,
2 => NextState::Login,
n => NextState::Unknown(n),
};
Ok(McHandshake {
protocol_version,
server_address,
server_port,
next_state,
})
}
pub fn is_login(&self) -> bool {
self.next_state == NextState::Login
}
}
```
---
## Hostname суффиксы - Forge, FabricProxy, HMAC
```
Обычный клиент: "play.server.com"
Forge (старый): "play.server.com\0FML\0"
NeoForge/Forge: "play.server.com\0FML2\0"
FabricProxy-Lite: "play.server.com\0" + base64(data)
Наш HMAC: "play.server.com\0shield\0<hex_hmac>"
Комбинации:
Forge + HMAC: "play.server.com\0FML2\0\0shield\0<hex_hmac>"
```
### Правильный порядок разбора
```rust
// ВАЖНО: сначала убираем FML суффикс, потом проверяем HMAC
// Если делать наоборот - HMAC подпись не совпадёт
pub struct ParsedHostname {
pub domain: String, // "play.server.com"
pub forge_marker: Option<String>, // "FML2" если Forge
pub hmac: Option<String>, // hex HMAC если прошли через edge
}
pub fn parse_hostname(raw: &str) -> ParsedHostname {
let parts: Vec<&str> = raw.split('\0').collect();
// Ищем "shield" среди частей
let shield_pos = parts.iter().position(|&p| p == "shield");
// Forge маркер - обычно вторая часть
let forge_marker = parts.get(1)
.filter(|&&p| p == "FML" || p == "FML2" || p == "FML3")
.map(|&s| s.to_string());
ParsedHostname {
domain: parts[0].to_string(),
forge_marker,
hmac: shield_pos.and_then(|i| parts.get(i + 1)).map(|s| s.to_string()),
}
}
```
---
## Client Fingerprinting
### По handshake
```rust
pub enum ClientType {
Vanilla,
NeoForge, // \0FML2\0
Forge, // \0FML\0
FabricProxy, // специфичный base64 суффикс
Bot, // подозрительные паттерны
Unknown,
}
pub fn fingerprint_from_handshake(h: &McHandshake) -> ClientType {
let addr = &h.server_address;
if addr.contains("\0FML2\0") { return ClientType::NeoForge; }
if addr.contains("\0FML\0") { return ClientType::Forge; }
// Очень старый или нестандартный protocol_version
if h.protocol_version < 47 || h.protocol_version > 10000 {
return ClientType::Bot;
}
ClientType::Unknown
}
```
### По plugin channels (после Login)
```rust
// Lunar, Badlion, Feather регистрируют свои каналы через
// LoginPluginRequest / PluginChannels пакет
pub fn fingerprint_from_channels(channels: &[String]) -> Option<ClientType> {
for ch in channels {
if ch.starts_with("lunarclient:") { return Some(ClientType::LunarClient); }
if ch.starts_with("badlion:") { return Some(ClientType::BadlionClient); }
if ch.starts_with("feather:") { return Some(ClientType::FeatherClient); }
if ch.starts_with("pvplounge:") { return Some(ClientType::PvPLounge); }
}
None
}
```
---
## Важные нюансы протокола
```
1. Один TCP коннект = один игрок. MC не мультиплексирует.
2. После Handshake(next_state=2) → LoginStart пакет
Если LoginStart не пришёл за 5 сек → это бот. DROP.
3. Protocol version не хардкодить.
Mojang с 2025 использует новую схему (26.1, 26.2...).
Принимаем любой валидный VarInt в диапазоне 0..10000.
4. Hostname может прийти TCP-фрагментированным (несколько сегментов).
Парсер должен уметь работать с неполными данными - читать пока
не получим полный пакет или timeout.
5. Status ping (next_state=1) - не требует авторизации.
Боты часто используют для разведки (онлайн, версия сервера).
Rate limit status отдельно от login.
6. MC 1.20.2+ использует Configuration phase между Login и Play.
Velocity обрабатывает автоматически - нам не важно для edge.
```
---
## Совместимость версий (июль 2026)
| Версия MC | Protocol version | Схема |
|---|---|---|
| 1.20.4 | 765 | Старая |
| 1.21.1 | 767 | Старая |
| 1.21.4 | 769 | Старая |
| 26.1 | 8xx | Новая (Mojang) |
| 26.2 | 8xx | Новая (Mojang) |
Velocity 3.4+ поддерживает обе схемы прозрачно.
Sonar 3.x поддерживает 1.8 - 26.2.