zektyc/scripts/antiflood.sh
riricdev 8e7ec41f18 chore: checkpoint initial — Zektyc dataset, avant humanisation
- site vitrine + fingerprint test + zmap + z-panel
- remplacement duckdns -> riricdev.tail5ea5cd.ts.net (canonical/og/alternates/docs)
- suppression du token DuckDNS (plus utilisé)
2026-09-06 13:03:25 +02:00

124 lines
4.8 KiB
Shell
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Zektyc — protection contre les anomalies de trafic (DoS/DDoS L4, SYN floods)
#
# Mise au point : NE COUPE QUE lorsque le réseau est réellement saturé, au bord
# de tomber. On ne réagit plus à un simple pic de débit (un burst bien absorbé
# qui ne sature pas le lien ne doit PAS déclencher). On se base sur le DÉBIT RX
# mesuré en octets par rapport au PLAFOND DE BANDE PASSANTE du lien (étalonné) :
# - on coupe seulement quand le débit RX soutenu approche le plafond
# (le lien est sur le point d'être saturé / de ralentir vraiment),
# - pendant plus de PERSIST ticks consécutifs (pas un pic bref).
# Le funnel + playit sont réactivés automatiquement quand ça redescend.
set -u
WEBHOOK="https://discord.com/api/webhooks/***REMOVED***"
STATE_FILE="/home/riricdev/zektyc/scripts/.synstate"
BASE_FILE="/home/riricdev/zektyc/scripts/.synbase"
LOG="/var/log/zektyc-syn.log"
IFACE_ETH="eth0" # interface réseau publique (les SYN floods y arrivent)
IFACE_TS="tailscale0" # interface tailscale (légitime)
INTERVAL=2 # période d'échantillonnage (s)
# Plafond de bande passante DOWN mesuré via speedtest (~80 Mbit/s ≈ 10 Mo/s).
# Tout le trafic entrant (SYN floods compris) passe par eth0 et sature ce plafond.
# 10000000 octets/s × INTERVAL(2) = octets par tick au plafond.
CAP_BYTES=$(( 10000000 * INTERVAL ))
# Fraction du plafond au-delà de laquelle on considère le lien "au bord de
# tomber" (0.90 = saturé à 90 %). Un burst bref ne suffit pas : il faut tenir
# au-dessus pendant PERSIST ticks consécutifs.
CAP_RATIO=0.90
PERSIST=4 # ticks consécutifs au-dessus du seuil requis avant de couper
COOLDOWN_UP=45 # (s) attendre avant de tenter un rétablissement une fois calme
log() { printf '%s %s\n' "$(date +%F\ %T)" "$1" >>"$LOG"; }
notify() { printf '{"content":"%s"}' "$1" | curl -s -m 10 -H "Content-Type: application/json" -X POST "$WEBHOOK" -d @- >/dev/null 2>&1 || true; }
# Lit le nombre TOTAL d'OCTETS reçus (RX) sur l'interface donnée, depuis
# /proc/net/dev (2e champ après le nom d'interface, colonne RX bytes).
# La saturation de bande passante se mesure en OCTETS, pas en paquets.
read_rx() { awk -v ifx="$1" '$0 ~ ifx {print $2}' /proc/net/dev; }
# Vrai si le funnel TCP du site (:443) est actif (relais brut + PROXY protocol v2
# vers nginx 127.0.0.1:444). Le nom de domaine est suivi de ":443" - on le cherche
# explicitement pour ne pas matcher l'entree fantome sur un autre port (:10000).
funnel_active() { tailscale funnel status 2>/dev/null | grep -qE 'tcp://riricdev\.tail5ea5cd\.ts\.net:443'; }
funnel_off() {
# coupe le funnel tailscale (site web) — mode TCP brut + PROXY protocol
if funnel_active; then
tailscale funnel --tcp=443 off >/dev/null 2>&1
log "Anomalie de trafic : funnel tailscale coupé"
fi
# coupe l'entrée playit.gg (serveurs Minecraft publics)
if systemctl is-active --quiet playit.service; then
systemctl stop playit.service >/dev/null 2>&1
log "Anomalie de trafic : entrée playit coupée"
fi
notify "🔴 **Zektyc** — trafic anormal détecté, accès temporairement suspendu."
}
funnel_on() {
# réactive le funnel tailscale (site web) — TCP brut + PPv2 vers nginx :444
if ! funnel_active; then
tailscale funnel --bg --proxy-protocol=2 --tcp=443 tcp://127.0.0.1:444 >/dev/null 2>&1
log "Retour à la normale : funnel tailscale réactivé"
fi
# réactive l'entrée playit.gg (serveurs Minecraft publics)
if ! systemctl is-active --quiet playit.service; then
systemctl start playit.service >/dev/null 2>&1
log "Retour à la normale : entrée playit réactivée"
fi
notify "🟢 **Zektyc** — accès rétabli."
}
# --- init ---
now=$(read_rx "$IFACE_ETH")
prev=$now
[[ ! -f "$STATE_FILE" ]] && echo "up" >"$STATE_FILE"
state="$(<"$STATE_FILE")"
streak=0
down_since=0
# seuil de saturation en octets/tic
thresh=$(( CAP_BYTES > 0 ? CAP_BYTES : 1 ))
while true; do
now=$(read_rx "$IFACE_ETH")
delta=$(( now - prev ))
prev=$now
[[ "$delta" -lt 0 ]] && delta=0
state="$(<"$STATE_FILE")"
# --- saturation : débit RX au-dessus de CAP_RATIO × plafond du lien ---
# Un SYN flood qui sature le lien fait monter le débit RX vers le plafond.
# On ne coupe que si ça tient PERSIST ticks consécutifs (pas un pic bref).
sat=0
# delta(thresh×CAP_RATIO) ⇔ delta*100 ≥ thresh×(CAP_RATIO×100)
if (( delta * 100 >= thresh * 90 )); then
sat=1
fi
if [[ "$sat" -eq 1 ]]; then
streak=$(( streak + 1 ))
else
streak=0
fi
# --- actions ---
if [[ "$state" == "up" && "$streak" -ge "$PERSIST" ]]; then
echo down >"$STATE_FILE"
down_since=$(date +%s)
funnel_off
elif [[ "$state" == "down" ]]; then
now_s=$(date +%s)
if (( now_s - down_since >= COOLDOWN_UP && streak == 0 )); then
echo up >"$STATE_FILE"
funnel_on
fi
fi
sleep "$INTERVAL"
done