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:
parent
0b53ed720b
commit
15f474486a
179 changed files with 5044 additions and 11519 deletions
156
src/xdp/filter.rs
Normal file
156
src/xdp/filter.rs
Normal 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
53
src/xdp/metrics.rs
Normal 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
18
src/xdp/mod.rs
Normal 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
26
src/xdp/noop.rs
Normal 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
13
src/xdp/stats.rs
Normal 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,
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue