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

10 KiB
Raw Blame History

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 завершён нормально
// ОРИГИНАЛ (сломан):
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

// В 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

// Оригинал: 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)

// Добавить 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

// Оригинал: 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)

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

// ── 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)

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),
        }
    }
}

Требования

# Проверка 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)