Multi-layer DDoS protection for Minecraft servers. - rampart-core: Edge node with XDP/eBPF + Rust L7 filtering - rampart-manager: REST API with JWT auth, Redis sync - rampart-cli: CLI tool for operators - velocity-plugin: Domain check, HMAC verify, server registry, load balancer - paper-plugin: Auto-registration, heartbeat, HMAC verify - dashboard: React + Vite web UI for management
9.6 KiB
9.6 KiB
Anti-Bot - Sonar, Challenge системы, Fingerprinting
Актуально: v0.2+
Путь игрока через защиту
Новый игрок
|
v
┌──────────────────┐
│ Edge нода │ Rate limit, Blacklist, Death code
│ Rust │ Невалидные пакеты → бан IP
└────────┬─────────┘
v (валидный handshake)
┌──────────────────┐
│ Velocity │ DomainCheck, HmacCheck
│ Java │ Неизвестный домен → блок
└────────┬─────────┘
v (подписанный HMAC)
┌──────────────────┐
│ Sonar Limbo │ Гравитация, Vehicle, TCP timing
│ Java │ Не прошёл → блок IP на N мин
└────────┬─────────┘
v (прошёл физику)
┌──────────────────┐
│ Custom │ Timing challenge, Map CAPTCHA
│ Challenge │ Не прошёл → блок IP
└────────┬─────────┘
v
┌──────────────────┐
│ Hub / Game │ Игрок на сервере
│ Server │ Поведенческий анализ первые 30 сек
└──────────────────┘
Каждый слой может заблокировать игрока. Verified DB на Redis - прошёл один раз, не проверяется снова (TTL 24h).
Слои защиты от ботов
[1] XDP rate limit - ограничивает скорость SYN flood
[2] Rust rate limit - ограничивает connections/сек per IP
[3] HMAC verification - только через наш edge (криптография)
[4] ASN reputation - датацентровые IP = строже
[5] Sonar 3.0 (Limbo) - физическая проверка
[6] Custom challenge - кастомная механика (нет готового обхода)
[7] Behavioral analysis - паттерны поведения на хабе
Sonar 3.0 - базовый слой (июль 2026)
GitHub: jonesdevelopment/sonar
Версия: 3.x, релиз 12 июля 2026
Поддержка: Velocity 3.4-3.5.x, MC 1.8-26.2
Как работает
Игрок → Velocity → Sonar перехватывает
↓
Отправляет на Limbo (лёгкий фейковый сервер)
↓
Проверки на Limbo:
├─ Гравитация: игрок должен падать вниз
├─ Vehicle: правильные пакеты при взаимодействии с лодкой
├─ TCP timing: не слишком быстрые ответы
└─ Очередь: физически ограничивает число одновременных верификаций
↓
Прошёл → IP в verified DB → следующие подключения проходят мгновенно
Конфиг
# sonar/config.yml
general:
max-online-per-ip: 3
min-players-for-attack: 8 # при N+ новых conn/сек → режим атаки
verification:
timing:
first-packet: 3500 # мс на первый пакет
movement: 10000 # мс на проверку физики
gravity:
enabled: true
captcha-on-fail: true
vehicle:
enabled: true
database:
type: MYSQL # или POSTGRESQL, H2
host: "10.0.0.1"
database: "sonar"
expiration: 5 # verified IP живёт N дней
Кастомный challenge (поверх Sonar)
Почему нужен кастомный
Sonar открытый → атакующий читает код → пишет обход
Кастомный → нет готового обхода → атакующий тратит время
Меняем механику регулярно → обход устаревает
Идеи challenge (от простого к сложному)
1. Timing challenge
// Игрок должен ответить МЕЖДУ 2 и 8 секундами
// Боты отвечают мгновенно или с постоянной задержкой
long sent = System.currentTimeMillis();
// ...ждём ответ...
long elapsed = System.currentTimeMillis() - sent;
if (elapsed < 2000) {
// Слишком быстро - скрипт
fail("Ответ слишком быстрый");
} else if (elapsed > 8000) {
// AFK/медленный скрипт
fail("Время вышло");
} else {
pass();
}
2. Map CAPTCHA
// Рендерим картинку на карте Minecraft
// Случайный шрифт из пула 50+ шрифтов
// Игрок вводит код в чате
MapRenderer renderer = new CaptchaMapRenderer(challenge.getCode());
ItemStack map = new ItemStack(Material.FILLED_MAP);
map.setItemMeta(mapMeta);
player.getInventory().setItemInMainHand(map);
player.sendMessage("§eВведи код с карты в чат:");
3. Поведенческий анализ (первые 30 сек на хабе)
// Смотрим на паттерны движения
// Реальный игрок: случайные повороты, ускорения, паузы
// Бот: линейное движение или полная неподвижность
@EventHandler
public void onPlayerMove(PlayerMoveEvent e) {
BehaviorProfile profile = profiles.get(e.getPlayer().getUniqueId());
profile.recordMovement(e.getTo());
if (profile.getSamples() >= 50) {
double score = profile.calculateBotProbability();
if (score > 0.85) {
triggerChallenge(e.getPlayer());
}
}
}
4. Контекстный вопрос
// Вопрос зависит от случайного события на сервере
// Бот не знает контекст
String[] events = {"Последний вошедший игрок", "Текущее время на сервере"};
// "Как зовут последнего игрока который зашёл перед тобой?"
// Бот не знает → провал
Репутационная система IP
// Каждый IP получает score от -100 до +100
// Хранится в Redis с TTL
pub struct IpReputation {
score: i32,
last_updated: u64,
}
impl IpReputation {
pub fn apply_event(&mut self, event: ReputationEvent) {
let delta = match event {
ReputationEvent::SuccessfulLogin => +10,
ReputationEvent::HourWithoutIssues => +5,
ReputationEvent::RateLimitHit => -20,
ReputationEvent::InvalidPacket => -30,
ReputationEvent::BotChallengeFailed => -50,
ReputationEvent::BotChallengePass => +15,
};
self.score = (self.score + delta).clamp(-100, 100);
}
pub fn get_rate_multiplier(&self) -> f64 {
match self.score {
s if s >= 80 => 2.0, // доверенный - больше лимит
s if s >= 0 => 1.0, // нормальный
s if s >= -30 => 0.5, // подозрительный
s if s >= -60 => 0.2, // проблемный
_ => 0.05, // почти в бане
}
}
}
Bloom Filter для блэклиста
// Для очень больших блэклистов (миллионы IP)
// Bloom filter: 1% false positive, но 100x меньше памяти
// HashSet<u32> на 1M IP: ~32 MB
// Bloom filter на 1M IP: ~2 MB при p=0.01
use bloomfilter::Bloom;
pub struct FastBlacklist {
bloom: Bloom<u32>, // быстрая предпроверка (может дать false positive)
exact: DashMap<u32, BanEntry>, // точная проверка (только если bloom сказал "да")
}
impl FastBlacklist {
pub fn is_blocked(&self, ip: u32) -> bool {
// Если bloom говорит "нет" - точно не в блэклисте (нет false negative)
if !self.bloom.check(&ip) { return false; }
// Bloom говорит "возможно да" - проверяем точно
self.exact.contains_key(&ip)
}
}
VPN / Proxy детекция
pub struct VpnDetector {
// MaxMind GeoLite2-ASN + список известных VPN/proxy ASN
asn_reader: maxminddb::Reader<Vec<u8>>,
vpn_asns: HashSet<u32>,
datacenter_keywords: Vec<Regex>,
}
impl VpnDetector {
pub fn classify(&self, ip: IpAddr) -> IpCategory {
let Ok(record) = self.asn_reader.lookup::<Asn>(ip) else {
return IpCategory::Unknown;
};
if let Some(asn) = record.autonomous_system_number {
if self.vpn_asns.contains(&asn) {
return IpCategory::VPN;
}
}
if let Some(org) = record.autonomous_system_organization {
if self.datacenter_keywords.iter().any(|r| r.is_match(org)) {
return IpCategory::Datacenter;
}
}
IpCategory::Residential
}
}
Список VPN ASN: https://github.com/X4BNet/lists_vpn (обновляется еженедельно)
MaxMind GeoLite2-ASN: бесплатно при регистрации на maxmind.com