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

156
src/xdp/filter.rs Normal file
View file

@ -0,0 +1,156 @@
use anyhow::{Context, Result, bail};
use libbpf_rs::{MapCore, MapFlags, Object, ObjectBuilder, RingBuffer, RingBufferBuilder, Xdp, XdpFlags};
use std::net::Ipv4Addr;
use std::os::unix::io::AsFd;
use super::XdpStats;
pub struct XdpFilter {
obj: Option<Object>,
ringbuf: Option<RingBuffer<'static>>,
ifindex: i32,
interface: String,
}
impl XdpFilter {
pub fn new(interface: &str) -> Self {
Self {
obj: None,
ringbuf: None,
ifindex: 0,
interface: interface.to_string(),
}
}
pub fn load(&mut self) -> Result<()> {
let bpf_obj = include_bytes!(concat!(env!("OUT_DIR"), "/universal_filter.o"));
let obj = ObjectBuilder::default()
.open_memory(bpf_obj)
.context("Failed to open XDP object")?
.load()
.context("Failed to load XDP object (verifier error?)")?;
let ifindex = unsafe { libc::if_nametoindex(self.interface.as_ptr() as *const libc::c_char) };
if ifindex == 0 {
bail!("interface '{}' not found", self.interface);
}
let prog = obj
.progs()
.find(|p| p.name() == "rampart_universal_filter")
.context("XDP program 'rampart_universal_filter' not found")?;
Xdp::new(prog.as_fd()).attach(ifindex as i32, XdpFlags::NONE)?;
let rbuf = build_ringbuf(&obj)?;
self.obj = Some(obj);
self.ringbuf = Some(rbuf);
self.ifindex = ifindex as i32;
tracing::info!("XDP filter attached to {}", self.interface);
Ok(())
}
pub fn unload(&mut self) -> Result<()> {
if self.ifindex != 0 {
let fd = unsafe { std::os::unix::io::BorrowedFd::borrow_raw(std::os::unix::io::RawFd::from(-1)) };
let _ = Xdp::new(fd).detach(self.ifindex, XdpFlags::NONE);
}
self.ringbuf = None;
self.obj = None;
self.ifindex = 0;
tracing::info!("XDP filter detached from {}", self.interface);
Ok(())
}
pub fn drain_events(&self) {
if let Some(rb) = &self.ringbuf {
let _ = rb.consume();
}
}
fn find_map<'a>(&'a self, name: &str) -> Result<impl MapCore + 'a> {
self.obj
.as_ref()
.context("XDP not loaded")?
.maps()
.find(|m| m.name() == name)
.with_context(|| format!("map '{}' not found", name))
}
pub fn ban_ip(&self, ip: Ipv4Addr, duration_secs: u64) -> Result<()> {
let map = self.find_map("blacklist_map")?;
let mut key = [0u8; 8];
key[0] = 32;
key[4..8].copy_from_slice(&ip.octets());
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
map.update(
&key,
&(now + duration_secs * 1_000_000_000).to_le_bytes(),
MapFlags::ANY,
)?;
Ok(())
}
pub fn unban_ip(&self, ip: Ipv4Addr) -> Result<()> {
let map = self.find_map("blacklist_map")?;
let mut key = [0u8; 8];
key[0] = 32;
key[4..8].copy_from_slice(&ip.octets());
map.delete(&key)?;
Ok(())
}
pub fn get_stats(&self) -> Result<XdpStats> {
let map = self.find_map("stats_map")?;
let sum = |idx: u32| -> u64 {
let key = idx.to_le_bytes();
match map.lookup(&key, MapFlags::ANY) {
Ok(Some(v)) => v
.chunks_exact(8)
.map(|c| u64::from_le_bytes(c.try_into().expect("chunk size 8")))
.sum(),
_ => 0,
}
};
Ok(XdpStats {
total: sum(0),
tcp: sum(1),
whitelist: sum(2),
blacklist: sum(3),
syn_throttle: sum(4),
passed: sum(5),
dropped: sum(6),
udp: sum(7),
rate_limit: sum(8),
})
}
}
unsafe impl Send for XdpFilter {}
impl Drop for XdpFilter {
fn drop(&mut self) {
let _ = self.unload();
}
}
fn build_ringbuf(obj: &Object) -> Result<RingBuffer<'static>> {
let map = obj
.maps()
.find(|m| m.name() == "events_map")
.context("events_map not found")?;
let mut builder = RingBufferBuilder::new();
builder.add(&map, |data: &[u8]| {
if data.len() >= 16 {
let ty = u32::from_ne_bytes(data[0..4].try_into().expect("4 bytes for type"));
let ip4 = u32::from_ne_bytes(data[4..8].try_into().expect("4 bytes for ip"));
let val = u64::from_ne_bytes(data[8..16].try_into().expect("8 bytes for val"));
tracing::debug!(event = ty, src_ip = ip4, data = val, "xdp event");
}
0
})?;
Ok(builder.build()?)
}

53
src/xdp/metrics.rs Normal file
View file

@ -0,0 +1,53 @@
use anyhow::{Context, Result};
use prometheus::{IntGauge, register};
use super::XdpStats;
pub struct XdpMetrics {
total: IntGauge,
tcp: IntGauge,
whitelist: IntGauge,
blacklist: IntGauge,
syn_throttle: IntGauge,
passed: IntGauge,
dropped: IntGauge,
udp: IntGauge,
rate_limit: IntGauge,
}
impl XdpMetrics {
fn new_gauge(name: &str, help: &str) -> Result<IntGauge> {
let gauge = IntGauge::new(name, help)?;
register(Box::new(gauge.clone())).context(format!("failed to register {name}"))?;
Ok(gauge)
}
pub fn register() -> Result<Self> {
let m = Self {
total: Self::new_gauge("rampart_xdp_total", "Total XDP packets processed")?,
tcp: Self::new_gauge("rampart_xdp_tcp", "TCP packets processed")?,
whitelist: Self::new_gauge("rampart_xdp_whitelist", "Whitelisted packets")?,
blacklist: Self::new_gauge("rampart_xdp_blacklist", "Blacklisted packets")?,
syn_throttle: Self::new_gauge("rampart_xdp_syn_throttle", "SYN packets rate-limited")?,
passed: Self::new_gauge("rampart_xdp_passed", "Packets passed to upper layers")?,
dropped: Self::new_gauge("rampart_xdp_dropped", "Packets dropped by filter")?,
udp: Self::new_gauge("rampart_xdp_udp", "UDP packets processed")?,
rate_limit: Self::new_gauge("rampart_xdp_rate_limit", "Packets rate-limited by window counter")?,
};
tracing::info!("XDP Prometheus metrics registered");
Ok(m)
}
pub fn update(&self, stats: &XdpStats) {
self.total.set(stats.total as i64);
self.tcp.set(stats.tcp as i64);
self.whitelist.set(stats.whitelist as i64);
self.blacklist.set(stats.blacklist as i64);
self.syn_throttle.set(stats.syn_throttle as i64);
self.passed.set(stats.passed as i64);
self.dropped.set(stats.dropped as i64);
self.udp.set(stats.udp as i64);
self.rate_limit.set(stats.rate_limit as i64);
tracing::debug!("XDP metrics updated");
}
}

18
src/xdp/mod.rs Normal file
View file

@ -0,0 +1,18 @@
//! Загрузчик XDP-программы (L3/L4-уровень защиты).
mod stats;
pub use stats::XdpStats;
#[cfg(feature = "xdp")]
mod filter;
#[cfg(feature = "xdp")]
pub use filter::XdpFilter;
#[cfg(feature = "xdp")]
mod metrics;
#[cfg(feature = "xdp")]
pub use metrics::XdpMetrics;
#[cfg(not(feature = "xdp"))]
mod noop;
#[cfg(not(feature = "xdp"))]
pub use noop::XdpFilter;

26
src/xdp/noop.rs Normal file
View file

@ -0,0 +1,26 @@
use anyhow::Result;
use std::net::Ipv4Addr;
pub struct XdpFilter;
impl XdpFilter {
pub fn new(_interface: &str) -> Self {
Self
}
pub fn load(&mut self) -> Result<()> {
Ok(())
}
pub fn unload(&mut self) -> Result<()> {
Ok(())
}
pub fn drain_events(&self) {}
pub fn ban_ip(&self, _ip: Ipv4Addr, _duration_secs: u64) -> Result<()> {
Ok(())
}
pub fn unban_ip(&self, _ip: Ipv4Addr) -> Result<()> {
Ok(())
}
pub fn get_stats(&self) -> Result<super::XdpStats> {
Ok(super::XdpStats::default())
}
}

13
src/xdp/stats.rs Normal file
View file

@ -0,0 +1,13 @@
/// Счётчики XDP-программы; индексы соответствуют STAT_* из xdp/core/maps.h.
#[derive(Debug, Clone, Copy, Default)]
pub struct XdpStats {
pub total: u64,
pub tcp: u64,
pub whitelist: u64,
pub blacklist: u64,
pub syn_throttle: u64,
pub passed: u64,
pub dropped: u64,
pub udp: u64,
pub rate_limit: u64,
}