Merge pull request 'v0.9.0' (#18) from v0.9.0 into main

Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
2026-04-28 21:36:28 +00:00
7 changed files with 137 additions and 15 deletions

View File

@@ -32,6 +32,7 @@ Das schützt klassische DNS-Anfragen genauso wie DoH, DoT und DoQ, ohne deine be
- Automatische Sperren bei Rate-Limit-Verstößen
- Erkennung von Random-Subdomain-Floods, z.B. `abc123.example.com`
- DNS-Flood-Watchlist: sofortiger permanenter Ban + AbuseIPDB-Meldung für definierte Domains
- Progressive Sperren für Wiederholungstäter, ähnlich wie bei fail2ban
- Unterstützung für DNS, DoH, DoT, DoQ und DNSCrypt
- IPv4 und IPv6
@@ -127,6 +128,7 @@ Wichtige Startpunkte:
- `RATE_LIMIT_MAX_REQUESTS`, `RATE_LIMIT_WINDOW` und `CHECK_INTERVAL` für die Erkennung
- `BAN_DURATION` und `PROGRESSIVE_BAN_*` für temporäre und progressive Sperren
- `WHITELIST` für vertrauenswürdige Clients wie Router, Management-IPs oder lokale Resolver
- `DNS_FLOOD_WATCHLIST_*` für sofortigen Permanent-Ban bei bekannten Flood-Domains
- `NOTIFY_*`, `REPORT_*`, `GEOIP_*`, `EXTERNAL_BLOCKLIST_*` und `EXTERNAL_WHITELIST_*` für optionale Funktionen
Bei Updates migriert der Installer die bestehende Konfiguration automatisch: vorhandene Werte bleiben erhalten, neue Parameter werden ergänzt und die alte Datei wird als `adguard-shield.conf.old` gesichert.

View File

@@ -18,6 +18,10 @@ SUBDOMAIN_FLOOD_ENABLED=true
SUBDOMAIN_FLOOD_MAX_UNIQUE=50 # Max. eindeutige Subdomains pro Basisdomain/Client
SUBDOMAIN_FLOOD_WINDOW=60 # Zeitfenster in Sekunden
# --- DNS-Flood-Watchlist ---
DNS_FLOOD_WATCHLIST_ENABLED=false
DNS_FLOOD_WATCHLIST="" # Kommagetrennt, z.B. "example.com,evil.org"
# --- Sperr-Einstellungen ---
BAN_DURATION=3600 # Basis-Sperrdauer in Sekunden
IPTABLES_CHAIN="ADGUARD_SHIELD"

View File

@@ -8,7 +8,7 @@
# Lizenz: MIT
###############################################################################
VERSION="v0.8.2"
VERSION="v0.9.0"
set -euo pipefail
@@ -369,6 +369,32 @@ is_whitelisted() {
return 1
}
# ─── DNS-Flood-Watchlist Prüfung ────────────────────────────────────────────
is_dns_flood_watchlist_match() {
local domain="$1"
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" != "true" ]]; then
return 1
fi
if [[ -z "${DNS_FLOOD_WATCHLIST:-}" ]]; then
return 1
fi
local entry
IFS=',' read -ra watchlist_entries <<< "$DNS_FLOOD_WATCHLIST"
for entry in "${watchlist_entries[@]}"; do
entry=$(echo "$entry" | xargs)
[[ -z "$entry" ]] && continue
if [[ "$domain" == "$entry" || "$domain" == *".$entry" ]]; then
return 0
fi
done
return 1
}
# ─── iptables Chain Setup ────────────────────────────────────────────────────
setup_iptables_chain() {
# IPv4 Chain erstellen falls nicht vorhanden
@@ -425,6 +451,13 @@ ban_client() {
fi
fi
# DNS-Flood-Watchlist: Sofort permanent sperren
if [[ "$reason" == "dns-flood-watchlist" ]]; then
is_permanent=true
effective_duration=0
log "WARN" "DNS-Flood-Watchlist: Erzwinge permanente Sperre für $client_ip ($domain)"
fi
local ban_until
local ban_until_display
if [[ "$is_permanent" == "true" ]]; then
@@ -439,16 +472,16 @@ ban_client() {
duration_display=$(format_duration "$effective_duration")
if [[ "$DRY_RUN" == "true" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]]; then
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe $offense_level] [${reason}]"
else
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) [${reason}]"
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
fi
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run (${reason})" "${duration_display}" "$protocol"
return 0
fi
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]]; then
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}] [${reason}]"
else
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
@@ -480,7 +513,7 @@ EOF
# Ban-History Eintrag
local history_duration="${duration_display}"
[[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]] && history_duration="${duration_display} (Stufe ${offense_level})"
[[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && "$reason" != "dns-flood-watchlist" ]] && history_duration="${duration_display} (Stufe ${offense_level})"
log_ban_history "BAN" "$client_ip" "$domain" "$count" "$reason" "$history_duration" "$protocol"
# Benachrichtigung senden
@@ -574,6 +607,7 @@ send_notification() {
local reason_label="Rate-Limit"
[[ "$reason" == "subdomain-flood" ]] && reason_label="Subdomain-Flood"
[[ "$reason" == "dns-flood-watchlist" ]] && reason_label="DNS-Flood-Watchlist"
local title
local message
@@ -591,12 +625,12 @@ send_notification() {
abuseipdb_hint=$'\n⚠ IP wurde an AbuseIPDB gemeldet'
fi
# Dauer-Anzeige mit Stufe
# Dauer-Anzeige mit Stufe (nicht bei Watchlist dort ist es immer permanent)
local dur_line
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" && "$reason" != "dns-flood-watchlist" ]]; then
dur_line="**${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]"
else
dur_line=$(format_duration "${BAN_DURATION}")
dur_line="**${duration_display}**"
fi
message="🚫 AdGuard Shield Ban auf ${my_hostname}${abuseipdb_hint}
@@ -839,7 +873,13 @@ analyze_queries() {
continue
fi
ban_client "$client" "$domain" "$count" "rate-limit" "$RATE_LIMIT_WINDOW" "$proto_display"
local ban_reason="rate-limit"
if is_dns_flood_watchlist_match "$domain"; then
ban_reason="dns-flood-watchlist"
log "WARN" "DNS-Flood-Watchlist Treffer: $client$domain (${count}x in ${RATE_LIMIT_WINDOW}s) → permanenter Ban + AbuseIPDB"
fi
ban_client "$client" "$domain" "$count" "$ban_reason" "$RATE_LIMIT_WINDOW" "$proto_display"
fi
done <<< "$violations"
}
@@ -961,7 +1001,13 @@ analyze_subdomain_flood() {
continue
fi
ban_client "$client" "*.${base_domain}" "$unique_count" "subdomain-flood" "$window" "$proto_display"
local ban_reason="subdomain-flood"
if is_dns_flood_watchlist_match "$base_domain"; then
ban_reason="dns-flood-watchlist"
log "WARN" "DNS-Flood-Watchlist Treffer (Subdomain-Flood): $client → *.${base_domain} → permanenter Ban + AbuseIPDB"
fi
ban_client "$client" "*.${base_domain}" "$unique_count" "$ban_reason" "$window" "$proto_display"
done <<< "$violations"
}
@@ -989,6 +1035,14 @@ show_status() {
echo ""
fi
# DNS-Flood-Watchlist Info
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" == "true" ]]; then
echo " 🎯 DNS-Flood-Watchlist: AKTIV"
echo " Domains: ${DNS_FLOOD_WATCHLIST:-<keine>}"
echo " Aktion: Sofort permanenter Ban + AbuseIPDB-Meldung"
echo ""
fi
# GeoIP-Filter Info
if [[ "${GEOIP_ENABLED:-false}" == "true" ]]; then
local geoip_mode_label
@@ -1021,6 +1075,7 @@ show_status() {
local reason_tag=""
[[ "$s_reason" == "subdomain-flood" ]] && reason_tag=" (Subdomain-Flood)"
[[ "$s_reason" == "dns-flood-watchlist" ]] && reason_tag=" (DNS-Flood-Watchlist)"
local count_info=""
if [[ -n "$s_count" && "$s_count" != "-" ]]; then
@@ -1033,7 +1088,9 @@ show_status() {
local proto_tag=" via ${s_proto}"
if [[ "$s_perm" == "true" ]]; then
if [[ "$s_perm" == "true" && "$s_reason" == "dns-flood-watchlist" ]]; then
echo " 🚫 Gesperrt: $s_ip$s_domain [PERMANENT${count_info}${proto_tag}]${reason_tag}"
elif [[ "$s_perm" == "true" ]]; then
echo " 🚫 Gesperrt: $s_ip$s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}${proto_tag}]${reason_tag}"
elif [[ -n "$s_level" && "$s_level" -gt 0 ]]; then
echo " 🚫 Gesperrt: $s_ip$s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until${count_info}${proto_tag}]${reason_tag}"
@@ -1312,6 +1369,11 @@ main_loop() {
else
log "INFO" " Subdomain-Flood-Schutz: deaktiviert"
fi
if [[ "${DNS_FLOOD_WATCHLIST_ENABLED:-false}" == "true" ]]; then
log "INFO" " DNS-Flood-Watchlist: AKTIV (Domains: ${DNS_FLOOD_WATCHLIST:-<keine>})"
else
log "INFO" " DNS-Flood-Watchlist: deaktiviert"
fi
if [[ "${ABUSEIPDB_ENABLED:-false}" == "true" ]]; then
log "INFO" " AbuseIPDB Reporting: AKTIV (Kategorien: ${ABUSEIPDB_CATEGORIES:-4})"
else

View File

@@ -56,6 +56,17 @@
> **Hinweis:** Die Subdomain-Flood-Erkennung hat ein eigenes Zeitfenster (`SUBDOMAIN_FLOOD_WINDOW`) und einen eigenen Schwellwert (`SUBDOMAIN_FLOOD_MAX_UNIQUE`), unabhängig von den Rate-Limit-Einstellungen.
### DNS-Flood-Watchlist-Sperre
1. Client `10.0.0.42` fragt `microsoft.com` 35x in 60 Sekunden an
2. Monitor erkennt: 35 > 30 (Limit überschritten)
3. Domain `microsoft.com` steht auf der DNS-Flood-Watchlist → **sofortige permanente Sperre**
4. Progressive-Ban-Stufe wird ignoriert — kein stufenweises Hochstufen
5. IP wird an AbuseIPDB gemeldet (falls aktiviert)
6. Permanente Sperre bleibt bis zur manuellen Freigabe aktiv
> **Hinweis:** Die Watchlist greift sowohl bei normalen Rate-Limit-Verstößen als auch bei Subdomain-Flood-Erkennungen. Subdomains werden automatisch erkannt: `foo.microsoft.com` matcht den Watchlist-Eintrag `microsoft.com`.
## iptables Strategie
Das Tool erstellt eine eigene Chain `ADGUARD_SHIELD`:
@@ -210,8 +221,10 @@ ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN
|-------|----------|
| `rate-limit` | Automatische Sperre wegen Limit-Überschreitung |
| `subdomain-flood` | Sperre wegen zu vieler eindeutiger Subdomains einer Basisdomain |
| `dns-flood-watchlist` | Sofortige permanente Sperre + AbuseIPDB-Meldung (Domain auf der Watchlist) |
| `dry-run` | Im Dry-Run erkannt (nicht wirklich gesperrt) |
| `dry-run (subdomain-flood)` | Subdomain-Flood im Dry-Run erkannt |
| `dry-run (dns-flood-watchlist)` | Watchlist-Treffer im Dry-Run erkannt |
| `expired` | Automatisch entsperrt nach Ablauf der Sperrdauer |
| `expired-cron` | Entsperrt durch den Cron-Job (`unban-expired.sh`) |
| `manual` | Manuell entsperrt per `unban`-Befehl |

View File

@@ -116,12 +116,12 @@ Bei Sperren aus der **externen Blocklist** werden Benachrichtigungen separat üb
### Service gestartet
**Überschrift:** ✅ AdGuard Shield
> 🟢 AdGuard Shield v0.8.2 wurde auf dns1 gestartet.
> 🟢 AdGuard Shield v0.9.0 wurde auf dns1 gestartet.
### Service gestoppt
**Überschrift:** 🚨 🛡️ AdGuard Shield
> 🔴 AdGuard Shield v0.8.2 wurde auf dns1 gestoppt.
> 🔴 AdGuard Shield v0.9.0 wurde auf dns1 gestoppt.
### Watchdog — Service wiederhergestellt
**Überschrift:** 🔄 AdGuard Shield Watchdog
@@ -167,6 +167,19 @@ Bei permanenter Sperre mit aktiviertem AbuseIPDB-Reporting erscheint zusätzlich
> Whois: https://www.whois.com/whois/95.71.42.116
> AbuseIPDB: https://www.abuseipdb.com/check/95.71.42.116
Bei DNS-Flood-Watchlist-Treffer (sofort permanent, ohne Stufe):
> 🚫 AdGuard Shield Ban auf dns1
> ⚠️ IP wurde an AbuseIPDB gemeldet
> ---
> IP: 95.71.42.116
> Hostname: example-host.provider.net
> Grund: 45x microsoft.com in 60s via DNS, DNS-Flood-Watchlist
> Dauer: PERMANENT
>
> Whois: https://www.whois.com/whois/95.71.42.116
> AbuseIPDB: https://www.abuseipdb.com/check/95.71.42.116
### Entsperrung (Unban)
**Überschrift:** ✅ AdGuard Shield

View File

@@ -70,6 +70,34 @@ xk9z3a.microsoft.com
> **Tipp:** Der Schwellwert `SUBDOMAIN_FLOOD_MAX_UNIQUE` sollte hoch genug sein, um legitime Clients nicht zu stören (z.B. CDNs nutzen oft viele Subdomains). Ein Wert von 50100 ist in den meisten Fällen sinnvoll.
### DNS-Flood-Watchlist
Domains bei denen eine Rate-Limit-Überschreitung **sofort** zu einer **permanenten Sperre** und einer **AbuseIPDB-Meldung** führt — ohne progressive Eskalation. Ideal für bekannte Angriffsziele, die regelmäßig geflutet werden (z.B. `microsoft.com`, `google.com`).
| Parameter | Standard | Beschreibung |
|-----------|----------|--------------|
| `DNS_FLOOD_WATCHLIST_ENABLED` | `false` | DNS-Flood-Watchlist aktivieren |
| `DNS_FLOOD_WATCHLIST` | *(leer)* | Überwachte Domains, kommagetrennt (z.B. `"microsoft.com,google.com"`) |
#### Wie funktioniert die Watchlist?
1. Die reguläre Rate-Limit-Prüfung erkennt, dass ein Client mehr als `RATE_LIMIT_MAX_REQUESTS` Anfragen für eine Domain gestellt hat
2. Zusätzlich wird geprüft, ob die angefragte Domain in der Watchlist steht (inkl. Subdomains: `foo.microsoft.com` matcht `microsoft.com`)
3. Trifft beides zu → **sofortige permanente Sperre** + **AbuseIPDB-Meldung** (falls aktiviert)
Die Watchlist greift sowohl bei normalen Rate-Limit-Verstößen als auch bei Subdomain-Flood-Erkennungen.
#### Beispiel
```bash
DNS_FLOOD_WATCHLIST_ENABLED=true
DNS_FLOOD_WATCHLIST="microsoft.com,google.com,apple.com"
```
→ Ein Client der `35x foo.microsoft.com` in 60s abfragt (bei `RATE_LIMIT_MAX_REQUESTS=30`) wird **sofort permanent** gesperrt und an AbuseIPDB gemeldet.
> **Hinweis:** Damit die AbuseIPDB-Meldung funktioniert, muss `ABUSEIPDB_ENABLED=true` und ein gültiger `ABUSEIPDB_API_KEY` konfiguriert sein. Ohne AbuseIPDB-Konfiguration wird nur permanent gesperrt.
### Sperr-Einstellungen
| Parameter | Standard | Beschreibung |
@@ -251,7 +279,7 @@ sudo systemctl restart adguard-shield
Der Report an AbuseIPDB enthält (auf Englisch):
- **Bei Rate-Limit:** `DNS flooding on our DNS server: 100x microsoft.com in 60s. Banned by Adguard Shield 🔗 https://tnvs.de/as`
- **Bei Rate-Limit / DNS-Flood-Watchlist:** `DNS flooding on our DNS server: 100x microsoft.com in 60s. Banned by Adguard Shield 🔗 https://tnvs.de/as`
- **Bei Subdomain-Flood:** `DNS flooding on our DNS server: 85x *.microsoft.com in 60s (random subdomain attack). Banned by Adguard Shield 🔗 https://tnvs.de/as`
Die Kategorie `4` (DDoS Attack) wird standardmäßig verwendet. Weitere Kategorien können kommagetrennt angegeben werden (z.B. `"4,15"`).

View File

@@ -6,7 +6,7 @@
# Lizenz: MIT
###############################################################################
VERSION="v0.8.2"
VERSION="v0.9.0"
set -euo pipefail