initial
This commit is contained in:
3
LICENSE
3
LICENSE
@@ -29,5 +29,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
SOFTWARE.
|
||||
126
README.md
126
README.md
@@ -1,11 +1,131 @@
|
||||
# template_repository
|
||||
```
|
||||
▄▄▄ ▓█████▄ ▄████ █ ██ ▄▄▄ ██▀███ ▓█████▄ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄
|
||||
▒████▄ ▒██▀ ██▌ ██▒ ▀█▒ ██ ▓██▒▒████▄ ▓██ ▒ ██▒▒██▀ ██▌ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌
|
||||
▒██ ▀█▄ ░██ █▌▒██░▄▄▄░▓██ ▒██░▒██ ▀█▄ ▓██ ░▄█ ▒░██ █▌ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌
|
||||
░██▄▄▄▄██ ░▓█▄ ▌░▓█ ██▓▓▓█ ░██░░██▄▄▄▄██ ▒██▀▀█▄ ░▓█▄ ▌ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌
|
||||
▓█ ▓██▒░▒████▓ ░▒▓███▀▒▒▒█████▓ ▓█ ▓██▒░██▓ ▒██▒░▒████▓ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓
|
||||
▒▒ ▓▒█░ ▒▒▓ ▒ ░▒ ▒ ░▒▓▒ ▒ ▒ ▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒▓ ▒ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒
|
||||
▒ ▒▒ ░ ░ ▒ ▒ ░ ░ ░░▒░ ░ ░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒
|
||||
░ ▒ ░ ░ ░ ░ ░ ░ ░░░ ░ ░ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
||||
░ ░ ░
|
||||
```
|
||||
|
||||
# AdGuard Shield
|
||||
|
||||
> **Autor:** Patrick Asmus | **E-Mail:** support@techniverse.net | **Version:** 1.0.0
|
||||
|
||||
Automatischer Schutz für deinen AdGuard Home DNS-Server gegen übermäßige Anfragen einzelner Clients. Überwacht die AdGuard Home API, erkennt Rate-Limit-Verstöße und sperrt missbrauchende Clients per iptables — für alle DNS-Protokolle (DNS, DoH, DoT, DoQ).
|
||||
|
||||
Wichtig: Link für Lizenz anpassen.
|
||||
## Was macht das Tool?
|
||||
|
||||
Wenn ein Client eine bestimmte Domain zu oft anfragt (z.B. >30x pro Minute), wird er automatisch auf Firewall-Ebene für alle DNS-Ports gesperrt. Nach einer konfigurierbaren Zeitspanne wird die Sperre automatisch aufgehoben.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatische Erkennung und Sperre bei Rate-Limit-Verstößen
|
||||
- Unterstützt **alle DNS-Protokolle**: DNS (53), DoH (443), DoT (853), DoQ (784/853/8853)
|
||||
- **IPv4 + IPv6**
|
||||
- Eigene iptables Chain — greift nicht in bestehende Regeln ein
|
||||
- Automatisches Entsperren nach konfigurierbarer Dauer
|
||||
- **Externe Blocklisten** — IP-Adressen von externen Textdateien (URLs) laden und automatisch sperren
|
||||
- **Ban-History** — lückenlose Protokollierung aller Sperren/Entsperrungen mit Zeitstempel
|
||||
- Whitelist für vertrauenswürdige IPs
|
||||
- Dry-Run Modus zum gefahrlosen Testen
|
||||
- Benachrichtigungen (Discord, Slack, Gotify, Ntfy)
|
||||
- systemd Service für dauerhaften Betrieb
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- Linux Server mit AdGuard Home (bare metal)
|
||||
- `curl`, `jq`, `iptables` / `ip6tables`
|
||||
- Root-Zugriff
|
||||
- AdGuard Home Web-API erreichbar (Standard: Port 3000)
|
||||
|
||||
## Schnellstart
|
||||
|
||||
```bash
|
||||
# 1. Repository klonen
|
||||
git clone <repo-url> /tmp/adguard-security
|
||||
cd /tmp/adguard-security
|
||||
|
||||
# 2. Installer ausführen (fragt interaktiv nach Zugangsdaten & Einstellungen)
|
||||
sudo bash install.sh install
|
||||
|
||||
# 3. Erst im Dry-Run testen (loggt nur, sperrt nichts)
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh dry-run
|
||||
|
||||
# 4. Wenn alles passt — Service starten
|
||||
sudo systemctl start adguard-ratelimit
|
||||
sudo systemctl status adguard-ratelimit
|
||||
```
|
||||
|
||||
## Wichtigste Befehle
|
||||
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh status # Aktive Sperren anzeigen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history # Ban-History anzeigen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh unban IP # Einzelne IP entsperren
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh flush # Alle Sperren aufheben
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh test # API-Verbindung testen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh blocklist-status # Externe Blocklisten Status
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh blocklist-sync # Blocklisten manuell synchronisieren
|
||||
sudo journalctl -u adguard-ratelimit -f # Logs live verfolgen
|
||||
```
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```
|
||||
├── adguard-ratelimit.sh # Haupt-Monitor-Script
|
||||
├── adguard-ratelimit.conf # Konfiguration
|
||||
├── adguard-ratelimit.service # systemd Unit
|
||||
├── external-blocklist-worker.sh # Externer Blocklist-Worker
|
||||
├── iptables-helper.sh # Manuelle iptables-Verwaltung
|
||||
├── unban-expired.sh # Cron-basiertes Entsperren
|
||||
├── install.sh # Installer / Uninstaller
|
||||
├── README.md
|
||||
└── doc/
|
||||
├── architektur.md # Architektur & Funktionsweise
|
||||
├── konfiguration.md # Alle Parameter erklärt
|
||||
├── befehle.md # Vollständige Befehlsreferenz
|
||||
├── benachrichtigungen.md # Webhook-Setup (Discord, Slack, Gotify)
|
||||
└── tipps-und-troubleshooting.md
|
||||
```
|
||||
|
||||
## Dokumentation
|
||||
|
||||
| Dokument | Inhalt |
|
||||
|----------|--------|
|
||||
| [Architektur](doc/architektur.md) | Wie das Tool funktioniert, iptables-Strategie, Ablauf einer Sperre |
|
||||
| [Konfiguration](doc/konfiguration.md) | Alle Parameter, Ports, Whitelist-Pflege, externe Blocklisten |
|
||||
| [Befehle](doc/befehle.md) | Vollständige Befehlsreferenz für Monitor, iptables-Helper und systemd |
|
||||
| [Benachrichtigungen](doc/benachrichtigungen.md) | Setup für Discord, Slack, Gotify, Ntfy |
|
||||
| [Tipps & Troubleshooting](doc/tipps-und-troubleshooting.md) | Best Practices, häufige Probleme, Deinstallation |
|
||||
|
||||
## Lizenz
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## 👥 Techniverse Community
|
||||
|
||||
Lust auf Austausch rund um Matrix, Selfhosting und andere smarte IT-Lösungen?
|
||||
In der **Techniverse Community** triffst du Gleichgesinnte, kannst Fragen stellen oder einfach nerdigen Talk genießen. 🚀
|
||||
|
||||
👉 **[Jetzt der Gruppe auf Matrix beitreten](https://matrix.to/#/#community:techniverse.net)**
|
||||
~ Direkte Raumadresse: `#community:techniverse.net`
|
||||
|
||||
👉 **[Für lockere Gespräche abseits der Kernthemen komm in den Talkraum](https://matrix.to/#/#talk:techniverse.net)**
|
||||
~ Direkte Raumadresse: `#talk:techniverse.net`
|
||||
|
||||
Wir freuen uns, wenn du dabei bist!
|
||||
|
||||
---
|
||||
|
||||
📝 **Blog:** [www.cleveradmin.de](https://www.cleveradmin.de)
|
||||
🌐 **Webseite:** [www.patrick-asmus.de](https://www.patrick-asmus.de)
|
||||
📧 **E-Mail:** [support@techniverse.net](mailto:support@techniverse.net)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://assets.techniverse.net/f1/git/graphics/gray0-catonline.svg" alt="">
|
||||
@@ -13,4 +133,4 @@ Wichtig: Link für Lizenz anpassen.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://assets.techniverse.net/f1/logos/small/license.png" alt="License" width="15" height="15"> <a href="./LICENSE">License</a> | <img src="https://assets.techniverse.net/f1/logos/small/matrix2.svg" alt="Matrix" width="15" height="15"> <a href="https://matrix.to/#/#community:techniverse.net">Matrix</a> | <img src="https://assets.techniverse.net/f1/logos/small/mastodon2.svg" alt="Mastodon" width="15" height="15"> <a href="https://social.techniverse.net/@donnerwolke">Mastodon</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
114
adguard-ratelimit.conf
Normal file
114
adguard-ratelimit.conf
Normal file
@@ -0,0 +1,114 @@
|
||||
###############################################################################
|
||||
# AdGuard Shield - Konfigurationsdatei
|
||||
# Schutz vor übermäßigen DNS-Anfragen einzelner Clients
|
||||
###############################################################################
|
||||
|
||||
# --- AdGuard Home API Einstellungen ---
|
||||
# URL der AdGuard Home Web-Oberfläche (ohne trailing slash)
|
||||
ADGUARD_URL="http://127.0.0.1:3000"
|
||||
|
||||
# AdGuard Home Zugangsdaten (Web-UI Login)
|
||||
ADGUARD_USER="admin"
|
||||
ADGUARD_PASS='changeme'
|
||||
|
||||
# --- Rate-Limit Einstellungen ---
|
||||
# Maximale Anfragen pro Domain pro Client innerhalb des Zeitfensters
|
||||
RATE_LIMIT_MAX_REQUESTS=30
|
||||
|
||||
# Zeitfenster in Sekunden (60 = 1 Minute)
|
||||
RATE_LIMIT_WINDOW=60
|
||||
|
||||
# Wie oft das Script die Logs prüft (in Sekunden)
|
||||
CHECK_INTERVAL=10
|
||||
|
||||
# --- Sperr-Einstellungen ---
|
||||
# Wie lange ein Client gesperrt wird (in Sekunden, 3600 = 1 Stunde)
|
||||
BAN_DURATION=3600
|
||||
|
||||
# iptables Chain-Name für die Sperren
|
||||
IPTABLES_CHAIN="ADGUARD_RATELIMIT"
|
||||
|
||||
# Welche Ports gesperrt werden sollen (DNS, DoT, DoH, DNSv5/QUIC)
|
||||
# Port 53 = DNS (UDP + TCP)
|
||||
# Port 443 = DNS-over-HTTPS (DoH)
|
||||
# Port 853 = DNS-over-TLS (DoT) / DNS-over-QUIC
|
||||
# Port 784 = DNS-over-QUIC (alternativ)
|
||||
# Port 8853 = DNS-over-QUIC (alternativ)
|
||||
BLOCKED_PORTS="53 443 853 784 8853"
|
||||
|
||||
# --- Whitelist ---
|
||||
# IP-Adressen die NIEMALS gesperrt werden (kommagetrennt)
|
||||
# Lokale Netze und wichtige Server hier eintragen
|
||||
WHITELIST="127.0.0.1,::1"
|
||||
|
||||
# --- Logging ---
|
||||
# Log-Datei Pfad
|
||||
LOG_FILE="/var/log/adguard-ratelimit.log"
|
||||
|
||||
# Log-Level: DEBUG, INFO, WARN, ERROR
|
||||
LOG_LEVEL="INFO"
|
||||
|
||||
# Maximale Größe der Log-Datei in MB (danach wird rotiert)
|
||||
LOG_MAX_SIZE_MB=50
|
||||
|
||||
# Ban-History Datei (protokolliert alle Sperren & Entsperrungen dauerhaft)
|
||||
BAN_HISTORY_FILE="/var/log/adguard-ratelimit-bans.log"
|
||||
|
||||
# --- Benachrichtigungen (optional) ---
|
||||
# Aktiviert Benachrichtigungen bei Sperren
|
||||
NOTIFY_ENABLED=false
|
||||
|
||||
# Webhook-URL für Benachrichtigungen (z.B. Discord, Slack, Gotify)
|
||||
# Discord: https://discord.com/api/webhooks/xxx/yyy
|
||||
# Gotify: https://gotify.example.com/message?token=xxx
|
||||
NOTIFY_WEBHOOK_URL=""
|
||||
|
||||
# Benachrichtigungs-Typ: "discord", "slack", "gotify", "ntfy", "generic"
|
||||
NOTIFY_TYPE="generic"
|
||||
|
||||
# --- Ntfy Einstellungen (nur bei NOTIFY_TYPE="ntfy") ---
|
||||
# Server-URL der Ntfy-Instanz (ohne trailing slash)
|
||||
NTFY_SERVER_URL="https://ntfy.sh"
|
||||
|
||||
# Topic-Name für die Benachrichtigungen
|
||||
NTFY_TOPIC=""
|
||||
|
||||
# Optionaler Access-Token (leer lassen wenn nicht benötigt)
|
||||
NTFY_TOKEN=""
|
||||
|
||||
# Priorität der Ntfy-Nachrichten (1=min, 3=default, 5=max)
|
||||
NTFY_PRIORITY="1"
|
||||
|
||||
# --- Externe Blocklist (optional) ---
|
||||
# Aktiviert den externen Blocklist-Worker
|
||||
EXTERNAL_BLOCKLIST_ENABLED=false
|
||||
|
||||
# URL(s) zu externen Textdateien mit IP-Adressen (eine IP pro Zeile)
|
||||
# Mehrere URLs kommagetrennt angeben
|
||||
# Beispiel: "https://example.com/blocklist.txt,https://other.com/bad-ips.txt"
|
||||
EXTERNAL_BLOCKLIST_URLS=""
|
||||
|
||||
# Wie oft die externe Blocklist geprüft wird (in Sekunden, 300 = 5 Minuten)
|
||||
EXTERNAL_BLOCKLIST_INTERVAL=300
|
||||
|
||||
# Sperrdauer für externe Blocklist-IPs in Sekunden (0 = permanent bis IP aus Liste entfernt)
|
||||
EXTERNAL_BLOCKLIST_BAN_DURATION=0
|
||||
|
||||
# Automatisch IPs entsperren die aus der externen Liste entfernt wurden?
|
||||
EXTERNAL_BLOCKLIST_AUTO_UNBAN=true
|
||||
|
||||
# Lokaler Cache-Pfad für die heruntergeladene Blocklist
|
||||
EXTERNAL_BLOCKLIST_CACHE_DIR="/var/lib/adguard-ratelimit/external-blocklist"
|
||||
|
||||
# --- Erweiterte Einstellungen ---
|
||||
# Pfad zur State-Datei (speichert aktive Sperren)
|
||||
STATE_DIR="/var/lib/adguard-ratelimit"
|
||||
|
||||
# Pfad zum PID-File
|
||||
PID_FILE="/var/run/adguard-ratelimit.pid"
|
||||
|
||||
# Anzahl der API-Einträge die pro Abfrage geholt werden (max 5000)
|
||||
API_QUERY_LIMIT=500
|
||||
|
||||
# Dry-Run Modus: true = nur loggen, nicht sperren (zum Testen)
|
||||
DRY_RUN=false
|
||||
36
adguard-ratelimit.service
Normal file
36
adguard-ratelimit.service
Normal file
@@ -0,0 +1,36 @@
|
||||
[Unit]
|
||||
Description=AdGuard Shield - DNS Rate-Limit Monitor
|
||||
Documentation=https://github.com/your-repo/adguard-security
|
||||
After=network.target AdGuardHome.service
|
||||
Wants=AdGuardHome.service
|
||||
StartLimitBurst=5
|
||||
StartLimitIntervalSec=60
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/adguard-ratelimit/adguard-ratelimit.sh start
|
||||
ExecStop=/opt/adguard-ratelimit/adguard-ratelimit.sh stop
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
|
||||
# Neustart-Verhalten
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
# Sicherheits-Hardening
|
||||
ProtectSystem=full
|
||||
ReadWritePaths=/var/log /var/lib/adguard-ratelimit /var/lib/adguard-ratelimit/external-blocklist /var/run
|
||||
ProtectHome=true
|
||||
NoNewPrivileges=false
|
||||
PrivateTmp=true
|
||||
|
||||
# iptables benötigt CAP_NET_ADMIN
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=adguard-ratelimit
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
711
adguard-ratelimit.sh
Normal file
711
adguard-ratelimit.sh
Normal file
@@ -0,0 +1,711 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield
|
||||
# Überwacht DNS-Anfragen und sperrt Clients bei Überschreitung des Limits
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Datum: 2026-03-03
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="1.0.0"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-ratelimit.conf"
|
||||
|
||||
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=adguard-ratelimit.conf
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ─── Abhängigkeiten prüfen ────────────────────────────────────────────────────
|
||||
check_dependencies() {
|
||||
local missing=()
|
||||
for cmd in curl jq iptables ip6tables date; do
|
||||
if ! command -v "$cmd" &>/dev/null; then
|
||||
missing+=("$cmd")
|
||||
fi
|
||||
done
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
log "ERROR" "Fehlende Abhängigkeiten: ${missing[*]}"
|
||||
echo "Bitte installieren: sudo apt install ${missing[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Logging ──────────────────────────────────────────────────────────────────
|
||||
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
local message="$*"
|
||||
local configured_level="${LOG_LEVEL:-INFO}"
|
||||
|
||||
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
local log_entry="[$timestamp] [$level] $message"
|
||||
|
||||
echo "$log_entry" | tee -a "$LOG_FILE"
|
||||
|
||||
# Log-Rotation prüfen
|
||||
if [[ -f "$LOG_FILE" ]]; then
|
||||
local size_kb
|
||||
size_kb=$(du -k "$LOG_FILE" 2>/dev/null | cut -f1)
|
||||
local max_kb=$((LOG_MAX_SIZE_MB * 1024))
|
||||
if [[ ${size_kb:-0} -gt $max_kb ]]; then
|
||||
mv "$LOG_FILE" "${LOG_FILE}.old"
|
||||
log "INFO" "Log-Datei rotiert"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Ban-History ─────────────────────────────────────────────────────────────
|
||||
log_ban_history() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local domain="${3:-}"
|
||||
local count="${4:-}"
|
||||
local reason="${5:-}"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
# Header schreiben falls Datei neu ist
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | GRUND" >> "$BAN_HISTORY_FILE"
|
||||
echo "#───────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
||||
fi
|
||||
|
||||
local duration="-"
|
||||
[[ "$action" == "BAN" ]] && duration="${BAN_DURATION}s"
|
||||
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %s\n" \
|
||||
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "$duration" "${reason:-rate-limit}" \
|
||||
>> "$BAN_HISTORY_FILE"
|
||||
}
|
||||
|
||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||
init_directories() {
|
||||
mkdir -p "$STATE_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
mkdir -p "$(dirname "$PID_FILE")"
|
||||
mkdir -p "$(dirname "$BAN_HISTORY_FILE")"
|
||||
}
|
||||
|
||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||
write_pid() {
|
||||
echo $$ > "$PID_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "INFO" "AdGuard Shield wird beendet..."
|
||||
stop_blocklist_worker
|
||||
rm -f "$PID_FILE"
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_already_running() {
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
local old_pid
|
||||
old_pid=$(cat "$PID_FILE")
|
||||
if kill -0 "$old_pid" 2>/dev/null; then
|
||||
echo "Monitor läuft bereits (PID: $old_pid). Beende." >&2
|
||||
exit 1
|
||||
else
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
||||
is_whitelisted() {
|
||||
local ip="$1"
|
||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||
for entry in "${wl_entries[@]}"; do
|
||||
entry=$(echo "$entry" | xargs) # trim
|
||||
if [[ "$ip" == "$entry" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── iptables Chain Setup ────────────────────────────────────────────────────
|
||||
setup_iptables_chain() {
|
||||
# IPv4 Chain erstellen falls nicht vorhanden
|
||||
if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
log "INFO" "Erstelle iptables Chain: $IPTABLES_CHAIN (IPv4)"
|
||||
iptables -N "$IPTABLES_CHAIN"
|
||||
|
||||
# Chain in INPUT einhängen für alle relevanten Ports
|
||||
for port in $BLOCKED_PORTS; do
|
||||
iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
|
||||
# IPv6 Chain erstellen falls nicht vorhanden
|
||||
if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
log "INFO" "Erstelle ip6tables Chain: $IPTABLES_CHAIN (IPv6)"
|
||||
ip6tables -N "$IPTABLES_CHAIN"
|
||||
|
||||
for port in $BLOCKED_PORTS; do
|
||||
ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Client sperren ─────────────────────────────────────────────────────────
|
||||
ban_client() {
|
||||
local client_ip="$1"
|
||||
local domain="$2"
|
||||
local count="$3"
|
||||
local ban_until
|
||||
ban_until=$(date -d "+${BAN_DURATION} seconds" '+%s' 2>/dev/null || date -v "+${BAN_DURATION}S" '+%s')
|
||||
|
||||
# Prüfen ob bereits gesperrt
|
||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||
if [[ -f "$state_file" ]]; then
|
||||
log "DEBUG" "Client $client_ip ist bereits gesperrt"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${RATE_LIMIT_WINDOW}s)"
|
||||
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${RATE_LIMIT_WINDOW}s) für ${BAN_DURATION}s"
|
||||
|
||||
# IPv4 oder IPv6 erkennen
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
# IPv6
|
||||
ip6tables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
# IPv4
|
||||
iptables -I "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# State speichern
|
||||
cat > "$state_file" << EOF
|
||||
CLIENT_IP=$client_ip
|
||||
DOMAIN=$domain
|
||||
COUNT=$count
|
||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
BAN_UNTIL_EPOCH=$ban_until
|
||||
BAN_UNTIL=$(date -d "@$ban_until" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -r "$ban_until" '+%Y-%m-%d %H:%M:%S')
|
||||
EOF
|
||||
|
||||
# Ban-History Eintrag
|
||||
log_ban_history "BAN" "$client_ip" "$domain" "$count" "rate-limit"
|
||||
|
||||
# Benachrichtigung senden
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "ban" "$client_ip" "$domain" "$count"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Client entsperren ──────────────────────────────────────────────────────
|
||||
unban_client() {
|
||||
local client_ip="$1"
|
||||
local reason="${2:-expired}"
|
||||
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
|
||||
|
||||
# Domain aus State lesen bevor wir löschen
|
||||
local domain="-"
|
||||
if [[ -f "$state_file" ]]; then
|
||||
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2)
|
||||
fi
|
||||
|
||||
log "INFO" "ENTSPERRE Client: $client_ip ($reason)"
|
||||
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$state_file"
|
||||
|
||||
# Ban-History Eintrag
|
||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason"
|
||||
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "unban" "$client_ip" "" ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Abgelaufene Sperren aufheben ───────────────────────────────────────────
|
||||
check_expired_bans() {
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
|
||||
for state_file in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
|
||||
local ban_until_epoch
|
||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2)
|
||||
local client_ip
|
||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
|
||||
if [[ -n "$ban_until_epoch" && "$now" -ge "$ban_until_epoch" ]]; then
|
||||
unban_client "$client_ip" "expired"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Benachrichtigungen ─────────────────────────────────────────────────────
|
||||
send_notification() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local domain="$3"
|
||||
local count="$4"
|
||||
|
||||
[[ -z "$NOTIFY_WEBHOOK_URL" ]] && return
|
||||
|
||||
local message
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
message="🚫 DNS Rate-Limit: Client **$client_ip** gesperrt (${count}x $domain in ${RATE_LIMIT_WINDOW}s). Sperre für ${BAN_DURATION}s."
|
||||
else
|
||||
message="✅ DNS Rate-Limit: Client **$client_ip** wurde entsperrt."
|
||||
fi
|
||||
|
||||
case "$NOTIFY_TYPE" in
|
||||
discord)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"content\": \"$message\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
slack)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"text\": \"$message\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
gotify)
|
||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||
-F "title=AdGuard Rate-Limit" \
|
||||
-F "message=$message" \
|
||||
-F "priority=5" &>/dev/null &
|
||||
;;
|
||||
ntfy)
|
||||
send_ntfy_notification "$action" "$message"
|
||||
;;
|
||||
generic)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"message\": \"$message\", \"action\": \"$action\", \"client\": \"$client_ip\", \"domain\": \"$domain\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Ntfy Benachrichtigung ───────────────────────────────────────────────────
|
||||
send_ntfy_notification() {
|
||||
local action="$1"
|
||||
local message="$2"
|
||||
|
||||
if [[ -z "${NTFY_TOPIC:-}" ]]; then
|
||||
log "WARN" "Ntfy: Kein Topic konfiguriert (NTFY_TOPIC ist leer)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||
local priority="${NTFY_PRIORITY:-4}"
|
||||
local title="AdGuard Rate-Limit"
|
||||
local tags
|
||||
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
tags="rotating_light,ban"
|
||||
else
|
||||
tags="white_check_mark,unban"
|
||||
fi
|
||||
|
||||
# Markdown-Formatierung entfernen für Ntfy
|
||||
local clean_message
|
||||
clean_message=$(echo "$message" | sed 's/\*\*//g')
|
||||
|
||||
local -a curl_args=(
|
||||
-s
|
||||
-X POST
|
||||
"${ntfy_url}/${NTFY_TOPIC}"
|
||||
-H "Title: ${title}"
|
||||
-H "Priority: ${priority}"
|
||||
-H "Tags: ${tags}"
|
||||
-d "${clean_message}"
|
||||
)
|
||||
|
||||
# Token hinzufügen falls konfiguriert
|
||||
if [[ -n "${NTFY_TOKEN:-}" ]]; then
|
||||
curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||
fi
|
||||
|
||||
curl "${curl_args[@]}" &>/dev/null &
|
||||
}
|
||||
|
||||
# ─── AdGuard Home API abfragen ──────────────────────────────────────────────
|
||||
query_adguard_log() {
|
||||
local time_from
|
||||
time_from=$(date -u -d "-${RATE_LIMIT_WINDOW} seconds" '+%Y-%m-%dT%H:%M:%S.000Z' 2>/dev/null \
|
||||
|| date -u -v "-${RATE_LIMIT_WINDOW}S" '+%Y-%m-%dT%H:%M:%S.000Z')
|
||||
|
||||
local response
|
||||
response=$(curl -s -u "${ADGUARD_USER}:${ADGUARD_PASS}" \
|
||||
--connect-timeout 5 \
|
||||
--max-time 10 \
|
||||
"${ADGUARD_URL}/control/querylog?limit=${API_QUERY_LIMIT}&response_status=all" 2>/dev/null)
|
||||
|
||||
if [[ -z "$response" || "$response" == "null" ]]; then
|
||||
log "ERROR" "Keine Antwort von AdGuard Home API"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Prüfen ob die Antwort gültiges JSON ist
|
||||
if ! echo "$response" | jq . &>/dev/null; then
|
||||
log "ERROR" "Ungültige API-Antwort (kein JSON)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# ─── Anfragen analysieren ───────────────────────────────────────────────────
|
||||
analyze_queries() {
|
||||
local api_response="$1"
|
||||
local now_epoch
|
||||
now_epoch=$(date '+%s')
|
||||
local window_start=$((now_epoch - RATE_LIMIT_WINDOW))
|
||||
|
||||
# Extrahiere Client-IP + Domain Paare aus dem Zeitfenster
|
||||
# und zähle die Häufigkeit pro (client, domain) Kombination
|
||||
local violations
|
||||
violations=$(echo "$api_response" | jq -r --argjson window_start "$window_start" '
|
||||
.data // [] |
|
||||
[.[] |
|
||||
select(.time != null) |
|
||||
{
|
||||
client: (.client // .client_info.ip // "unknown"),
|
||||
domain: (.question.name // "unknown" | rtrimstr(".")),
|
||||
time_epoch: (.time | split(".")[0] | sub("T"; " ") | sub("Z$"; "") )
|
||||
}
|
||||
] |
|
||||
group_by(.client + "|" + .domain) |
|
||||
map({
|
||||
client: .[0].client,
|
||||
domain: .[0].domain,
|
||||
count: length
|
||||
}) |
|
||||
.[] |
|
||||
select(.count > 0) |
|
||||
"\(.client)|\(.domain)|\(.count)"
|
||||
' 2>/dev/null)
|
||||
|
||||
if [[ -z "$violations" ]]; then
|
||||
log "DEBUG" "Keine Anfragen im Zeitfenster gefunden"
|
||||
return
|
||||
fi
|
||||
|
||||
# Prüfe jede Kombination gegen das Limit
|
||||
while IFS='|' read -r client domain count; do
|
||||
[[ -z "$client" || -z "$domain" || -z "$count" ]] && continue
|
||||
|
||||
log "DEBUG" "Client: $client, Domain: $domain, Anfragen: $count"
|
||||
|
||||
if [[ "$count" -gt "$RATE_LIMIT_MAX_REQUESTS" ]]; then
|
||||
if is_whitelisted "$client"; then
|
||||
log "INFO" "Client $client ist auf der Whitelist - keine Sperre (${count}x $domain)"
|
||||
continue
|
||||
fi
|
||||
|
||||
ban_client "$client" "$domain" "$count"
|
||||
fi
|
||||
done <<< "$violations"
|
||||
}
|
||||
|
||||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||
show_status() {
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " AdGuard Home Rate-Limit Monitor - Status"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# Aktive Sperren
|
||||
local ban_count=0
|
||||
if [[ -d "$STATE_DIR" ]]; then
|
||||
for state_file in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
ban_count=$((ban_count + 1))
|
||||
echo " 🚫 Gesperrt:"
|
||||
while IFS='=' read -r key value; do
|
||||
printf " %-20s %s\n" "$key:" "$value"
|
||||
done < "$state_file"
|
||||
echo ""
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $ban_count -eq 0 ]]; then
|
||||
echo " ✅ Keine aktiven Sperren"
|
||||
else
|
||||
echo " Gesamt: $ban_count aktive Sperren"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# iptables Regeln anzeigen
|
||||
echo " iptables Regeln ($IPTABLES_CHAIN):"
|
||||
if iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
iptables -n -L "$IPTABLES_CHAIN" --line-numbers 2>/dev/null | sed 's/^/ /'
|
||||
else
|
||||
echo " Chain existiert noch nicht"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Ban-History anzeigen ────────────────────────────────────────────────────
|
||||
show_history() {
|
||||
local lines="${1:-50}"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " AdGuard Home Rate-Limit - Ban History (letzte $lines Einträge)"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
echo " Noch keine History vorhanden."
|
||||
echo " Datei: $BAN_HISTORY_FILE"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# Header zeigen
|
||||
head -3 "$BAN_HISTORY_FILE" | sed 's/^/ /'
|
||||
echo ""
|
||||
|
||||
# Letzte N Einträge (ohne Header-Zeilen)
|
||||
grep -v '^#' "$BAN_HISTORY_FILE" | tail -n "$lines" | sed 's/^/ /'
|
||||
|
||||
echo ""
|
||||
local total
|
||||
total=$(grep -vc '^#' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
||||
local bans
|
||||
bans=$(grep -c '| BAN ' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
||||
local unbans
|
||||
unbans=$(grep -c '| UNBAN ' "$BAN_HISTORY_FILE" 2>/dev/null || echo "0")
|
||||
echo " Gesamt: $total Einträge ($bans Sperren, $unbans Entsperrungen)"
|
||||
echo " Datei: $BAN_HISTORY_FILE"
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Alle Sperren aufheben ──────────────────────────────────────────────────
|
||||
flush_all_bans() {
|
||||
log "INFO" "Alle Sperren werden aufgehoben..."
|
||||
|
||||
for state_file in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
local client_ip
|
||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
unban_client "$client_ip" "manual-flush"
|
||||
done
|
||||
|
||||
# Chain leeren
|
||||
iptables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
ip6tables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
|
||||
log "INFO" "Alle Sperren aufgehoben"
|
||||
}
|
||||
|
||||
# ─── Externer Blocklist-Worker starten ───────────────────────────────────────
|
||||
start_blocklist_worker() {
|
||||
if [[ "${EXTERNAL_BLOCKLIST_ENABLED:-false}" != "true" ]]; then
|
||||
log "DEBUG" "Externer Blocklist-Worker ist deaktiviert"
|
||||
return
|
||||
fi
|
||||
|
||||
local worker_script="${SCRIPT_DIR}/external-blocklist-worker.sh"
|
||||
if [[ ! -f "$worker_script" ]]; then
|
||||
log "WARN" "Blocklist-Worker Script nicht gefunden: $worker_script"
|
||||
return
|
||||
fi
|
||||
|
||||
log "INFO" "Starte externen Blocklist-Worker im Hintergrund..."
|
||||
bash "$worker_script" start &
|
||||
BLOCKLIST_WORKER_PID=$!
|
||||
log "INFO" "Blocklist-Worker gestartet (PID: $BLOCKLIST_WORKER_PID)"
|
||||
}
|
||||
|
||||
# ─── Externer Blocklist-Worker stoppen ───────────────────────────────────────
|
||||
stop_blocklist_worker() {
|
||||
local worker_pid_file="/var/run/adguard-blocklist-worker.pid"
|
||||
if [[ -f "$worker_pid_file" ]]; then
|
||||
local wpid
|
||||
wpid=$(cat "$worker_pid_file")
|
||||
if kill -0 "$wpid" 2>/dev/null; then
|
||||
log "INFO" "Stoppe Blocklist-Worker (PID: $wpid)..."
|
||||
kill "$wpid" 2>/dev/null || true
|
||||
rm -f "$worker_pid_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
log "INFO" "AdGuard Shield v${VERSION} gestartet"
|
||||
log "INFO" " Limit: ${RATE_LIMIT_MAX_REQUESTS} Anfragen pro ${RATE_LIMIT_WINDOW}s"
|
||||
log "INFO" " Sperrdauer: ${BAN_DURATION}s"
|
||||
log "INFO" " Prüfintervall: ${CHECK_INTERVAL}s"
|
||||
log "INFO" " Dry-Run: ${DRY_RUN}"
|
||||
log "INFO" " Whitelist: ${WHITELIST}"
|
||||
log "INFO" " Externe Blocklist: ${EXTERNAL_BLOCKLIST_ENABLED:-false}"
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
|
||||
# Blocklist-Worker als Hintergrundprozess starten
|
||||
start_blocklist_worker
|
||||
|
||||
while true; do
|
||||
# Abgelaufene Sperren prüfen
|
||||
check_expired_bans
|
||||
|
||||
# API abfragen
|
||||
local api_response
|
||||
if api_response=$(query_adguard_log); then
|
||||
analyze_queries "$api_response"
|
||||
fi
|
||||
|
||||
sleep "$CHECK_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Signal-Handler ──────────────────────────────────────────────────────────
|
||||
trap cleanup SIGTERM SIGINT SIGHUP
|
||||
|
||||
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
check_dependencies
|
||||
check_already_running
|
||||
init_directories
|
||||
write_pid
|
||||
setup_iptables_chain
|
||||
main_loop
|
||||
;;
|
||||
stop)
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
kill "$(cat "$PID_FILE")" 2>/dev/null || true
|
||||
rm -f "$PID_FILE"
|
||||
echo "Monitor gestoppt"
|
||||
else
|
||||
echo "Monitor läuft nicht"
|
||||
fi
|
||||
;;
|
||||
blocklist-status)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/external-blocklist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" status
|
||||
else
|
||||
echo "Blocklist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
blocklist-sync)
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
_worker_script="${SCRIPT_DIR}/external-blocklist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" sync
|
||||
else
|
||||
echo "Blocklist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
blocklist-flush)
|
||||
init_directories
|
||||
_worker_script="${SCRIPT_DIR}/external-blocklist-worker.sh"
|
||||
if [[ -f "$_worker_script" ]]; then
|
||||
bash "$_worker_script" flush
|
||||
else
|
||||
echo "Blocklist-Worker nicht gefunden"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
;;
|
||||
flush)
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
flush_all_bans
|
||||
echo "Alle Sperren aufgehoben"
|
||||
;;
|
||||
unban)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Nutzung: $0 unban <IP-Adresse>" >&2
|
||||
exit 1
|
||||
fi
|
||||
init_directories
|
||||
unban_client "$2" "manual"
|
||||
echo "Client $2 entsperrt"
|
||||
;;
|
||||
test)
|
||||
echo "Teste Verbindung zur AdGuard Home API..."
|
||||
check_dependencies
|
||||
init_directories
|
||||
if response=$(query_adguard_log); then
|
||||
entry_count=$(echo "$response" | jq '.data | length' 2>/dev/null || echo "0")
|
||||
echo "✅ Verbindung erfolgreich! $entry_count Log-Einträge gefunden."
|
||||
else
|
||||
echo "❌ Verbindung fehlgeschlagen! Prüfe URL und Zugangsdaten in $CONFIG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
history)
|
||||
init_directories
|
||||
show_history "${2:-50}"
|
||||
;;
|
||||
dry-run)
|
||||
DRY_RUN=true
|
||||
check_dependencies
|
||||
check_already_running
|
||||
init_directories
|
||||
write_pid
|
||||
setup_iptables_chain
|
||||
main_loop
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield v${VERSION}
|
||||
|
||||
Nutzung: $0 {start|stop|status|history|flush|unban|test|dry-run|blocklist-status|blocklist-sync|blocklist-flush}
|
||||
|
||||
Befehle:
|
||||
start Startet den Monitor (inkl. Blocklist-Worker)
|
||||
stop Stoppt den Monitor
|
||||
status Zeigt aktive Sperren und Regeln
|
||||
history [N] Zeigt die letzten N Ban-Einträge (Standard: 50)
|
||||
flush Hebt alle Sperren auf
|
||||
unban IP Entsperrt eine bestimmte IP-Adresse
|
||||
test Testet die Verbindung zur AdGuard Home API
|
||||
dry-run Startet im Testmodus (keine echten Sperren)
|
||||
blocklist-status Zeigt Status der externen Blocklisten
|
||||
blocklist-sync Einmalige Synchronisation der externen Blocklisten
|
||||
blocklist-flush Entfernt alle Sperren der externen Blocklisten
|
||||
|
||||
Konfiguration: $CONFIG_FILE
|
||||
Log-Datei: $LOG_FILE
|
||||
Ban-History: $BAN_HISTORY_FILE
|
||||
State: $STATE_DIR
|
||||
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
138
doc/architektur.md
Normal file
138
doc/architektur.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Architektur & Funktionsweise
|
||||
|
||||
## Überblick
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Client Anfragen │
|
||||
│ (DNS/DoH/DoT/DoQ) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐ ┌──────────────────────┐
|
||||
│ AdGuard Home │────▶│ Query Log (API) │
|
||||
│ DNS Server │ └──────────┬───────────┘
|
||||
└─────────────────────┘ │
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ adguard-ratelimit.sh │
|
||||
│ (Monitor Script) │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ iptables │ │ Log │ │ Webhook │
|
||||
│ DROP │ │ Datei │ │ Notify │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## Ablauf einer Sperre
|
||||
|
||||
1. Client `192.168.1.50` fragt `microsoft.com` 45x in 60 Sekunden an
|
||||
2. Monitor fragt die AdGuard Home API alle 10 Sekunden ab (`/control/querylog`)
|
||||
3. Die Anfragen werden pro Client+Domain-Kombination gezählt
|
||||
4. Monitor erkennt: 45 > 30 (Limit überschritten)
|
||||
5. Prüfung: Ist der Client auf der Whitelist? → Nein
|
||||
6. iptables-Regel wird erstellt: `DROP` für `192.168.1.50` auf allen DNS-Ports
|
||||
7. State-Datei wird angelegt: `/var/lib/adguard-ratelimit/192.168.1.50.ban`
|
||||
8. Ban-History Eintrag wird in `/var/log/adguard-ratelimit-bans.log` geschrieben
|
||||
9. Log-Eintrag + optionale Webhook-Benachrichtigung
|
||||
10. Nach 3600 Sekunden (1 Stunde): automatische Entsperrung + History-Eintrag
|
||||
|
||||
## iptables Strategie
|
||||
|
||||
Das Tool erstellt eine eigene Chain `ADGUARD_RATELIMIT`:
|
||||
|
||||
```
|
||||
INPUT Chain
|
||||
├── ... (bestehende Regeln bleiben unberührt)
|
||||
├── -p tcp --dport 53 → ADGUARD_RATELIMIT
|
||||
├── -p udp --dport 53 → ADGUARD_RATELIMIT
|
||||
├── -p tcp --dport 443 → ADGUARD_RATELIMIT
|
||||
├── -p udp --dport 443 → ADGUARD_RATELIMIT
|
||||
├── -p tcp --dport 853 → ADGUARD_RATELIMIT
|
||||
├── -p udp --dport 853 → ADGUARD_RATELIMIT
|
||||
└── ...
|
||||
|
||||
ADGUARD_RATELIMIT Chain
|
||||
├── -s 192.168.1.50 → DROP (gesperrter Client)
|
||||
├── -s 10.0.0.25 → DROP (gesperrter Client)
|
||||
└── RETURN (alle anderen passieren)
|
||||
```
|
||||
|
||||
**Vorteile der eigenen Chain:**
|
||||
- Greift nicht in bestehende Firewall-Regeln ein
|
||||
- Kann komplett geflusht werden ohne andere Regeln zu beeinflussen
|
||||
- Einfaches Debugging per `iptables -L ADGUARD_RATELIMIT`
|
||||
|
||||
## State-Management
|
||||
|
||||
Jede aktive Sperre wird als Datei gespeichert:
|
||||
|
||||
```
|
||||
/var/lib/adguard-ratelimit/192.168.1.50.ban
|
||||
```
|
||||
|
||||
Inhalt:
|
||||
```
|
||||
CLIENT_IP=192.168.1.50
|
||||
DOMAIN=microsoft.com
|
||||
COUNT=45
|
||||
BAN_TIME=2026-03-03 14:30:00
|
||||
BAN_UNTIL_EPOCH=1741012200
|
||||
BAN_UNTIL=2026-03-03 15:30:00
|
||||
```
|
||||
|
||||
Das ermöglicht:
|
||||
- Persistenz über Script-Neustarts hinweg
|
||||
- Statusabfragen jederzeit möglich
|
||||
- Automatisches Aufräumen per Cron-Job
|
||||
|
||||
## Dateistruktur nach Installation
|
||||
|
||||
```
|
||||
/opt/adguard-ratelimit/
|
||||
├── adguard-ratelimit.sh # Haupt-Monitor-Script
|
||||
├── adguard-ratelimit.conf # Konfiguration (chmod 600)
|
||||
├── iptables-helper.sh # iptables Verwaltung
|
||||
└── unban-expired.sh # Cron-basiertes Entsperren
|
||||
|
||||
/etc/systemd/system/
|
||||
└── adguard-ratelimit.service
|
||||
|
||||
/var/lib/adguard-ratelimit/
|
||||
└── *.ban # State-Dateien aktiver Sperren
|
||||
|
||||
/var/log/
|
||||
├── adguard-ratelimit.log # Laufzeit-Log
|
||||
└── adguard-ratelimit-bans.log # Ban-History (alle Sperren/Entsperrungen)
|
||||
```
|
||||
|
||||
## Ban-History
|
||||
|
||||
Jede Sperre und Entsperrung wird dauerhaft in der Ban-History protokolliert (`/var/log/adguard-ratelimit-bans.log`). Das ermöglicht eine lückenlose Nachvollziehbarkeit, auch nachdem State-Dateien bereits gelöscht wurden.
|
||||
|
||||
**Format:**
|
||||
```
|
||||
ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | GRUND
|
||||
2026-03-03 14:30:12 | BAN | 192.168.1.50 | microsoft.com | 45 | 3600s | rate-limit
|
||||
2026-03-03 15:30:12 | UNBAN | 192.168.1.50 | microsoft.com | - | - | expired
|
||||
2026-03-03 16:10:33 | UNBAN | 10.0.0.25 | telemetry.example.com | - | - | manual
|
||||
```
|
||||
|
||||
**Mögliche Gründe (GRUND-Spalte):**
|
||||
| Grund | Bedeutung |
|
||||
|-------|----------|
|
||||
| `rate-limit` | Automatische Sperre wegen Limit-Überschreitung |
|
||||
| `dry-run` | Im Dry-Run erkannt (nicht wirklich gesperrt) |
|
||||
| `expired` | Automatisch entsperrt nach Ablauf der Sperrdauer |
|
||||
| `expired-cron` | Entsperrt durch den Cron-Job (`unban-expired.sh`) |
|
||||
| `manual` | Manuell entsperrt per `unban`-Befehl |
|
||||
| `manual-flush` | Entsperrt durch `flush`-Befehl (alle Sperren aufgehoben) |
|
||||
|
||||
**History anzeigen:**
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history # letzte 50
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history 200 # letzte 200
|
||||
```
|
||||
134
doc/befehle.md
Normal file
134
doc/befehle.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Befehle & Nutzung
|
||||
|
||||
## Monitor (Hauptscript)
|
||||
|
||||
```bash
|
||||
# Starten
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh start
|
||||
|
||||
# Stoppen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh stop
|
||||
|
||||
# Status + aktive Sperren anzeigen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh status
|
||||
|
||||
# Ban-History anzeigen (letzte 50 Einträge)
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history
|
||||
|
||||
# Ban-History anzeigen (letzte 100 Einträge)
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history 100
|
||||
|
||||
# Alle Sperren aufheben
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh flush
|
||||
|
||||
# Einzelne IP entsperren
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh unban 192.168.1.100
|
||||
|
||||
# API-Verbindung testen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh test
|
||||
|
||||
# Dry-Run (nur loggen, nichts sperren)
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh dry-run
|
||||
|
||||
# Externe Blocklist - Status anzeigen
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh blocklist-status
|
||||
|
||||
# Externe Blocklist - Einmalige Synchronisation
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh blocklist-sync
|
||||
|
||||
# Externe Blocklist - Alle Sperren der externen Liste aufheben
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh blocklist-flush
|
||||
```
|
||||
|
||||
## iptables Helper
|
||||
|
||||
Für die manuelle Verwaltung der Firewall-Regeln:
|
||||
|
||||
```bash
|
||||
# Chain erstellen
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh create
|
||||
|
||||
# Alle Regeln anzeigen
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh status
|
||||
|
||||
# IP manuell sperren
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh ban 192.168.1.100
|
||||
|
||||
# IP entsperren
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh unban 192.168.1.100
|
||||
|
||||
# Alle Regeln leeren
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh flush
|
||||
|
||||
# Chain komplett entfernen
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh remove
|
||||
|
||||
# Regeln speichern / wiederherstellen
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh save
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh restore
|
||||
```
|
||||
|
||||
## Externer Blocklist-Worker
|
||||
|
||||
Der Worker kann auch standalone gesteuert werden:
|
||||
|
||||
```bash
|
||||
# Worker manuell starten (normalerweise automatisch per Hauptscript)
|
||||
sudo /opt/adguard-ratelimit/external-blocklist-worker.sh start
|
||||
|
||||
# Worker stoppen
|
||||
sudo /opt/adguard-ratelimit/external-blocklist-worker.sh stop
|
||||
|
||||
# Einmalige Synchronisation (z.B. nach Konfigurationsänderung)
|
||||
sudo /opt/adguard-ratelimit/external-blocklist-worker.sh sync
|
||||
|
||||
# Status anzeigen
|
||||
sudo /opt/adguard-ratelimit/external-blocklist-worker.sh status
|
||||
|
||||
# Alle externen Sperren aufheben
|
||||
sudo /opt/adguard-ratelimit/external-blocklist-worker.sh flush
|
||||
```
|
||||
|
||||
## systemd Service
|
||||
|
||||
```bash
|
||||
# Start / Stop / Restart
|
||||
sudo systemctl start adguard-ratelimit
|
||||
sudo systemctl stop adguard-ratelimit
|
||||
sudo systemctl restart adguard-ratelimit
|
||||
|
||||
# Status
|
||||
sudo systemctl status adguard-ratelimit
|
||||
|
||||
# Autostart aktivieren / deaktivieren
|
||||
sudo systemctl enable adguard-ratelimit
|
||||
sudo systemctl disable adguard-ratelimit
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
```bash
|
||||
# systemd Journal
|
||||
sudo journalctl -u adguard-ratelimit -f
|
||||
|
||||
# Log-Datei direkt
|
||||
sudo tail -f /var/log/adguard-ratelimit.log
|
||||
|
||||
# Nur Sperr-Einträge
|
||||
sudo grep "SPERRE" /var/log/adguard-ratelimit.log
|
||||
|
||||
# Nur Entsperr-Einträge
|
||||
sudo grep "ENTSPERRE" /var/log/adguard-ratelimit.log
|
||||
```
|
||||
|
||||
## Cron-basiertes Entsperren
|
||||
|
||||
Als Alternative oder Ergänzung zum Haupt-Monitor:
|
||||
|
||||
```bash
|
||||
# Crontab bearbeiten
|
||||
sudo crontab -e
|
||||
|
||||
# Alle 5 Minuten abgelaufene Sperren prüfen
|
||||
*/5 * * * * /opt/adguard-ratelimit/unban-expired.sh
|
||||
```
|
||||
110
doc/benachrichtigungen.md
Normal file
110
doc/benachrichtigungen.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Webhook-Benachrichtigungen
|
||||
|
||||
Das Tool kann bei Sperren und Entsperrungen Benachrichtigungen an verschiedene Dienste senden.
|
||||
|
||||
## Aktivierung
|
||||
|
||||
In der Konfiguration (`adguard-ratelimit.conf`):
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="<typ>"
|
||||
NOTIFY_WEBHOOK_URL="<url>"
|
||||
```
|
||||
|
||||
## Ntfy
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="ntfy"
|
||||
NTFY_SERVER_URL="https://ntfy.sh"
|
||||
NTFY_TOPIC="adguard-ratelimit"
|
||||
NTFY_TOKEN=""
|
||||
NTFY_PRIORITY="4"
|
||||
```
|
||||
|
||||
> **Hinweis:** Bei Ntfy wird `NOTIFY_WEBHOOK_URL` nicht benötigt – Server-URL und Topic werden separat konfiguriert.
|
||||
|
||||
**Eigene Ntfy-Instanz:**
|
||||
```bash
|
||||
NTFY_SERVER_URL="https://ntfy.mein-server.de"
|
||||
NTFY_TOPIC="dns-security"
|
||||
NTFY_TOKEN="tk_mein_geheimer_token"
|
||||
```
|
||||
|
||||
**Prioritäten:**
|
||||
| Wert | Bedeutung |
|
||||
|------|-----------|
|
||||
| 1 | Minimum |
|
||||
| 2 | Niedrig |
|
||||
| 3 | Standard |
|
||||
| 4 | Hoch |
|
||||
| 5 | Maximum |
|
||||
|
||||
**Token erstellen (Self-hosted):**
|
||||
1. Ntfy Web-UI → Benutzer/Tokens
|
||||
2. Token kopieren und in `NTFY_TOKEN` eintragen
|
||||
3. Bei ntfy.sh: Account erstellen → Access Token generieren
|
||||
|
||||
## Discord
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="discord"
|
||||
NOTIFY_WEBHOOK_URL="https://discord.com/api/webhooks/xxx/yyy"
|
||||
```
|
||||
|
||||
**Webhook erstellen:**
|
||||
1. Discord Server → Servereinstellungen → Integrationen → Webhooks
|
||||
2. Neuer Webhook → URL kopieren
|
||||
|
||||
## Gotify
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="gotify"
|
||||
NOTIFY_WEBHOOK_URL="https://gotify.example.com/message?token=xxx"
|
||||
```
|
||||
|
||||
**Token erstellen:**
|
||||
1. Gotify Web-UI → Apps → App erstellen
|
||||
2. Token kopieren und in die URL einfügen
|
||||
|
||||
## Slack
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="slack"
|
||||
NOTIFY_WEBHOOK_URL="https://hooks.slack.com/services/xxx/yyy/zzz"
|
||||
```
|
||||
|
||||
**Webhook erstellen:**
|
||||
1. Slack App → Incoming Webhooks aktivieren
|
||||
2. Webhook-URL kopieren
|
||||
|
||||
## Generic (eigener Endpoint)
|
||||
|
||||
```bash
|
||||
NOTIFY_ENABLED=true
|
||||
NOTIFY_TYPE="generic"
|
||||
NOTIFY_WEBHOOK_URL="https://your-server.com/webhook"
|
||||
```
|
||||
|
||||
Sendet einen POST mit JSON-Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "🚫 DNS Rate-Limit: Client 192.168.1.50 gesperrt ...",
|
||||
"action": "ban",
|
||||
"client": "192.168.1.50",
|
||||
"domain": "microsoft.com"
|
||||
}
|
||||
```
|
||||
|
||||
## Beispiel-Nachrichten
|
||||
|
||||
**Sperre:**
|
||||
> 🚫 DNS Rate-Limit: Client **192.168.1.50** gesperrt (45x microsoft.com in 60s). Sperre für 3600s.
|
||||
|
||||
**Entsperrung:**
|
||||
> ✅ DNS Rate-Limit: Client **192.168.1.50** wurde entsperrt.
|
||||
130
doc/konfiguration.md
Normal file
130
doc/konfiguration.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Konfiguration
|
||||
|
||||
Die Konfigurationsdatei liegt nach der Installation unter:
|
||||
|
||||
```
|
||||
/opt/adguard-ratelimit/adguard-ratelimit.conf
|
||||
```
|
||||
|
||||
## Alle Parameter
|
||||
|
||||
### AdGuard Home API
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `ADGUARD_URL` | `http://127.0.0.1:3000` | AdGuard Home Web-UI URL |
|
||||
| `ADGUARD_USER` | `admin` | API Benutzername |
|
||||
| `ADGUARD_PASS` | `changeme` | API Passwort |
|
||||
|
||||
### Rate-Limit
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `RATE_LIMIT_MAX_REQUESTS` | `30` | Max. Anfragen pro Domain/Client innerhalb des Zeitfensters |
|
||||
| `RATE_LIMIT_WINDOW` | `60` | Zeitfenster in Sekunden |
|
||||
| `CHECK_INTERVAL` | `10` | Wie oft die Logs geprüft werden (Sekunden) |
|
||||
| `API_QUERY_LIMIT` | `500` | Anzahl API-Einträge pro Abfrage (max 5000) |
|
||||
|
||||
### Sperr-Einstellungen
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `BAN_DURATION` | `3600` | Sperrdauer in Sekunden (3600 = 1 Stunde) |
|
||||
| `IPTABLES_CHAIN` | `ADGUARD_RATELIMIT` | Name der iptables Chain |
|
||||
| `BLOCKED_PORTS` | `53 443 853 784 8853` | Ports die gesperrt werden |
|
||||
| `WHITELIST` | `127.0.0.1,::1` | IPs die nie gesperrt werden (kommagetrennt) |
|
||||
|
||||
### Logging
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `LOG_FILE` | `/var/log/adguard-ratelimit.log` | Pfad zur Log-Datei |
|
||||
| `LOG_LEVEL` | `INFO` | Log-Level: `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||||
| `LOG_MAX_SIZE_MB` | `50` | Max. Log-Größe bevor rotiert wird |
|
||||
| `BAN_HISTORY_FILE` | `/var/log/adguard-ratelimit-bans.log` | Datei für die Ban-History (alle Sperren/Entsperrungen) |
|
||||
|
||||
### Benachrichtigungen
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `NOTIFY_ENABLED` | `false` | Webhook-Benachrichtigungen aktivieren |
|
||||
| `NOTIFY_WEBHOOK_URL` | *(leer)* | Webhook-URL |
|
||||
| `NOTIFY_TYPE` | `generic` | Typ: `discord`, `slack`, `gotify`, `generic` |
|
||||
|
||||
### Erweitert
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|--------------|
|
||||
| `STATE_DIR` | `/var/lib/adguard-ratelimit` | Verzeichnis für State-Dateien |
|
||||
| `PID_FILE` | `/var/run/adguard-ratelimit.pid` | PID-Datei |
|
||||
| `DRY_RUN` | `false` | Testmodus — nur loggen, nicht sperren |
|
||||
### Externe Blocklist
|
||||
|
||||
Ermöglicht das Einbinden externer IP-Blocklisten (z.B. gehostete Textdateien mit einer IP pro Zeile). Der Worker läuft als Hintergrundprozess und prüft periodisch auf Änderungen.
|
||||
|
||||
| Parameter | Standard | Beschreibung |
|
||||
|-----------|----------|}--------------|
|
||||
| `EXTERNAL_BLOCKLIST_ENABLED` | `false` | Aktiviert den externen Blocklist-Worker |
|
||||
| `EXTERNAL_BLOCKLIST_URLS` | *(leer)* | URL(s) zu Textdateien mit IPs (kommagetrennt) |
|
||||
| `EXTERNAL_BLOCKLIST_INTERVAL` | `300` | Prüfintervall in Sekunden (300 = 5 Min.) |
|
||||
| `EXTERNAL_BLOCKLIST_BAN_DURATION` | `0` | Sperrdauer in Sekunden (0 = permanent bis IP aus Liste entfernt) |
|
||||
| `EXTERNAL_BLOCKLIST_AUTO_UNBAN` | `true` | IPs automatisch entsperren wenn aus Liste entfernt |
|
||||
| `EXTERNAL_BLOCKLIST_CACHE_DIR` | `/var/lib/adguard-ratelimit/external-blocklist` | Lokaler Cache für heruntergeladene Listen |
|
||||
|
||||
#### Externe Blocklist einrichten
|
||||
|
||||
1. Erstelle eine Textdatei auf einem Webserver mit einer IP pro Zeile:
|
||||
|
||||
```text
|
||||
# Kommentare werden ignoriert
|
||||
192.168.100.50
|
||||
10.0.0.99
|
||||
2001:db8::dead:beef
|
||||
```
|
||||
|
||||
2. Aktiviere die Blocklist in der Konfiguration:
|
||||
|
||||
```bash
|
||||
EXTERNAL_BLOCKLIST_ENABLED=true
|
||||
EXTERNAL_BLOCKLIST_URLS="https://example.com/blocklist.txt"
|
||||
EXTERNAL_BLOCKLIST_INTERVAL=300
|
||||
```
|
||||
|
||||
3. Mehrere Listen können kommagetrennt angegeben werden:
|
||||
|
||||
```bash
|
||||
EXTERNAL_BLOCKLIST_URLS="https://example.com/list1.txt,https://other.com/list2.txt"
|
||||
```
|
||||
|
||||
4. Service neustarten:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart adguard-ratelimit
|
||||
```
|
||||
## Gesperrte Ports im Detail
|
||||
|
||||
Bei einem Rate-Limit-Verstoß werden **alle** DNS-Protokoll-Ports für den Client gesperrt:
|
||||
|
||||
| Port | Protokoll | Beschreibung |
|
||||
|------|-----------|-------------|
|
||||
| 53 | UDP/TCP | Standard DNS |
|
||||
| 443 | TCP | DNS-over-HTTPS (DoH) |
|
||||
| 853 | TCP | DNS-over-TLS (DoT) |
|
||||
| 853 | UDP | DNS-over-QUIC (DoQ) |
|
||||
| 784 | UDP | DNS-over-QUIC (alternativ) |
|
||||
| 8853 | UDP | DNS-over-QUIC (alternativ) |
|
||||
|
||||
## Whitelist richtig pflegen
|
||||
|
||||
Die Whitelist sollte mindestens enthalten:
|
||||
|
||||
- `127.0.0.1` und `::1` (Localhost)
|
||||
- Die IP deines Routers / Gateways
|
||||
- Deine eigenen Management-IPs
|
||||
- Andere vertrauenswürdige DNS-Clients
|
||||
|
||||
Beispiel:
|
||||
|
||||
```
|
||||
WHITELIST="127.0.0.1,::1,192.168.1.1,192.168.1.10,fd00::1"
|
||||
```
|
||||
94
doc/tipps-und-troubleshooting.md
Normal file
94
doc/tipps-und-troubleshooting.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Tipps & Troubleshooting
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Erst immer im Dry-Run testen**, bevor der scharfe Modus aktiviert wird
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh dry-run
|
||||
```
|
||||
- **Whitelist großzügig pflegen**: Eigene IPs, Router, wichtige Server nicht vergessen
|
||||
- **Sperrdauer anpassen**: Für DDoS-artige Muster ggf. länger sperren
|
||||
- **Logs regelmäßig prüfen**: Falsche Positive erkennen und Whitelist anpassen
|
||||
- **Ban-History nutzen**: `history`-Befehl zeigt alle vergangenen Sperren — hilfreich um Muster zu erkennen
|
||||
- **Log-Level auf DEBUG** setzen wenn etwas nicht funktioniert
|
||||
|
||||
## Häufige Probleme
|
||||
|
||||
### API-Verbindung schlägt fehl
|
||||
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh test
|
||||
```
|
||||
|
||||
**Mögliche Ursachen:**
|
||||
- Falsche URL in `ADGUARD_URL` (Port prüfen!)
|
||||
- Falsche Zugangsdaten (`ADGUARD_USER` / `ADGUARD_PASS`)
|
||||
- AdGuard Home läuft nicht
|
||||
- Firewall blockiert lokale Verbindung
|
||||
|
||||
**Lösung:** URL manuell testen:
|
||||
```bash
|
||||
curl -s -u admin:passwort http://127.0.0.1:3000/control/querylog?limit=1
|
||||
```
|
||||
|
||||
### iptables-Fehler: "Permission denied"
|
||||
|
||||
Das Script muss als **root** laufen, da iptables Root-Rechte benötigt.
|
||||
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh start
|
||||
```
|
||||
|
||||
### Client wird fälschlich gesperrt
|
||||
|
||||
1. Client sofort entsperren:
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh unban 192.168.1.100
|
||||
```
|
||||
2. In der Ban-History prüfen, warum gesperrt wurde:
|
||||
```bash
|
||||
sudo /opt/adguard-ratelimit/adguard-ratelimit.sh history | grep 192.168.1.100
|
||||
```
|
||||
3. IP zur Whitelist hinzufügen in `adguard-ratelimit.conf`
|
||||
3. Service neustarten:
|
||||
```bash
|
||||
sudo systemctl restart adguard-ratelimit
|
||||
```
|
||||
|
||||
### Sperren überleben Reboot nicht
|
||||
|
||||
Das ist normal — iptables-Regeln sind flüchtig. Der **Service** erstellt die Chain beim Start automatisch neu. Aktive Sperren aus dem State-Verzeichnis werden aber nicht automatisch wiederhergestellt.
|
||||
|
||||
**Optionen:**
|
||||
- `iptables-persistent` installieren (`apt install iptables-persistent`)
|
||||
- Oder den State beim Boot wiederherstellen lassen (Feature-Idee)
|
||||
|
||||
### Zu viele false positives
|
||||
|
||||
- `RATE_LIMIT_MAX_REQUESTS` erhöhen (z.B. 50 oder 100)
|
||||
- `RATE_LIMIT_WINDOW` vergrößern (z.B. 120 Sekunden)
|
||||
- Windows-Clients fragen manche Domains von Natur aus sehr oft an — Whitelist nutzen
|
||||
|
||||
### Monitor startet nicht (PID-File)
|
||||
|
||||
```bash
|
||||
# Altes PID-File entfernen
|
||||
sudo rm -f /var/run/adguard-ratelimit.pid
|
||||
sudo systemctl start adguard-ratelimit
|
||||
```
|
||||
|
||||
## Deinstallation
|
||||
|
||||
```bash
|
||||
sudo bash install.sh uninstall
|
||||
```
|
||||
|
||||
Oder manuell:
|
||||
```bash
|
||||
sudo systemctl stop adguard-ratelimit
|
||||
sudo systemctl disable adguard-ratelimit
|
||||
sudo /opt/adguard-ratelimit/iptables-helper.sh remove
|
||||
sudo rm -rf /opt/adguard-ratelimit
|
||||
sudo rm -f /etc/systemd/system/adguard-ratelimit.service
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
640
external-blocklist-worker.sh
Normal file
640
external-blocklist-worker.sh
Normal file
@@ -0,0 +1,640 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - Externer Blocklist-Worker
|
||||
# Lädt externe IP-Blocklisten herunter und sperrt/entsperrt IPs automatisch.
|
||||
# Wird als Hintergrundprozess vom Hauptscript gestartet.
|
||||
#
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Datum: 2026-03-03
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-ratelimit.conf"
|
||||
|
||||
# ─── Konfiguration laden ───────────────────────────────────────────────────────
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck source=adguard-ratelimit.conf
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ─── Worker PID-File ──────────────────────────────────────────────────────────
|
||||
WORKER_PID_FILE="/var/run/adguard-blocklist-worker.pid"
|
||||
|
||||
# ─── Logging (eigene Funktion, nutzt gleiche Log-Datei) ───────────────────────
|
||||
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
||||
|
||||
log() {
|
||||
local level="$1"
|
||||
shift
|
||||
local message="$*"
|
||||
local configured_level="${LOG_LEVEL:-INFO}"
|
||||
|
||||
if [[ ${LOG_LEVELS[$level]:-1} -ge ${LOG_LEVELS[$configured_level]:-1} ]]; then
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
local log_entry="[$timestamp] [$level] [BLOCKLIST-WORKER] $message"
|
||||
echo "$log_entry" | tee -a "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Ban-History ─────────────────────────────────────────────────────────────
|
||||
log_ban_history() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local reason="${3:-external-blocklist}"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | GRUND" >> "$BAN_HISTORY_FILE"
|
||||
echo "#───────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
||||
fi
|
||||
|
||||
local duration="permanent"
|
||||
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] && duration="${EXTERNAL_BLOCKLIST_BAN_DURATION}s"
|
||||
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %s\n" \
|
||||
"$timestamp" "$action" "$client_ip" "-" "-" "$duration" "$reason" \
|
||||
>> "$BAN_HISTORY_FILE"
|
||||
}
|
||||
|
||||
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
|
||||
init_directories() {
|
||||
mkdir -p "$EXTERNAL_BLOCKLIST_CACHE_DIR"
|
||||
mkdir -p "$STATE_DIR"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
}
|
||||
|
||||
# ─── Whitelist Prüfung ───────────────────────────────────────────────────────
|
||||
is_whitelisted() {
|
||||
local ip="$1"
|
||||
IFS=',' read -ra wl_entries <<< "$WHITELIST"
|
||||
for entry in "${wl_entries[@]}"; do
|
||||
entry=$(echo "$entry" | xargs) # trim
|
||||
if [[ "$ip" == "$entry" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ─── iptables Chain Setup ────────────────────────────────────────────────────
|
||||
setup_iptables_chain() {
|
||||
# IPv4 Chain erstellen falls nicht vorhanden
|
||||
if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
log "INFO" "Erstelle iptables Chain: $IPTABLES_CHAIN (IPv4)"
|
||||
iptables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
|
||||
# IPv6 Chain erstellen falls nicht vorhanden
|
||||
if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
log "INFO" "Erstelle ip6tables Chain: $IPTABLES_CHAIN (IPv6)"
|
||||
ip6tables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── IP sperren ──────────────────────────────────────────────────────────────
|
||||
ban_ip() {
|
||||
local ip="$1"
|
||||
local state_file="${STATE_DIR}/ext_${ip//[:\/]/_}.ban"
|
||||
|
||||
# Bereits gesperrt?
|
||||
if [[ -f "$state_file" ]]; then
|
||||
log "DEBUG" "IP $ip bereits über externe Blocklist gesperrt"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Nicht auch vom Hauptscript gesperrt? (State-Datei ohne ext_ Prefix)
|
||||
local main_state_file="${STATE_DIR}/${ip//[:\/]/_}.ban"
|
||||
if [[ -f "$main_state_file" ]]; then
|
||||
log "DEBUG" "IP $ip bereits vom Rate-Limiter gesperrt - überspringe"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
log "WARN" "[DRY-RUN] WÜRDE sperren (externe Blocklist): $ip"
|
||||
log_ban_history "DRY" "$ip" "external-blocklist-dry-run"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "WARN" "SPERRE IP (externe Blocklist): $ip"
|
||||
|
||||
# iptables-Regel setzen
|
||||
if [[ "$ip" == *:* ]]; then
|
||||
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# State speichern
|
||||
local ban_until_epoch="0"
|
||||
local ban_until_display="permanent"
|
||||
if [[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]]; then
|
||||
ban_until_epoch=$(date -d "+${EXTERNAL_BLOCKLIST_BAN_DURATION} seconds" '+%s' 2>/dev/null \
|
||||
|| date -v "+${EXTERNAL_BLOCKLIST_BAN_DURATION}S" '+%s')
|
||||
ban_until_display=$(date -d "@$ban_until_epoch" '+%Y-%m-%d %H:%M:%S' 2>/dev/null \
|
||||
|| date -r "$ban_until_epoch" '+%Y-%m-%d %H:%M:%S')
|
||||
fi
|
||||
|
||||
cat > "$state_file" << EOF
|
||||
CLIENT_IP=$ip
|
||||
DOMAIN=-
|
||||
COUNT=-
|
||||
BAN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
BAN_UNTIL_EPOCH=$ban_until_epoch
|
||||
BAN_UNTIL=$ban_until_display
|
||||
SOURCE=external-blocklist
|
||||
EOF
|
||||
|
||||
log_ban_history "BAN" "$ip" "external-blocklist"
|
||||
|
||||
# Benachrichtigung senden
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "ban" "$ip"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── IP entsperren ───────────────────────────────────────────────────────────
|
||||
unban_ip() {
|
||||
local ip="$1"
|
||||
local reason="${2:-external-blocklist-removed}"
|
||||
local state_file="${STATE_DIR}/ext_${ip//[:\/]/_}.ban"
|
||||
|
||||
[[ -f "$state_file" ]] || return 0
|
||||
|
||||
log "INFO" "ENTSPERRE IP (externe Blocklist entfernt): $ip"
|
||||
|
||||
if [[ "$ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$state_file"
|
||||
log_ban_history "UNBAN" "$ip" "$reason"
|
||||
|
||||
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
|
||||
send_notification "unban" "$ip"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Benachrichtigung ────────────────────────────────────────────────────────
|
||||
send_notification() {
|
||||
local action="$1"
|
||||
local ip="$2"
|
||||
|
||||
[[ -z "${NOTIFY_WEBHOOK_URL:-}" ]] && return
|
||||
|
||||
local message
|
||||
if [[ "$action" == "ban" ]]; then
|
||||
message="🚫 Externe Blocklist: IP **$ip** gesperrt."
|
||||
else
|
||||
message="✅ Externe Blocklist: IP **$ip** entsperrt (aus Liste entfernt)."
|
||||
fi
|
||||
|
||||
case "${NOTIFY_TYPE:-generic}" in
|
||||
discord)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"content\": \"$message\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
slack)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"text\": \"$message\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
gotify)
|
||||
curl -s -X POST "$NOTIFY_WEBHOOK_URL" \
|
||||
-F "title=AdGuard Shield - Externe Blocklist" \
|
||||
-F "message=$message" \
|
||||
-F "priority=5" &>/dev/null &
|
||||
;;
|
||||
ntfy)
|
||||
local ntfy_url="${NTFY_SERVER_URL:-https://ntfy.sh}"
|
||||
local -a curl_args=(
|
||||
-s -X POST "${ntfy_url}/${NTFY_TOPIC}"
|
||||
-H "Title: AdGuard Shield - Externe Blocklist"
|
||||
-H "Priority: ${NTFY_PRIORITY:-3}"
|
||||
-H "Tags: rotating_light,blocklist"
|
||||
-d "$(echo "$message" | sed 's/\*\*//g')"
|
||||
)
|
||||
[[ -n "${NTFY_TOKEN:-}" ]] && curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
|
||||
curl "${curl_args[@]}" &>/dev/null &
|
||||
;;
|
||||
generic)
|
||||
curl -s -H "Content-Type: application/json" \
|
||||
-d "{\"message\": \"$message\", \"action\": \"$action\", \"client\": \"$ip\", \"source\": \"external-blocklist\"}" \
|
||||
"$NOTIFY_WEBHOOK_URL" &>/dev/null &
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── Externe Blocklist herunterladen ─────────────────────────────────────────
|
||||
download_blocklist() {
|
||||
local url="$1"
|
||||
local index="$2"
|
||||
local cache_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/blocklist_${index}.txt"
|
||||
local etag_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/blocklist_${index}.etag"
|
||||
local tmp_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/blocklist_${index}.tmp"
|
||||
|
||||
log "DEBUG" "Prüfe externe Blocklist: $url"
|
||||
|
||||
# HTTP-Header für bedingte Anfrage vorbereiten
|
||||
local -a curl_args=(
|
||||
-s
|
||||
-L
|
||||
--connect-timeout 10
|
||||
--max-time 30
|
||||
-o "$tmp_file"
|
||||
-w "%{http_code}"
|
||||
)
|
||||
|
||||
# ETag für If-None-Match Header nutzen falls vorhanden
|
||||
if [[ -f "$etag_file" ]]; then
|
||||
local stored_etag
|
||||
stored_etag=$(cat "$etag_file")
|
||||
curl_args+=(-H "If-None-Match: ${stored_etag}")
|
||||
fi
|
||||
|
||||
# Download-Header separat abfragen für ETag
|
||||
local http_code
|
||||
http_code=$(curl "${curl_args[@]}" -D "${tmp_file}.headers" "$url" 2>/dev/null) || {
|
||||
log "WARN" "Fehler beim Download der Blocklist: $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 304 Not Modified - keine Änderung
|
||||
if [[ "$http_code" == "304" ]]; then
|
||||
log "DEBUG" "Blocklist nicht geändert (HTTP 304): $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Fehlerhafte HTTP-Codes
|
||||
if [[ "$http_code" != "200" ]]; then
|
||||
log "WARN" "Blocklist Download fehlgeschlagen (HTTP $http_code): $url"
|
||||
rm -f "$tmp_file" "${tmp_file}.headers"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Neuen ETag speichern falls vorhanden
|
||||
if [[ -f "${tmp_file}.headers" ]]; then
|
||||
local new_etag
|
||||
new_etag=$(grep -i '^etag:' "${tmp_file}.headers" | head -1 | sed 's/^[^:]*: *//;s/\r$//')
|
||||
if [[ -n "$new_etag" ]]; then
|
||||
echo "$new_etag" > "$etag_file"
|
||||
fi
|
||||
fi
|
||||
rm -f "${tmp_file}.headers"
|
||||
|
||||
# Prüfen ob sich der Inhalt tatsächlich geändert hat (Fallback für Server ohne ETag)
|
||||
if [[ -f "$cache_file" ]]; then
|
||||
if diff -q "$tmp_file" "$cache_file" &>/dev/null; then
|
||||
log "DEBUG" "Blocklist Inhalt unverändert: $url"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Neue Datei übernehmen
|
||||
mv "$tmp_file" "$cache_file"
|
||||
log "INFO" "Blocklist aktualisiert: $url"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── IPs aus Blocklist-Datei parsen ──────────────────────────────────────────
|
||||
parse_blocklist_ips() {
|
||||
local cache_file="$1"
|
||||
|
||||
[[ -f "$cache_file" ]] || return
|
||||
|
||||
# Zeilen lesen, Leerzeilen und Kommentare ignorieren, IPs extrahieren
|
||||
while IFS= read -r line; do
|
||||
# Leerzeilen überspringen
|
||||
[[ -z "$line" ]] && continue
|
||||
# Kommentare überspringen (# am Anfang)
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
# Whitespace trimmen
|
||||
line=$(echo "$line" | xargs)
|
||||
# Leere Zeilen nach Trim überspringen
|
||||
[[ -z "$line" ]] && continue
|
||||
# CIDR-Notation oder reine IP ausgeben
|
||||
echo "$line"
|
||||
done < "$cache_file"
|
||||
}
|
||||
|
||||
# ─── Aktuelle externe Sperren ermitteln ──────────────────────────────────────
|
||||
get_currently_banned_external_ips() {
|
||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
grep '^CLIENT_IP=' "$state_file" | cut -d= -f2
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Abgelaufene externe Sperren prüfen ─────────────────────────────────────
|
||||
check_expired_external_bans() {
|
||||
[[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]] || return
|
||||
|
||||
local now
|
||||
now=$(date '+%s')
|
||||
|
||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
|
||||
local ban_until_epoch
|
||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2)
|
||||
local client_ip
|
||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
|
||||
if [[ -n "$ban_until_epoch" && "$ban_until_epoch" -gt 0 && "$now" -ge "$ban_until_epoch" ]]; then
|
||||
unban_ip "$client_ip" "external-blocklist-expired"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Blocklisten synchronisieren ─────────────────────────────────────────────
|
||||
sync_blocklists() {
|
||||
local any_updated=false
|
||||
|
||||
# Alle URLs holen
|
||||
IFS=',' read -ra urls <<< "$EXTERNAL_BLOCKLIST_URLS"
|
||||
local index=0
|
||||
|
||||
for url in "${urls[@]}"; do
|
||||
url=$(echo "$url" | xargs) # trim
|
||||
[[ -z "$url" ]] && continue
|
||||
|
||||
if download_blocklist "$url" "$index"; then
|
||||
any_updated=true
|
||||
fi
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
# Alle gewünschten IPs zusammenführen (aus allen Cache-Dateien)
|
||||
local all_desired_ips_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/.all_ips.tmp"
|
||||
> "$all_desired_ips_file"
|
||||
|
||||
for cache_file in "${EXTERNAL_BLOCKLIST_CACHE_DIR}"/blocklist_*.txt; do
|
||||
[[ -f "$cache_file" ]] || continue
|
||||
parse_blocklist_ips "$cache_file" >> "$all_desired_ips_file"
|
||||
done
|
||||
|
||||
# Duplikate entfernen und sortieren
|
||||
local unique_ips_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/.all_ips_unique.tmp"
|
||||
sort -u "$all_desired_ips_file" > "$unique_ips_file"
|
||||
|
||||
local desired_count
|
||||
desired_count=$(wc -l < "$unique_ips_file" | xargs)
|
||||
log "DEBUG" "Externe Blockliste enthält $desired_count eindeutige IPs"
|
||||
|
||||
# ─── Neue IPs sperren ────────────────────────────────────────────────────
|
||||
local new_bans=0
|
||||
while IFS= read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
|
||||
# Whitelist prüfen
|
||||
if is_whitelisted "$ip"; then
|
||||
log "DEBUG" "IP $ip ist auf der Whitelist - überspringe (externe Blocklist)"
|
||||
continue
|
||||
fi
|
||||
|
||||
ban_ip "$ip"
|
||||
new_bans=$((new_bans + 1))
|
||||
done < "$unique_ips_file"
|
||||
|
||||
# ─── Entfernte IPs entsperren ────────────────────────────────────────────
|
||||
if [[ "$EXTERNAL_BLOCKLIST_AUTO_UNBAN" == "true" ]]; then
|
||||
local removed_count=0
|
||||
while IFS= read -r banned_ip; do
|
||||
[[ -z "$banned_ip" ]] && continue
|
||||
# Prüfen ob die IP noch in der gewünschten Liste ist
|
||||
if ! grep -qxF "$banned_ip" "$unique_ips_file" 2>/dev/null; then
|
||||
unban_ip "$banned_ip" "external-blocklist-removed"
|
||||
removed_count=$((removed_count + 1))
|
||||
fi
|
||||
done < <(get_currently_banned_external_ips)
|
||||
|
||||
if [[ $removed_count -gt 0 ]]; then
|
||||
log "INFO" "$removed_count IPs aus externer Blocklist entfernt und entsperrt"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Abgelaufene Sperren prüfen (nur bei zeitlich begrenzten Sperren)
|
||||
check_expired_external_bans
|
||||
|
||||
# Aufräumen
|
||||
rm -f "$all_desired_ips_file" "$unique_ips_file"
|
||||
|
||||
if [[ "$new_bans" -gt 0 ]]; then
|
||||
log "INFO" "$new_bans neue IPs aus externer Blocklist gesperrt"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── PID-Management ──────────────────────────────────────────────────────────
|
||||
write_pid() {
|
||||
echo $$ > "$WORKER_PID_FILE"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
log "INFO" "Externer Blocklist-Worker wird beendet..."
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_already_running() {
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local old_pid
|
||||
old_pid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$old_pid" 2>/dev/null; then
|
||||
log "DEBUG" "Blocklist-Worker läuft bereits (PID: $old_pid)"
|
||||
return 1
|
||||
else
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||
show_status() {
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo " Externer Blocklist-Worker - Status"
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
if [[ "$EXTERNAL_BLOCKLIST_ENABLED" != "true" ]]; then
|
||||
echo " ⚠️ Externer Blocklist-Worker ist deaktiviert"
|
||||
echo " Aktivieren: EXTERNAL_BLOCKLIST_ENABLED=true in $CONFIG_FILE"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# Worker-Prozess Status
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
local pid
|
||||
pid=$(cat "$WORKER_PID_FILE")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo " ✅ Worker läuft (PID: $pid)"
|
||||
else
|
||||
echo " ❌ Worker nicht aktiv (veraltete PID-Datei)"
|
||||
fi
|
||||
else
|
||||
echo " ❌ Worker nicht aktiv"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Konfigurierte URLs
|
||||
echo " Konfigurierte Blocklisten:"
|
||||
IFS=',' read -ra urls <<< "$EXTERNAL_BLOCKLIST_URLS"
|
||||
local index=0
|
||||
for url in "${urls[@]}"; do
|
||||
url=$(echo "$url" | xargs)
|
||||
[[ -z "$url" ]] && continue
|
||||
local cache_file="${EXTERNAL_BLOCKLIST_CACHE_DIR}/blocklist_${index}.txt"
|
||||
local ip_count=0
|
||||
if [[ -f "$cache_file" ]]; then
|
||||
ip_count=$(grep -cv '^\s*#\|^\s*$' "$cache_file" 2>/dev/null || echo "0")
|
||||
local last_modified
|
||||
last_modified=$(date -r "$cache_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unbekannt")
|
||||
echo " [$index] $url"
|
||||
echo " IPs: $ip_count | Zuletzt aktualisiert: $last_modified"
|
||||
else
|
||||
echo " [$index] $url (noch nicht heruntergeladen)"
|
||||
fi
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Aktive externe Sperren
|
||||
local ext_ban_count=0
|
||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
ext_ban_count=$((ext_ban_count + 1))
|
||||
done
|
||||
echo " Aktive Sperren (externe Blocklist): $ext_ban_count"
|
||||
|
||||
echo ""
|
||||
echo " Prüfintervall: ${EXTERNAL_BLOCKLIST_INTERVAL}s"
|
||||
echo " Auto-Unban: ${EXTERNAL_BLOCKLIST_AUTO_UNBAN}"
|
||||
if [[ "$EXTERNAL_BLOCKLIST_BAN_DURATION" -gt 0 ]]; then
|
||||
echo " Sperrdauer: ${EXTERNAL_BLOCKLIST_BAN_DURATION}s"
|
||||
else
|
||||
echo " Sperrdauer: permanent (bis aus Liste entfernt)"
|
||||
fi
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════════"
|
||||
}
|
||||
|
||||
# ─── Einmalig synchronisieren ────────────────────────────────────────────────
|
||||
run_once() {
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
|
||||
if [[ -z "${EXTERNAL_BLOCKLIST_URLS:-}" ]]; then
|
||||
log "ERROR" "Keine externen Blocklist-URLs konfiguriert (EXTERNAL_BLOCKLIST_URLS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "INFO" "Einmalige Blocklist-Synchronisation..."
|
||||
sync_blocklists
|
||||
log "INFO" "Synchronisation abgeschlossen"
|
||||
}
|
||||
|
||||
# ─── Hauptschleife ──────────────────────────────────────────────────────────
|
||||
main_loop() {
|
||||
init_directories
|
||||
setup_iptables_chain
|
||||
|
||||
if [[ -z "${EXTERNAL_BLOCKLIST_URLS:-}" ]]; then
|
||||
log "ERROR" "Keine externen Blocklist-URLs konfiguriert (EXTERNAL_BLOCKLIST_URLS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
log "INFO" "Externer Blocklist-Worker gestartet"
|
||||
log "INFO" " URLs: ${EXTERNAL_BLOCKLIST_URLS}"
|
||||
log "INFO" " Prüfintervall: ${EXTERNAL_BLOCKLIST_INTERVAL}s"
|
||||
log "INFO" " Auto-Unban: ${EXTERNAL_BLOCKLIST_AUTO_UNBAN}"
|
||||
log "INFO" "═══════════════════════════════════════════════════════════"
|
||||
|
||||
while true; do
|
||||
sync_blocklists
|
||||
sleep "$EXTERNAL_BLOCKLIST_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# ─── Signal-Handler ──────────────────────────────────────────────────────────
|
||||
trap cleanup SIGTERM SIGINT SIGHUP
|
||||
|
||||
# ─── Kommandozeilen-Argumente ────────────────────────────────────────────────
|
||||
case "${1:-start}" in
|
||||
start)
|
||||
if ! check_already_running; then
|
||||
exit 0
|
||||
fi
|
||||
write_pid
|
||||
main_loop
|
||||
;;
|
||||
stop)
|
||||
if [[ -f "$WORKER_PID_FILE" ]]; then
|
||||
kill "$(cat "$WORKER_PID_FILE")" 2>/dev/null || true
|
||||
rm -f "$WORKER_PID_FILE"
|
||||
echo "Blocklist-Worker gestoppt"
|
||||
else
|
||||
echo "Blocklist-Worker läuft nicht"
|
||||
fi
|
||||
;;
|
||||
sync)
|
||||
run_once
|
||||
;;
|
||||
status)
|
||||
init_directories
|
||||
show_status
|
||||
;;
|
||||
flush)
|
||||
init_directories
|
||||
echo "Entferne alle externen Blocklist-Sperren..."
|
||||
for state_file in "${STATE_DIR}"/ext_*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
unban_ip "$_ip" "manual-flush"
|
||||
done
|
||||
echo "Alle externen Blocklist-Sperren aufgehoben"
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
AdGuard Shield - Externer Blocklist-Worker
|
||||
|
||||
Nutzung: $0 {start|stop|sync|status|flush}
|
||||
|
||||
Befehle:
|
||||
start Startet den Worker (Dauerbetrieb)
|
||||
stop Stoppt den Worker
|
||||
sync Einmalige Synchronisation
|
||||
status Zeigt Status und konfigurierte Listen
|
||||
flush Entfernt alle externen Blocklist-Sperren
|
||||
|
||||
Konfiguration: $CONFIG_FILE
|
||||
|
||||
USAGE
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
300
install.sh
Normal file
300
install.sh
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - Installer
|
||||
# Autor: Patrick Asmus
|
||||
# E-Mail: support@techniverse.net
|
||||
# Lizenz: MIT
|
||||
###############################################################################
|
||||
|
||||
VERSION="1.0.0"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_DIR="/opt/adguard-ratelimit"
|
||||
SERVICE_FILE="/etc/systemd/system/adguard-ratelimit.service"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Farben
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}"
|
||||
echo " ▄▄▄ ▓█████▄ ▄████ █ ██ ▄▄▄ ██▀███ ▓█████▄ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ "
|
||||
echo "▒████▄ ▒██▀ ██▌ ██▒ ▀█▒ ██ ▓██▒▒████▄ ▓██ ▒ ██▒▒██▀ ██▌ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌"
|
||||
echo "▒██ ▀█▄ ░██ █▌▒██░▄▄▄░▓██ ▒██░▒██ ▀█▄ ▓██ ░▄█ ▒░██ █▌ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌"
|
||||
echo "░██▄▄▄▄██ ░▓█▄ ▌░▓█ ██▓▓▓█ ░██░░██▄▄▄▄██ ▒██▀▀█▄ ░▓█▄ ▌ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌"
|
||||
echo " ▓█ ▓██▒░▒████▓ ░▒▓███▀▒▒▒█████▓ ▓█ ▓██▒░██▓ ▒██▒░▒████▓ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ "
|
||||
echo " ▒▒ ▓▒█░ ▒▒▓ ▒ ░▒ ▒ ░▒▓▒ ▒ ▒ ▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒▓ ▒ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ "
|
||||
echo " ▒ ▒▒ ░ ░ ▒ ▒ ░ ░ ░░▒░ ░ ░ ▒ ▒▒ ░ ░▒ ░ ▒░ ░ ▒ ▒ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ "
|
||||
echo " ░ ▒ ░ ░ ░ ░ ░ ░ ░░░ ░ ░ ░ ▒ ░░ ░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ "
|
||||
echo " ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ "
|
||||
echo " ░ ░ ░ "
|
||||
echo -e "${NC}"
|
||||
echo -e "${GREEN} Version: ${VERSION}${NC}"
|
||||
echo -e "${BLUE} Autor: Patrick Asmus${NC}"
|
||||
echo -e "${BLUE} E-Mail: support@techniverse.net${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo -e "${RED}Dieses Script muss als root ausgeführt werden!${NC}" >&2
|
||||
echo "Bitte mit 'sudo $0' ausführen."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
echo -e "${YELLOW}Prüfe Abhängigkeiten...${NC}"
|
||||
local missing=()
|
||||
|
||||
for cmd in curl jq iptables ip6tables; do
|
||||
if command -v "$cmd" &>/dev/null; then
|
||||
echo -e " ✅ $cmd"
|
||||
else
|
||||
echo -e " ❌ $cmd"
|
||||
missing+=("$cmd")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}Installiere fehlende Pakete...${NC}"
|
||||
|
||||
if command -v apt &>/dev/null; then
|
||||
apt update -qq
|
||||
apt install -y -qq curl jq iptables
|
||||
elif command -v dnf &>/dev/null; then
|
||||
dnf install -y curl jq iptables
|
||||
elif command -v yum &>/dev/null; then
|
||||
yum install -y curl jq iptables
|
||||
elif command -v pacman &>/dev/null; then
|
||||
pacman -S --noconfirm curl jq iptables
|
||||
else
|
||||
echo -e "${RED}Konnte Paketmanager nicht erkennen. Bitte installiere manuell: ${missing[*]}${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
install_files() {
|
||||
echo -e "${YELLOW}Installiere Dateien nach $INSTALL_DIR ...${NC}"
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
mkdir -p /var/lib/adguard-ratelimit
|
||||
mkdir -p /var/log
|
||||
|
||||
# Dateien kopieren
|
||||
cp "$SCRIPT_DIR/adguard-ratelimit.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/iptables-helper.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/unban-expired.sh" "$INSTALL_DIR/"
|
||||
cp "$SCRIPT_DIR/external-blocklist-worker.sh" "$INSTALL_DIR/"
|
||||
|
||||
# Konfigurationsdatei nur kopieren wenn nicht vorhanden (Update-Sicher)
|
||||
if [[ ! -f "$INSTALL_DIR/adguard-ratelimit.conf" ]]; then
|
||||
cp "$SCRIPT_DIR/adguard-ratelimit.conf" "$INSTALL_DIR/"
|
||||
echo -e " ✅ Konfiguration kopiert (NEU)"
|
||||
else
|
||||
cp "$SCRIPT_DIR/adguard-ratelimit.conf" "$INSTALL_DIR/adguard-ratelimit.conf.new"
|
||||
echo -e " ℹ️ Konfiguration existiert bereits - neue Version als .conf.new gespeichert"
|
||||
fi
|
||||
|
||||
# Ausführbar machen
|
||||
chmod +x "$INSTALL_DIR/adguard-ratelimit.sh"
|
||||
chmod +x "$INSTALL_DIR/iptables-helper.sh"
|
||||
chmod +x "$INSTALL_DIR/unban-expired.sh"
|
||||
chmod +x "$INSTALL_DIR/external-blocklist-worker.sh"
|
||||
chmod 600 "$INSTALL_DIR/adguard-ratelimit.conf"
|
||||
|
||||
echo -e " ✅ Dateien installiert"
|
||||
echo ""
|
||||
}
|
||||
|
||||
install_service() {
|
||||
echo -e "${YELLOW}Installiere systemd Service...${NC}"
|
||||
|
||||
cp "$SCRIPT_DIR/adguard-ratelimit.service" "$SERVICE_FILE"
|
||||
systemctl daemon-reload
|
||||
systemctl enable adguard-ratelimit.service
|
||||
|
||||
echo -e " ✅ Service installiert und aktiviert"
|
||||
echo ""
|
||||
}
|
||||
|
||||
configure() {
|
||||
echo -e "${YELLOW}Konfiguration:${NC}"
|
||||
echo ""
|
||||
|
||||
local conf="$INSTALL_DIR/adguard-ratelimit.conf"
|
||||
|
||||
# AdGuard URL
|
||||
read -rp " AdGuard Home URL [http://127.0.0.1:3000]: " adguard_url
|
||||
adguard_url="${adguard_url:-http://127.0.0.1:3000}"
|
||||
sed -i "s|^ADGUARD_URL=.*|ADGUARD_URL=\"$adguard_url\"|" "$conf"
|
||||
|
||||
# Benutzername
|
||||
read -rp " AdGuard Home Benutzername [admin]: " adguard_user
|
||||
adguard_user="${adguard_user:-admin}"
|
||||
sed -i "s|^ADGUARD_USER=.*|ADGUARD_USER=\"$adguard_user\"|" "$conf"
|
||||
|
||||
# Passwort
|
||||
read -rsp " AdGuard Home Passwort: " adguard_pass
|
||||
echo ""
|
||||
if [[ -n "$adguard_pass" ]]; then
|
||||
# Einfache Quotes damit $-Zeichen im Passwort nicht expandiert werden
|
||||
sed -i "s|^ADGUARD_PASS=.*|ADGUARD_PASS='$adguard_pass'|" "$conf"
|
||||
fi
|
||||
|
||||
# Rate Limit
|
||||
read -rp " Max. Anfragen pro Domain/Client pro Minute [30]: " rate_limit
|
||||
rate_limit="${rate_limit:-30}"
|
||||
sed -i "s|^RATE_LIMIT_MAX_REQUESTS=.*|RATE_LIMIT_MAX_REQUESTS=$rate_limit|" "$conf"
|
||||
|
||||
# Sperrdauer
|
||||
read -rp " Sperrdauer in Sekunden [3600]: " ban_duration
|
||||
ban_duration="${ban_duration:-3600}"
|
||||
sed -i "s|^BAN_DURATION=.*|BAN_DURATION=$ban_duration|" "$conf"
|
||||
|
||||
# Whitelist
|
||||
read -rp " Whitelist IPs (kommagetrennt) [127.0.0.1,::1]: " whitelist
|
||||
whitelist="${whitelist:-127.0.0.1,::1}"
|
||||
sed -i "s|^WHITELIST=.*|WHITELIST=\"$whitelist\"|" "$conf"
|
||||
|
||||
echo ""
|
||||
echo -e " ✅ Konfiguration gespeichert"
|
||||
echo ""
|
||||
}
|
||||
|
||||
test_connection() {
|
||||
echo -e "${YELLOW}Teste Verbindung zur AdGuard Home API...${NC}"
|
||||
|
||||
source "$INSTALL_DIR/adguard-ratelimit.conf"
|
||||
|
||||
local response
|
||||
response=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-u "${ADGUARD_USER}:${ADGUARD_PASS}" \
|
||||
--connect-timeout 5 \
|
||||
"${ADGUARD_URL}/control/querylog?limit=1" 2>/dev/null)
|
||||
|
||||
if [[ "$response" == "200" ]]; then
|
||||
echo -e " ✅ Verbindung erfolgreich! (HTTP $response)"
|
||||
else
|
||||
echo -e " ❌ Verbindung fehlgeschlagen (HTTP $response)"
|
||||
echo -e " ${YELLOW}Bitte prüfe URL und Zugangsdaten in: $INSTALL_DIR/adguard-ratelimit.conf${NC}"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${GREEN} AdGuard Shield - Installation abgeschlossen!${NC}"
|
||||
echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo " Installationspfad: $INSTALL_DIR"
|
||||
echo " Konfiguration: $INSTALL_DIR/adguard-ratelimit.conf"
|
||||
echo " Service: adguard-ratelimit.service"
|
||||
echo " Log-Datei: /var/log/adguard-ratelimit.log"
|
||||
echo ""
|
||||
echo " Nächste Schritte:"
|
||||
echo " ─────────────────"
|
||||
echo " 1. Konfiguration prüfen:"
|
||||
echo " sudo nano $INSTALL_DIR/adguard-ratelimit.conf"
|
||||
echo ""
|
||||
echo " 2. Erst im Dry-Run testen:"
|
||||
echo " sudo $INSTALL_DIR/adguard-ratelimit.sh dry-run"
|
||||
echo ""
|
||||
echo " 3. Service starten:"
|
||||
echo " sudo systemctl start adguard-ratelimit"
|
||||
echo ""
|
||||
echo " 4. Status prüfen:"
|
||||
echo " sudo systemctl status adguard-ratelimit"
|
||||
echo " sudo $INSTALL_DIR/adguard-ratelimit.sh status"
|
||||
echo ""
|
||||
echo " 5. Logs verfolgen:"
|
||||
echo " sudo journalctl -u adguard-ratelimit -f"
|
||||
echo " sudo tail -f /var/log/adguard-ratelimit.log"
|
||||
echo ""
|
||||
echo " Weitere Befehle:"
|
||||
echo " sudo $INSTALL_DIR/iptables-helper.sh status"
|
||||
echo " sudo $INSTALL_DIR/adguard-ratelimit.sh flush"
|
||||
echo " sudo $INSTALL_DIR/adguard-ratelimit.sh unban <IP>"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Deinstallation ─────────────────────────────────────────────────────────
|
||||
uninstall() {
|
||||
echo -e "${YELLOW}Deinstalliere AdGuard Shield...${NC}"
|
||||
echo ""
|
||||
|
||||
# Service stoppen und deaktivieren
|
||||
if systemctl is-active adguard-ratelimit &>/dev/null; then
|
||||
systemctl stop adguard-ratelimit
|
||||
echo " ✅ Service gestoppt"
|
||||
fi
|
||||
if systemctl is-enabled adguard-ratelimit &>/dev/null; then
|
||||
systemctl disable adguard-ratelimit
|
||||
echo " ✅ Service deaktiviert"
|
||||
fi
|
||||
rm -f "$SERVICE_FILE"
|
||||
systemctl daemon-reload
|
||||
echo " ✅ Service-Datei entfernt"
|
||||
|
||||
# iptables Chain aufräumen
|
||||
if [[ -f "$INSTALL_DIR/iptables-helper.sh" ]]; then
|
||||
bash "$INSTALL_DIR/iptables-helper.sh" remove || true
|
||||
fi
|
||||
|
||||
# Dateien entfernen
|
||||
read -rp " Konfiguration und Logs behalten? [j/N]: " keep
|
||||
if [[ "${keep,,}" == "j" ]]; then
|
||||
rm -f "$INSTALL_DIR/adguard-ratelimit.sh"
|
||||
rm -f "$INSTALL_DIR/iptables-helper.sh"
|
||||
echo " ✅ Scripts entfernt (Konfiguration behalten)"
|
||||
else
|
||||
rm -rf "$INSTALL_DIR"
|
||||
rm -rf /var/lib/adguard-ratelimit
|
||||
rm -f /var/log/adguard-ratelimit.log*
|
||||
echo " ✅ Alles entfernt"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Deinstallation abgeschlossen.${NC}"
|
||||
}
|
||||
|
||||
# ─── Hauptprogramm ──────────────────────────────────────────────────────────
|
||||
case "${1:-install}" in
|
||||
install)
|
||||
print_header
|
||||
check_root
|
||||
check_dependencies
|
||||
install_files
|
||||
configure
|
||||
install_service
|
||||
test_connection
|
||||
print_summary
|
||||
;;
|
||||
uninstall)
|
||||
print_header
|
||||
check_root
|
||||
uninstall
|
||||
;;
|
||||
update)
|
||||
print_header
|
||||
check_root
|
||||
install_files
|
||||
systemctl daemon-reload
|
||||
echo -e "${GREEN}AdGuard Shield Update abgeschlossen. Service neustarten mit: sudo systemctl restart adguard-ratelimit${NC}"
|
||||
;;
|
||||
*)
|
||||
echo "Nutzung: $0 {install|uninstall|update}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
234
iptables-helper.sh
Normal file
234
iptables-helper.sh
Normal file
@@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - iptables Helper
|
||||
# Verwaltet die Firewall-Regeln für AdGuard Shield
|
||||
# Kann auch standalone genutzt werden zur Verwaltung der Sperren
|
||||
###############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-ratelimit.conf"
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "FEHLER: Konfigurationsdatei nicht gefunden: $CONFIG_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
# ─── Chain erstellen ─────────────────────────────────────────────────────────
|
||||
create_chain() {
|
||||
echo "Erstelle iptables Chain: $IPTABLES_CHAIN"
|
||||
|
||||
# IPv4
|
||||
if ! iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
iptables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
iptables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
iptables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
echo " ✅ IPv4 Chain erstellt"
|
||||
else
|
||||
echo " ℹ️ IPv4 Chain existiert bereits"
|
||||
fi
|
||||
|
||||
# IPv6
|
||||
if ! ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
ip6tables -N "$IPTABLES_CHAIN"
|
||||
for port in $BLOCKED_PORTS; do
|
||||
ip6tables -I INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
ip6tables -I INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN"
|
||||
done
|
||||
echo " ✅ IPv6 Chain erstellt"
|
||||
else
|
||||
echo " ℹ️ IPv6 Chain existiert bereits"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Chain entfernen ─────────────────────────────────────────────────────────
|
||||
remove_chain() {
|
||||
echo "Entferne iptables Chain: $IPTABLES_CHAIN"
|
||||
|
||||
# IPv4 - Referenzen entfernen, dann Chain löschen
|
||||
if iptables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
for port in $BLOCKED_PORTS; do
|
||||
iptables -D INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
iptables -D INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
done
|
||||
iptables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
iptables -X "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
echo " ✅ IPv4 Chain entfernt"
|
||||
else
|
||||
echo " ℹ️ IPv4 Chain existiert nicht"
|
||||
fi
|
||||
|
||||
# IPv6
|
||||
if ip6tables -n -L "$IPTABLES_CHAIN" &>/dev/null; then
|
||||
for port in $BLOCKED_PORTS; do
|
||||
ip6tables -D INPUT -p tcp --dport "$port" -j "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
ip6tables -D INPUT -p udp --dport "$port" -j "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
done
|
||||
ip6tables -F "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
ip6tables -X "$IPTABLES_CHAIN" 2>/dev/null || true
|
||||
echo " ✅ IPv6 Chain entfernt"
|
||||
else
|
||||
echo " ℹ️ IPv6 Chain existiert nicht"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── Chain leeren ────────────────────────────────────────────────────────────
|
||||
flush_chain() {
|
||||
echo "Leere iptables Chain: $IPTABLES_CHAIN"
|
||||
iptables -F "$IPTABLES_CHAIN" 2>/dev/null && echo " ✅ IPv4 geleert" || echo " ⚠️ IPv4 Chain nicht gefunden"
|
||||
ip6tables -F "$IPTABLES_CHAIN" 2>/dev/null && echo " ✅ IPv6 geleert" || echo " ⚠️ IPv6 Chain nicht gefunden"
|
||||
|
||||
# State-Dateien auch aufräumen
|
||||
rm -f "${STATE_DIR}"/*.ban 2>/dev/null || true
|
||||
echo " ✅ State-Dateien bereinigt"
|
||||
}
|
||||
|
||||
# ─── IP manuell sperren ─────────────────────────────────────────────────────
|
||||
ban_ip() {
|
||||
local ip="$1"
|
||||
echo "Sperre IP: $ip"
|
||||
|
||||
if [[ "$ip" == *:* ]]; then
|
||||
ip6tables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP
|
||||
echo " ✅ IPv6 Adresse gesperrt"
|
||||
else
|
||||
iptables -I "$IPTABLES_CHAIN" -s "$ip" -j DROP
|
||||
echo " ✅ IPv4 Adresse gesperrt"
|
||||
fi
|
||||
}
|
||||
|
||||
# ─── IP entsperren ──────────────────────────────────────────────────────────
|
||||
unban_ip() {
|
||||
local ip="$1"
|
||||
echo "Entsperre IP: $ip"
|
||||
|
||||
if [[ "$ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null \
|
||||
&& echo " ✅ IPv6 Adresse entsperrt" \
|
||||
|| echo " ⚠️ IPv6 Regel nicht gefunden"
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$ip" -j DROP 2>/dev/null \
|
||||
&& echo " ✅ IPv4 Adresse entsperrt" \
|
||||
|| echo " ⚠️ IPv4 Regel nicht gefunden"
|
||||
fi
|
||||
|
||||
# State-Datei entfernen
|
||||
rm -f "${STATE_DIR}/${ip//[:\/]/_}.ban" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ─── Status anzeigen ─────────────────────────────────────────────────────────
|
||||
show_rules() {
|
||||
echo ""
|
||||
echo "══════════════════════════════════════════════════════════════════"
|
||||
echo " iptables Regeln für Chain: $IPTABLES_CHAIN"
|
||||
echo "══════════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
echo " --- IPv4 ---"
|
||||
if iptables -n -L "$IPTABLES_CHAIN" --line-numbers &>/dev/null; then
|
||||
iptables -n -L "$IPTABLES_CHAIN" --line-numbers -v 2>/dev/null | sed 's/^/ /'
|
||||
else
|
||||
echo " Chain existiert nicht"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " --- IPv6 ---"
|
||||
if ip6tables -n -L "$IPTABLES_CHAIN" --line-numbers &>/dev/null; then
|
||||
ip6tables -n -L "$IPTABLES_CHAIN" --line-numbers -v 2>/dev/null | sed 's/^/ /'
|
||||
else
|
||||
echo " Chain existiert nicht"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " --- Aktive Sperren (State) ---"
|
||||
local count=0
|
||||
if [[ -d "$STATE_DIR" ]]; then
|
||||
for f in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$f" ]] || continue
|
||||
count=$((count + 1))
|
||||
local ip domain ban_time ban_until
|
||||
ip=$(grep '^CLIENT_IP=' "$f" | cut -d= -f2)
|
||||
domain=$(grep '^DOMAIN=' "$f" | cut -d= -f2)
|
||||
ban_time=$(grep '^BAN_TIME=' "$f" | cut -d= -f2)
|
||||
ban_until=$(grep '^BAN_UNTIL=' "$f" | cut -d= -f2)
|
||||
printf " %-20s %-30s seit %-20s bis %s\n" "$ip" "$domain" "$ban_time" "$ban_until"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ $count -eq 0 ]]; then
|
||||
echo " Keine aktiven Sperren"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Persistenz (iptables-save/restore kompatibel) ──────────────────────────
|
||||
save_rules() {
|
||||
local save_file="${STATE_DIR}/iptables-rules.v4"
|
||||
local save_file6="${STATE_DIR}/iptables-rules.v6"
|
||||
|
||||
iptables-save > "$save_file" 2>/dev/null && echo " ✅ IPv4 Regeln gespeichert: $save_file"
|
||||
ip6tables-save > "$save_file6" 2>/dev/null && echo " ✅ IPv6 Regeln gespeichert: $save_file6"
|
||||
}
|
||||
|
||||
restore_rules() {
|
||||
local save_file="${STATE_DIR}/iptables-rules.v4"
|
||||
local save_file6="${STATE_DIR}/iptables-rules.v6"
|
||||
|
||||
[[ -f "$save_file" ]] && iptables-restore < "$save_file" && echo " ✅ IPv4 Regeln wiederhergestellt"
|
||||
[[ -f "$save_file6" ]] && ip6tables-restore < "$save_file6" && echo " ✅ IPv6 Regeln wiederhergestellt"
|
||||
}
|
||||
|
||||
# ─── Hauptprogramm ──────────────────────────────────────────────────────────
|
||||
case "${1:-help}" in
|
||||
create)
|
||||
create_chain
|
||||
;;
|
||||
remove)
|
||||
remove_chain
|
||||
;;
|
||||
flush)
|
||||
flush_chain
|
||||
;;
|
||||
ban)
|
||||
[[ -z "${2:-}" ]] && { echo "Nutzung: $0 ban <IP>" >&2; exit 1; }
|
||||
ban_ip "$2"
|
||||
;;
|
||||
unban)
|
||||
[[ -z "${2:-}" ]] && { echo "Nutzung: $0 unban <IP>" >&2; exit 1; }
|
||||
unban_ip "$2"
|
||||
;;
|
||||
status|show)
|
||||
show_rules
|
||||
;;
|
||||
save)
|
||||
save_rules
|
||||
;;
|
||||
restore)
|
||||
restore_rules
|
||||
;;
|
||||
*)
|
||||
cat << USAGE
|
||||
iptables Helper für AdGuard Rate-Limit
|
||||
|
||||
Nutzung: $0 {create|remove|flush|ban|unban|status|save|restore}
|
||||
|
||||
Befehle:
|
||||
create Erstellt die iptables Chain
|
||||
remove Entfernt die Chain und alle Regeln
|
||||
flush Leert alle Regeln in der Chain
|
||||
ban <IP> Sperrt eine IP-Adresse manuell
|
||||
unban <IP> Entsperrt eine IP-Adresse
|
||||
status Zeigt alle aktuellen Regeln
|
||||
save Speichert die aktuellen Regeln
|
||||
restore Stellt gespeicherte Regeln wieder her
|
||||
|
||||
Chain-Name: $IPTABLES_CHAIN
|
||||
Gesperrte Ports: $BLOCKED_PORTS
|
||||
|
||||
USAGE
|
||||
;;
|
||||
esac
|
||||
75
unban-expired.sh
Normal file
75
unban-expired.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# AdGuard Shield - Cron-basierter Unban-Timer
|
||||
# Kann als Alternative zum Haupt-Script für das Entsperren genutzt werden.
|
||||
# Wird z.B. alle 5 Minuten per Cron aufgerufen um abgelaufene Sperren zu prüfen.
|
||||
#
|
||||
# Crontab-Eintrag:
|
||||
# */5 * * * * /opt/adguard-ratelimit/unban-expired.sh
|
||||
###############################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="${SCRIPT_DIR}/adguard-ratelimit.conf"
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
source "$CONFIG_FILE"
|
||||
|
||||
BAN_HISTORY_FILE="${BAN_HISTORY_FILE:-/var/log/adguard-ratelimit-bans.log}"
|
||||
LOG_PREFIX="[$(date '+%Y-%m-%d %H:%M:%S')] [UNBAN-TIMER]"
|
||||
NOW=$(date '+%s')
|
||||
|
||||
# History-Eintrag schreiben
|
||||
log_ban_history() {
|
||||
local action="$1"
|
||||
local client_ip="$2"
|
||||
local domain="${3:-}"
|
||||
local count="${4:-}"
|
||||
local reason="${5:-}"
|
||||
local timestamp
|
||||
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
|
||||
if [[ ! -f "$BAN_HISTORY_FILE" ]]; then
|
||||
echo "# AdGuard Shield - Ban History" > "$BAN_HISTORY_FILE"
|
||||
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | GRUND" >> "$BAN_HISTORY_FILE"
|
||||
echo "#─────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
|
||||
fi
|
||||
|
||||
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %s\n" \
|
||||
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "-" "${reason:-expired}" \
|
||||
>> "$BAN_HISTORY_FILE"
|
||||
}
|
||||
|
||||
unban_count=0
|
||||
|
||||
for state_file in "${STATE_DIR}"/*.ban; do
|
||||
[[ -f "$state_file" ]] || continue
|
||||
|
||||
ban_until_epoch=$(grep '^BAN_UNTIL_EPOCH=' "$state_file" | cut -d= -f2)
|
||||
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
|
||||
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2)
|
||||
|
||||
if [[ -n "$ban_until_epoch" && "$NOW" -ge "$ban_until_epoch" ]]; then
|
||||
echo "$LOG_PREFIX Entsperre abgelaufene Sperre: $client_ip" >> "$LOG_FILE"
|
||||
|
||||
# iptables Regel entfernen
|
||||
if [[ "$client_ip" == *:* ]]; then
|
||||
ip6tables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
else
|
||||
iptables -D "$IPTABLES_CHAIN" -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Ban-History Eintrag
|
||||
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "expired-cron"
|
||||
|
||||
rm -f "$state_file"
|
||||
unban_count=$((unban_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $unban_count -gt 0 ]]; then
|
||||
echo "$LOG_PREFIX $unban_count Sperren aufgehoben" >> "$LOG_FILE"
|
||||
fi
|
||||
Reference in New Issue
Block a user