guard/docs/research/ebpf.md
loki5512344 269daa071f
v0.3: 6-layer architecture complete
Layers:
  Layer 1: XDP/eBPF — TCP state machine, SYN throttle, blacklist, ringbuf
  Layer 2: PoW Challenge — SHA-256 hashcash, dynamic difficulty, constant-time verify
  Layer 3: Rust Core — HMAC handshake, rate limit, death code (existing)
  Layer 4: Velocity — Physics check, CAPTCHA, protocol verification
  Layer 5: Paper — Heartbeat, auto-registration (existing)
  Layer 6: Traffic Intel — EWMA, 168h profiling, reputation, alerts

Infra: XDP→Prometheus metrics, ClickHouse + Grafana dashboard, Docker Compose
Testing: 100-IP DDoS simulation, MHDDoS ref analysis, load test report
Fixes: VarInt sign extension UB, pure ACK deadlock, RST/FIN cleanup
Ref: MHDDoS, Sonar, LimboFilter, AtomGuard, Infrarust, MC-XDP-eBPF, PowGo
2026-07-21 15:47:36 +02:00

334 lines
10 KiB
Markdown
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.

# eBPF / XDP — Rampart XDP слой
> Актуально: v0.4+
> Требует: Linux kernel 5.10+, KVM или Bare Metal (не OpenVZ/LXC)
---
## Почему XDP
```
Обычный путь пакета (без XDP):
NIC → driver → kernel TCP stack → socket buffer → userspace → решение
XDP путь:
NIC driver → XDP_DROP (ещё до kernel stack)
Никаких аллокаций, никаких копий, никаких syscall
```
| Метод | Задержка дропа | CPU на 5M pps |
|-------|---------------|---------------|
| iptables | ~10 мкс | ~80% |
| nftables | ~8 мкс | ~70% |
| Rust userspace | ~5 мкс | ~50% |
| **XDP (generic)** | ~2 мкс | ~30% |
| **XDP (native)** | ~0.5 мкс | ~15% |
| **XDP (offload)** | ~0.1 мкс | ~0% |
---
## TCP State Machine
Rampart использует stateful подход из Minecraft-XDP-eBPF, с исправлениями:
```
┌──────────┐
│ SYN │
│ received │
└────┬─────┘
│
┌────▼─────┐
┌────────►│AWAIT_ACK │◄─────────┐
│ │ (SYN-ACK │ │
│ │ sent) │ │
│ └────┬─────┘ │
│ │ ACK received │
│ ┌────▼──────────┐ │
│ │AWAIT_MC_ │ │ retransmit
│ │HANDSHAKE ├─────┘ (если pure ACK)
│ └────┬──────────┘
│ │ MC handshake
│ ┌────▼──────────┐
│ │AWAIT_LOGIN │
│ └────┬──────────┘
│ │ LoginStart
│ ┌────▼──────────┐ ┌──────────────┐
│ │ VERIFIED │────►│Idle bpf_timer│
│ └────┬──────────┘ │ (60 sec) │
│ │ └──────┬───────┘
│ ┌────▼──────────┐ │ timeout
│ │ PING_SENT │ │
│ └────┬──────────┘ ┌──────▼───────┐
│ │ │ ENTRY_DELETED│
│ ┌────▼──────────┐ └──────────────┘
│ │PING_COMPLETE │
│ └────┬──────────┘
│ │
│ ┌────▼──────────┐
└─────────┤ CONN_DROP │
│ (RST/FIN) │
└───────────────┘
```
## Исправления относительно Minecraft-XDP-eBPF
### 1. Pure ACK deadlock (критический баг в MC-XDP-eBPF)
```
Оригинал (MC-XDP-eBPF):
AWAIT_ACK → pure ACK → DROP + переход в AWAIT_MC_HANDSHAKE
→ сервер не видит ACK → retransmit SYN-ACK → ~1-7 сек лага
Наш фикс:
AWAIT_ACK → pure ACK → PASS + переход в AWAIT_MC_HANDSHAKE
→ сервер видит ACK → TCP handshake завершён нормально
```
```c
// ОРИГИНАЛ (сломан):
if (state == AWAIT_ACK) {
initial_state->state = state = AWAIT_MC_HANDSHAKE;
if (tcp_payload >= tcp_payload_end) {
goto drop; // Pure ACK dropped → DEADLOCK
}
}
// ИСПРАВЛЕНИЕ:
if (state == AWAIT_ACK) {
initial_state->state = state = AWAIT_MC_HANDSHAKE;
if (tcp_payload >= tcp_payload_end) {
return XDP_PASS; // Pure ACK passed → handshake ok
}
}
```
### 2. Stale conntrack на RST/FIN
```c
// В AWAIT_MC_HANDSHAKE — RST/FIN должен чистить entry:
if ((tcp->fin || tcp->rst) && state != AWAIT_ACK) {
bpf_map_delete_elem(&conntrack_map, &flow_key);
return XDP_PASS;
}
```
### 3. Player map LRU
```c
// Оригинал: BPF_MAP_TYPE_HASH (plain) — при заполнении дропает новых игроков
// Исправление:
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65535);
__type(key, struct flow_key);
__type(value, struct player_entry);
} player_connection_map SEC(".maps");
```
### 4. Idle timer на conntrack (не только на player)
```c
// Добавить bpf_timer в conntrack entry:
struct conntrack_entry {
__u32 state;
__u32 expected_seq;
__u32 src_ip;
__u16 src_port;
__u8 fails;
struct bpf_timer timer; // 30 sec idle timeout
};
```
### 5. IPv6 support
```c
// Оригинал: return XDP_PASS for non-IP (IPv6 bypass)
// Исправление:
if (eth->h_proto != bpf_htons(ETH_P_IP)
&& eth->h_proto != bpf_htons(ETH_P_IPV6)) {
return XDP_PASS;
}
// Отдельный flow key для IPv6:
struct flow_key_v6 {
struct in6_addr src_ip;
struct in6_addr dst_ip;
__u16 src_port;
__u16 dst_port;
};
```
### 6. IP/CIDR whitelist (issue #42 из MC-XDP-eBPF)
```c
struct whitelist_key {
__u32 prefixlen;
__u32 ip;
};
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 10000);
__type(key, struct whitelist_key);
__type(value, __u8);
__uint(map_flags, BPF_F_NO_PREALLOC);
} whitelist_map SEC(".maps");
```
---
## BPF Maps
```c
// ── Blacklist (LPM_TRIE для CIDR) ──
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 100000);
__type(key, struct lpm_key);
__type(value, __u64); // timestamp ban
__uint(map_flags, BPF_F_NO_PREALLOC);
} blacklist_map SEC(".maps");
// ── Whitelist (LPM_TRIE для CIDR) ──
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 1000);
__type(key, struct lpm_key);
__type(value, __u8);
__uint(map_flags, BPF_F_NO_PREALLOC);
} whitelist_map SEC(".maps");
// ── Conntrack (unverified connections, LRU) ──
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 16384);
__type(key, struct flow_key);
__type(value, struct conntrack_entry);
} conntrack_map SEC(".maps");
// ── Verified players (LRU) ──
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65535);
__type(key, struct flow_key);
__type(value, struct player_entry);
} player_connection_map SEC(".maps");
// ── SYN throttle per-IP ──
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65535);
__type(key, __u32); // src IP
__type(value, struct throttle_entry);
} connection_throttle SEC(".maps");
// ── Statistics (per-CPU, для Prometheus) ──
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 16);
__type(key, __u32);
__type(value, __u64);
} stats_map SEC(".maps");
```
---
## Граница XDP / Rust
```
XDP делает (L3/L4): Rust делает (L7):
TCP state machine PoW Challenge (SHA256)
SYN throttle per-IP MC handshake парсинг
IP blacklist (LPM_TRIE) HMAC sign/verify
IP whitelist (CIDR) Rate limit per-IP
Invalid TCP flags drop Death code auto-ban
UDP drop GeoIP/ASN reputation
Per-connection seq tracking Blacklist (сложные правила)
bpf_timer idle cleanup Reassembly буфер
```
XDP **не может** (даже в kernel 6.x):
- SHA256 (нет heap, нет looping на достаточное время)
- Floating point (ограничен)
- Сложные строковые операции
- TLS инспекция
- GeoIP / DNS resolve
---
## Загрузка (Rust + libbpf-rs)
```rust
use libbpf_rs::{MapFlags, ObjectBuilder};
pub struct RampartXdp {
obj: Object,
interface: String,
}
impl RampartXdp {
pub fn load(interface: &str) -> Result<Self> {
let obj = ObjectBuilder::default()
.open_file("/etc/rampart/xdp_filter.o")?
.load()?;
let prog = obj.prog("rampart_xdp_filter").unwrap();
prog.attach_xdp(if_nametoindex(interface)?)?;
Ok(Self { obj, interface: interface.to_string() })
}
pub fn ban_ip(&self, ip: Ipv4Addr, duration: Duration) {
let mut map = self.obj.map("blacklist_map").unwrap();
let key = LpmKey::new(32, ip);
let ts = now_nanos() + duration.as_nanos() as u64;
map.update(&key.to_bytes(), &ts.to_le_bytes(), MapFlags::ANY).unwrap();
}
pub fn get_stats(&self) -> XdpStats {
XdpStats {
total: self.read_percpu_sum("stats_map", 0),
passed: self.read_percpu_sum("stats_map", 1),
blocked: self.read_percpu_sum("stats_map", 2),
ratelimit: self.read_percpu_sum("stats_map", 3),
}
}
}
```
---
## Требования
```bash
# Проверка XDP
ethtool -i eth0 | grep driver # i40e, mlx5, virtio
# Тип виртуализации
systemd-detect-virt # kvm/none = XDP, openvz/lxc = нет
# Версия ядра
uname -r # >= 5.10
# Зависимости
apt-get install -y libbpf-dev clang llvm linux-headers-$(uname -r)
```
## ringbuf (вместо perfbuf)
| | perfbuf | ringbuf (kernel 5.8+) |
|---|---|---|
| Тип | Per-CPU | Один разделяемый |
| Копирование | Одно | Одно |
| Порядок | Не гарантирован | Гарантирован |
| Память | Per-CPU | Меньше |
| **Вывод** | Устаревший | **Используй ringbuf** |
## Лимиты BPF verifier
```
Максимум инструкций: 1M (kernel 5.2+)
Максимум стека: 512 байт
Максимум вложенности: 8
Циклы: разрешены с 5.3+
Dynamic alloc: нет (только BPF maps)
```