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
5.4 KiB
5.4 KiB
HAProxy и свой Rust Load Balancer
HAProxy - хорошее начало для v0.1-v0.2.
Свой Rust LB - цель для v0.4 (убирает SPOF, добавляет MC-aware health check).
Проблема с HAProxy
HAProxy как единственная точка входа = Single Point of Failure
Если HAProxy упал:
Все 20 Velocity нод недоступны
Все игроки дисконнектятся
Нет автоматического failover
Решение:
v0.1-v0.2: HAProxy + keepalived (VRRP failover)
v0.4+: Собственный Rust LB (несколько инстансов + SO_REUSEPORT)
HAProxy конфиг (v0.1)
# /etc/haproxy/haproxy.cfg
global
maxconn 100000
log /dev/log local0
stats socket /run/haproxy/admin.sock mode 660 level admin
defaults
mode tcp
timeout connect 3s
timeout client 30s
timeout server 30s
option tcplog
# ── Входящие игроки (от edge нод) ──
frontend minecraft_in
bind *:25565
mode tcp
# Принимаем только от наших edge нод
acl is_edge_ip src 10.0.100.0/24
tcp-request connection reject if !is_edge_ip
default_backend velocity_pool
# ── Velocity кластер ──
backend velocity_pool
mode tcp
balance leastconn # наименьшее число активных соединений
option tcp-check # проверяем что порт открыт
# check inter 3s - проверяем каждые 3 сек
# rise 2 - нужно 2 успеха чтобы считать живым
# fall 3 - 3 неудачи → выводим из ротации
server vel1 10.0.0.2:25565 check inter 3s rise 2 fall 3
server vel2 10.0.0.3:25565 check inter 3s rise 2 fall 3
server vel3 10.0.0.4:25565 check inter 3s rise 2 fall 3
# ... до vel20
# ── Stats страница (для Prometheus) ──
frontend stats
bind 10.0.0.1:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:${HAPROXY_STATS_PASS}
HAProxy + keepalived (устраняет SPOF)
# Два HAProxy сервера, один активный (MASTER), второй резервный (BACKUP)
# Виртуальный IP переключается автоматически при падении MASTER
# /etc/keepalived/keepalived.conf (на MASTER)
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100 # MASTER имеет высший приоритет
authentication {
auth_type PASS
auth_pass rampart
}
virtual_ipaddress {
10.0.0.1/24 # виртуальный IP, на него смотрят edge ноды
}
notify_master "/etc/keepalived/notify.sh MASTER"
notify_backup "/etc/keepalived/notify.sh BACKUP"
}
# На BACKUP: state BACKUP, priority 90
Свой Rust Load Balancer (v0.4)
Преимущества
✓ Нет SPOF - несколько инстансов на разных машинах
✓ SO_REUSEPORT - линейный scale по CPU
✓ MC-aware health check (не просто TCP, а настоящий MC ping)
✓ Hot reload без рестарта (добавить/убрать Velocity)
✓ Нативная интеграция с Redis/NATS
✓ Метрики в формате Prometheus из коробки
MC-aware Health Check
// Не просто TCP connect, а настоящий MC Status ping
async fn check_velocity_health(addr: &SocketAddr) -> bool {
let mut stream = match tokio::time::timeout(
Duration::from_secs(2),
TcpStream::connect(addr)
).await {
Ok(Ok(s)) => s,
_ => return false,
};
// Шлём MC Handshake (next_state=1, status ping)
let handshake = build_mc_handshake("health.check", addr.port(), 1);
if stream.write_all(&handshake).await.is_err() { return false; }
// Шлём Status Request (0x00)
let status_req = vec![0x01, 0x00];
if stream.write_all(&status_req).await.is_err() { return false; }
// Ждём Status Response
let mut buf = vec![0u8; 1024];
match tokio::time::timeout(Duration::from_secs(1), stream.read(&mut buf)).await {
Ok(Ok(n)) if n > 5 => true,
_ => false,
}
}
Hot Reload
pub struct RustLoadBalancer {
backends: Arc<ArcSwap<Vec<Backend>>>, // ArcSwap - lock-free swap
}
impl RustLoadBalancer {
// Атомарная замена списка бэкендов - без блокировки
pub async fn reload(&self, new_backends: Vec<Backend>) {
self.backends.store(Arc::new(new_backends));
// Текущие соединения не прерываются
// Новые соединения идут по новому списку
}
}
// ArcSwap из crates.io: arc-swap = "1"
Несколько инстансов без SPOF
# На трёх разных машинах запускаем Rust LB
# Edge ноды видят все три через DNS round-robin или BGP anycast
DNS:
lb.internal A → 10.0.0.10 (LB1)
lb.internal A → 10.0.0.11 (LB2)
lb.internal A → 10.0.0.12 (LB3)
Если LB1 упал:
DNS TTL = 10 сек → edge ноды переключаются на LB2/LB3
Без keepalived, без VRRP, без единой точки отказа