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

99
xdp/core/common.h Normal file
View file

@ -0,0 +1,99 @@
#ifndef RAMPART_COMMON_H
#define RAMPART_COMMON_H
#include <linux/types.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#define ETH_P_IP 0x0800
#define ETH_P_IPV6 0x86DD
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IP_OFFSET 0x1FFF
#define IP_MF 0x2000
// ── Protocol-agnostic TCP state machine ──
enum connection_state {
STATE_SYN_RECEIVED = 0, // SYN seen, waiting for ACK (handshake completing)
STATE_ESTABLISHED = 1, // handshake done; payload handed to proto_hook()
};
// ── Flow key (4-tuple for connection tracking) ──
struct flow_key {
__u32 src_ip;
__u32 dst_ip;
__u16 src_port;
__u16 dst_port;
};
// ── Connection tracking entry ──
// LRU maps evict stale entries automatically when full; RST/FIN removes
// entries explicitly. last_seen supports future timer-based idle cleanup.
struct conntrack_entry {
__u32 state;
__u32 expected_seq;
__u32 src_ip;
__u64 last_seen;
__u16 src_port;
__u8 fails;
};
// ── SYN / UDP throttle entry ──
struct throttle_entry {
__u64 window_start;
__u32 hits;
};
// ── LPM key for blacklist/whitelist ──
struct lpm_key {
__u32 prefixlen;
__u32 ip;
};
// ── Ringbuf event (userspace receives these) ──
enum event_type {
EVENT_BAN = 0,
EVENT_RATE_LIMIT = 1,
EVENT_POLICY_DROP = 2,
EVENT_CONN_DROP = 3,
};
struct xdp_event {
__u32 type;
__u32 src_ip;
__u32 metadata;
__u64 timestamp;
};
// ── Bounds check macros (dual-bounds для verifier) ──
#define CHECK_BOUNDS_OR_RETURN(ptr, sz, pend, dend) \
do { \
if ((void *)(ptr) + (sz) > (void *)(dend)) \
goto error; \
barrier_var(ptr); \
if ((void *)(ptr) + (sz) > (void *)(pend)) \
goto error; \
} while (0)
#define barrier_var(var) asm volatile("" : "+r"(var))
// ── Ringbuf for events → userspace (declared here for push_event) ──
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24);
} events_ringbuf SEC(".maps");
// ── Event push to ringbuf ──
static __always_inline void push_event(enum event_type type, __u32 src_ip, __u32 meta)
{
struct xdp_event *e = bpf_ringbuf_reserve(&events_ringbuf, sizeof(struct xdp_event), 0);
if (!e)
return;
e->type = type;
e->src_ip = src_ip;
e->metadata = meta;
e->timestamp = bpf_ktime_get_ns();
bpf_ringbuf_submit(e, 0);
}
#endif /* RAMPART_COMMON_H */

36
xdp/core/config.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef RAMPART_CONFIG_H
#define RAMPART_CONFIG_H
// ⚙️ Runtime configurable globals (patched by Rust loader)
// These are volatile const — compiler replaces reads with immediate values
// after loader writes to .rodata section
// ── Port range ──
static volatile const __u16 G_START_PORT = 25565;
static volatile const __u16 G_END_PORT = 25570;
// ── SYN throttle ──
static volatile const __u32 G_SYN_HIT_COUNT = 10; // max SYNs / window
static volatile const __u64 G_SYN_WINDOW_NS = 3000000000ULL; // 3 sec
static volatile const __u64 G_SYN_BAN_DURATION_NS = 60000000000ULL; // 60 sec
// ── Idle timeouts ──
static volatile const __u64 G_CONNTRACK_IDLE_NS = 30000000000ULL; // 30 sec
static volatile const __u64 G_PLAYER_IDLE_NS = 120000000000ULL; // 120 sec
// ── Blacklist default ban duration ──
static volatile const __u64 G_BAN_DURATION_NS = 300000000000ULL; // 5 min
// ── Max out-of-order packets before dropping connection ──
static volatile const __u8 G_MAX_OUT_OF_ORDER = 4;
// ── UDP policy ──
static volatile const __u8 G_UDP_POLICY = 0; // 0 = pass, 1 = drop, 2 = rate-limit
static volatile const __u32 G_UDP_HIT_COUNT = 100; // max packets / window
static volatile const __u64 G_UDP_WINDOW_NS = 1000000000ULL; // 1 sec
// ── Feature flags ──
static volatile const __u8 G_FEATURE_SYN_THROTTLE = 1;
static volatile const __u8 G_FEATURE_EVENTS = 1;
#endif /* RAMPART_CONFIG_H */

66
xdp/core/maps.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef RAMPART_MAPS_H
#define RAMPART_MAPS_H
// 🔗 Blacklist (LPM_TRIE for CIDR support)
// Cleared by Rust userspace or per-entry expiry via ringbuf
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__uint(max_entries, 100000);
__type(key, struct lpm_key);
__type(value, __u64); // ban expiry (ktime_ns)
__uint(map_flags, BPF_F_NO_PREALLOC);
} blacklist_map SEC(".maps");
// 🔗 Whitelist (LPM_TRIE for CIDR) — checked before any filter
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");
// 🔗 Connection tracking (SYN_RECEIVED → ESTABLISHED)
// 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");
// 🔗 SYN throttle per-source-IP (LRU)
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");
// 🔗 UDP rate limit per-source-IP (LRU) — policy from config.h
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 65535);
__type(key, __u32); // src_ip
__type(value, struct throttle_entry);
} udp_rate_limit SEC(".maps");
// 🔗 Statistics (per-CPU, атомарные инкременты)
#define STAT_TOTAL 0
#define STAT_TCP 1
#define STAT_WHITELIST 2
#define STAT_BLACKLIST 3
#define STAT_SYN_THROTTLE 4
#define STAT_PASS 5
#define STAT_DROP 6
#define STAT_UDP 7
#define STAT_RATE_LIMIT 8
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 16);
__type(key, __u32);
__type(value, __u64);
} stats_map SEC(".maps");
#endif /* RAMPART_MAPS_H */

26
xdp/core/stats.h Normal file
View file

@ -0,0 +1,26 @@
#ifndef RAMPART_STATS_H
#define RAMPART_STATS_H
#include "common.h"
#include "maps.h"
// ── Per-CPU stat increment ──
static __always_inline void inc_stat(__u32 idx)
{
__u64 *val = bpf_map_lookup_elem(&stats_map, &idx);
if (val)
__sync_fetch_and_add(val, 1);
}
// ── Convenience wrappers ──
static __always_inline void inc_total(void) { inc_stat(STAT_TOTAL); }
static __always_inline void inc_tcp(void) { inc_stat(STAT_TCP); }
static __always_inline void inc_udp(void) { inc_stat(STAT_UDP); }
static __always_inline void inc_whitelist(void) { inc_stat(STAT_WHITELIST); }
static __always_inline void inc_blacklist(void) { inc_stat(STAT_BLACKLIST); }
static __always_inline void inc_syn_throttle(void) { inc_stat(STAT_SYN_THROTTLE); }
static __always_inline void inc_rate_limit(void) { inc_stat(STAT_RATE_LIMIT); }
static __always_inline void inc_pass(void) { inc_stat(STAT_PASS); }
static __always_inline void inc_drop(void) { inc_stat(STAT_DROP); }
#endif /* RAMPART_STATS_H */

398
xdp/core/universal_filter.c Normal file
View file

@ -0,0 +1,398 @@
// ── Rampart Universal XDP Filter ──
// Protocol-agnostic L3/L4 filter with an L7 extension point.
//
// Responsibilities (L3/L4):
// 1. No pure ACK drop (prevents TCP handshake deadlock)
// 2. LRU conntrack: SYN_RECEIVED → ESTABLISHED, seq tracking
// 3. RST/FIN removes conntrack entry (no stale state)
// 4. IPv6 support alongside IPv4
// 5. IP/CIDR blacklist + whitelist (LPM_TRIE)
// 6. SYN throttle per-IP, invalid TCP flag combos dropped
// 7. UDP policy from config.h: pass / drop / rate-limit
//
// Deep payload inspection is delegated to an optional protocol hook:
// see ../hooks/hook_api.h. Without a hook, established connections pass.
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "common.h"
#include "maps.h"
#include "config.h"
#include "stats.h"
#include "../hooks/hook_api.h"
char __license[] SEC("license") = "GPL";
// ── TCP flag check (drop malicious combos) ──
// Returns 1 if packet should be dropped
static __always_inline __u8 detect_tcp_bypass(struct tcphdr *tcp)
{
__u8 flags = *((__u8 *)tcp + 13);
// No flags at all — bogus packet
if ((flags & 0x3F) == 0)
return 1;
// SYN+FIN or SYN+RST — always forged
if (tcp->syn) {
if (tcp->fin || tcp->rst)
return 1;
}
// SYN+ACK from client side — only servers send this
if (tcp->syn && tcp->ack)
return 1;
// URG flag — not used by any mainstream protocol on protected ports
if (tcp->urg)
return 1;
return 0;
}
// ── Blacklist lookup with expiry; removes expired entries ──
static __always_inline __u8 is_blacklisted(__u32 src_ip, __u64 now)
{
struct lpm_key key = { .prefixlen = 32, .ip = src_ip };
__u64 *ban_until = bpf_map_lookup_elem(&blacklist_map, &key);
if (!ban_until)
return 0;
if (*ban_until > now)
return 1;
bpf_map_delete_elem(&blacklist_map, &key); // expired — clean up
return 0;
}
static __always_inline void ban_ip(__u32 src_ip, __u64 until)
{
struct lpm_key key = { .prefixlen = 32, .ip = src_ip };
bpf_map_update_elem(&blacklist_map, &key, &until, BPF_ANY);
}
// ── Generic per-IP window throttle (shared by SYN throttle and UDP RL) ──
// map: LRU_HASH keyed by src_ip; returns 1 if limit exceeded (caller bans/drops)
static __always_inline __u8 throttle_hit(void *map, __u32 src_ip,
__u32 max_hits, __u64 window_ns,
__u64 now)
{
struct throttle_entry *th = bpf_map_lookup_elem(map, &src_ip);
if (th) {
if (now - th->window_start < window_ns) {
if (th->hits >= max_hits)
return 1;
__sync_fetch_and_add(&th->hits, 1);
} else {
th->window_start = now;
th->hits = 1;
}
} else {
struct throttle_entry new_th = { .window_start = now, .hits = 1 };
bpf_map_update_elem(map, &src_ip, &new_th, BPF_ANY);
}
return 0;
}
// ══════════════════════════════════════════
// Main XDP entry point
// ══════════════════════════════════════════
SEC("xdp")
int rampart_universal_filter(struct xdp_md *ctx)
{
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
inc_total();
// ── Parse Ethernet header ──
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
// Non-IP traffic: pass (ARP, etc.)
if (eth->h_proto != bpf_htons(ETH_P_IP) && eth->h_proto != bpf_htons(ETH_P_IPV6))
return XDP_PASS;
// ── Parse IP header ──
__u32 src_ip = 0;
__u32 dst_ip = 0;
__u8 l4_proto = 0;
__u8 is_ipv6 = 0;
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->ihl < 5)
return XDP_DROP;
if (ip->protocol != IPPROTO_TCP && ip->protocol != IPPROTO_UDP)
return XDP_PASS;
// Non-sequential fragment: pass (can't inspect ports safely)
if (ip->frag_off & bpf_htons(IP_OFFSET))
return XDP_PASS;
// First fragment with MF: drop (can't reassemble)
if (ip->frag_off & bpf_htons(IP_MF))
return XDP_DROP;
src_ip = ip->saddr;
dst_ip = ip->daddr;
l4_proto = ip->protocol;
} else {
struct ipv6hdr *ip6 = (void *)(eth + 1);
if ((void *)(ip6 + 1) > data_end)
return XDP_PASS;
if (ip6->nexthdr != IPPROTO_TCP && ip6->nexthdr != IPPROTO_UDP)
return XDP_PASS; // extension headers not walked
// Use IPv4-mapped IPv6 address for flow key (::ffff:0:0/96)
if (ip6->saddr.in6_u.u6_addr32[0] != 0 ||
ip6->saddr.in6_u.u6_addr32[1] != 0 ||
ip6->saddr.in6_u.u6_addr32[2] != bpf_htonl(0xFFFF) ||
ip6->daddr.in6_u.u6_addr32[0] != 0 ||
ip6->daddr.in6_u.u6_addr32[1] != 0 ||
ip6->daddr.in6_u.u6_addr32[2] != bpf_htonl(0xFFFF)) {
// Non-mapped IPv6 — pass for now (not supported)
return XDP_PASS;
}
// FIX: source address must come from saddr, never daddr
src_ip = ip6->saddr.in6_u.u6_addr32[3];
dst_ip = ip6->daddr.in6_u.u6_addr32[3];
l4_proto = ip6->nexthdr;
is_ipv6 = 1;
}
// ── Parse L4 header ──
__u8 ip_hdr_len;
void *l4;
if (is_ipv6) {
struct ipv6hdr *ip6 = (void *)(eth + 1);
l4 = (void *)(ip6 + 1);
ip_hdr_len = sizeof(struct ipv6hdr);
} else {
struct iphdr *ip = (void *)(eth + 1);
l4 = (void *)ip + (ip->ihl * 4);
ip_hdr_len = ip->ihl * 4;
}
if ((void *)l4 + sizeof(struct tcphdr) > data_end)
return XDP_PASS;
// ── Port range check (protected services) ──
__u16 dst_port;
if (l4_proto == IPPROTO_TCP)
dst_port = bpf_ntohs(((struct tcphdr *)l4)->dest);
else
dst_port = bpf_ntohs(((struct udphdr *)l4)->dest);
if (dst_port < G_START_PORT || dst_port > G_END_PORT)
return XDP_PASS;
if (l4_proto == IPPROTO_TCP)
inc_tcp();
else
inc_udp();
__u64 now = bpf_ktime_get_ns();
// ── Whitelist check (CIDR) — before any filtering ──
struct lpm_key wl_key = { .prefixlen = 32, .ip = src_ip };
if (bpf_map_lookup_elem(&whitelist_map, &wl_key)) {
inc_whitelist();
return XDP_PASS;
}
// ── Blacklist check (CIDR with expiry) ──
if (is_blacklisted(src_ip, now)) {
inc_blacklist();
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_BAN, src_ip, 0);
return XDP_DROP;
}
// ═══════════ UDP path ═══════════
if (l4_proto == IPPROTO_UDP) {
switch (G_UDP_POLICY) {
case 1: // drop
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_POLICY_DROP, src_ip, 0);
return XDP_DROP;
case 2: // rate-limit
if (throttle_hit(&udp_rate_limit, src_ip,
G_UDP_HIT_COUNT, G_UDP_WINDOW_NS, now)) {
inc_rate_limit();
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_RATE_LIMIT, src_ip, G_UDP_HIT_COUNT);
return XDP_DROP;
}
/* fallthrough: within limits — pass */
default: // pass
inc_pass();
return XDP_PASS;
}
}
// ═══════════ TCP path ═══════════
struct tcphdr *tcp = l4;
// Malicious TCP flags
if (detect_tcp_bypass(tcp)) {
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_POLICY_DROP, src_ip, 0);
return XDP_DROP;
}
// TCP header length check
if (tcp->doff < 5)
return XDP_DROP;
__u8 tcp_hdr_len = tcp->doff * 4;
if ((void *)data + sizeof(struct ethhdr) + ip_hdr_len + tcp_hdr_len > data_end)
return XDP_DROP;
// Compute payload pointers
__u8 *tcp_payload = (__u8 *)tcp + tcp_hdr_len;
__u8 *tcp_payload_end = (__u8 *)data_end;
__s32 tcp_payload_len = (__s32)(tcp_payload_end - tcp_payload);
if (tcp_payload_len < 0)
tcp_payload_len = 0;
// ── Build flow key ──
struct flow_key flow = {
.src_ip = src_ip,
.dst_ip = dst_ip,
.src_port = tcp->source,
.dst_port = tcp->dest,
};
// ── SYN handling (new connection) ──
if (tcp->syn && !tcp->ack) {
// SYN throttle per-IP
if (G_FEATURE_SYN_THROTTLE &&
throttle_hit(&connection_throttle, src_ip,
G_SYN_HIT_COUNT, G_SYN_WINDOW_NS, now)) {
ban_ip(src_ip, now + G_SYN_BAN_DURATION_NS);
inc_syn_throttle();
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_RATE_LIMIT, src_ip, G_SYN_HIT_COUNT);
return XDP_DROP;
}
// Create conntrack entry for new connection
struct conntrack_entry ce = {};
ce.state = STATE_SYN_RECEIVED;
ce.expected_seq = bpf_ntohl(tcp->seq) + 1; // expect ACK seq
ce.src_ip = src_ip;
ce.src_port = tcp->source;
ce.last_seen = now;
if (bpf_map_update_elem(&conntrack_map, &flow, &ce, BPF_ANY)) {
// Map full — should not happen with LRU
inc_drop();
return XDP_DROP;
}
inc_pass();
return XDP_PASS; // Let SYN through
}
// ── Connection tracking lookup ──
struct conntrack_entry *conn = bpf_map_lookup_elem(&conntrack_map, &flow);
if (!conn) {
// Unknown connection — drop
inc_drop();
return XDP_DROP;
}
conn->last_seen = now;
// Handle RST/FIN — clean up conntrack entry
if (tcp->rst || tcp->fin) {
bpf_map_delete_elem(&conntrack_map, &flow);
inc_pass();
return XDP_PASS;
}
// ── Sequence number tracking (established connections only) ──
__u32 seq = bpf_ntohl(tcp->seq);
if (conn->state == STATE_ESTABLISHED && tcp_payload_len > 0 &&
seq != conn->expected_seq) {
conn->fails++;
if (conn->fails >= G_MAX_OUT_OF_ORDER) {
bpf_map_delete_elem(&conntrack_map, &flow);
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_CONN_DROP, src_ip, conn->fails);
return XDP_DROP;
}
// Allow retransmission (don't update expected_seq)
inc_pass();
return XDP_PASS;
}
// ── State machine ──
if (conn->state == STATE_SYN_RECEIVED) {
// Expecting the ACK that completes the 3-way handshake
if (!tcp->ack || seq != conn->expected_seq) {
inc_drop();
return XDP_DROP;
}
// Do NOT drop pure ACK here. Other XDP filters drop the pure ACK
// expecting the data packet to serve as ACK, which causes ~1-7s
// TCP handshake deadlock. We pass the ACK through and promote
// the connection to ESTABLISHED.
conn->state = STATE_ESTABLISHED;
conn->expected_seq = seq + tcp_payload_len;
if (tcp_payload_len == 0) {
inc_pass();
return XDP_PASS;
}
// Client sent data together with the final ACK — fall through to hook
}
// STATE_ESTABLISHED: empty segments pass untouched (keep-alives),
// payload segments go through the optional protocol hook.
if (tcp_payload_len > 0) {
struct cursor c = { .pos = tcp_payload, .end = tcp_payload_end };
struct pkt_meta m = {
.src_ip = src_ip,
.dst_ip = dst_ip,
.src_port = bpf_ntohs(tcp->source),
.dst_port = dst_port,
.payload_len = (__u16)tcp_payload_len,
.tcp_flags = *((__u8 *)tcp + 13),
.is_ipv6 = is_ipv6,
};
enum xdp_action verdict = proto_hook(&c, &m);
if (verdict == XDP_DROP) {
bpf_map_delete_elem(&conntrack_map, &flow);
inc_drop();
if (G_FEATURE_EVENTS)
push_event(EVENT_CONN_DROP, src_ip, 0);
return XDP_DROP;
}
conn->expected_seq = seq + tcp_payload_len;
}
inc_pass();
return XDP_PASS;
}