DNS Protokolle werden nun im Log und auch sonst überall angezeigt.

This commit is contained in:
2026-03-04 20:32:39 +01:00
parent be504eaad9
commit c055f7f1d7
5 changed files with 131 additions and 43 deletions

View File

@@ -48,6 +48,8 @@ IPTABLES_CHAIN="ADGUARD_SHIELD"
# Port 53 = DNS (UDP + TCP)
# Port 443 = DNS-over-HTTPS (DoH)
# Port 853 = DNS-over-TLS (tls://...:853) / DNS-over-QUIC (quic://...:853)
# Hinweis: Das verwendete Protokoll (DNS/DoH/DoT/DoQ) wird automatisch
# aus der AdGuard Home API erkannt und in Logs/History angezeigt.
BLOCKED_PORTS="53 443 853"
# --- Whitelist ---

View File

@@ -79,23 +79,25 @@ log_ban_history() {
local count="${4:-}"
local reason="${5:-}"
local duration="${6:-}"
local protocol="${7:-}"
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"
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | GRUND" >> "$BAN_HISTORY_FILE"
echo "#──────────────────────────────────────────────────────────────────────────────────────────────────" >> "$BAN_HISTORY_FILE"
fi
if [[ -z "$duration" && "$action" == "BAN" ]]; then
duration="${BAN_DURATION}s"
fi
[[ -z "$duration" ]] && duration="-"
[[ -z "$protocol" ]] && protocol="-"
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %s\n" \
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "$duration" "${reason:-rate-limit}" \
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "$duration" "$protocol" "${reason:-rate-limit}" \
>> "$BAN_HISTORY_FILE"
}
@@ -213,6 +215,22 @@ reset_offense_level() {
rm -f "$offense_file"
}
# ─── Protokoll-Erkennung ─────────────────────────────────────────────────────
# Wandelt AdGuard Home client_proto Werte in lesbare Protokoll-Namen um
# API-Werte: "" = Plain DNS, "doh" = DNS-over-HTTPS, "dot" = DNS-over-TLS,
# "doq" = DNS-over-QUIC, "dnscrypt" = DNSCrypt
format_protocol() {
local proto="$1"
case "${proto,,}" in
doh) echo "DoH" ;;
dot) echo "DoT" ;;
doq) echo "DoQ" ;;
dnscrypt) echo "DNSCrypt" ;;
""|dns) echo "DNS" ;;
*) echo "${proto:-DNS}" ;;
esac
}
# ─── Verzeichnisse erstellen ──────────────────────────────────────────────────
init_directories() {
mkdir -p "$STATE_DIR"
@@ -298,6 +316,7 @@ ban_client() {
local count="$3"
local reason="${4:-rate-limit}"
local window="${5:-$RATE_LIMIT_WINDOW}"
local protocol="${6:-DNS}"
# Prüfen ob bereits gesperrt
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
@@ -335,18 +354,18 @@ ban_client() {
if [[ "$DRY_RUN" == "true" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s) für ${duration_display} [Stufe $offense_level] [${reason}]"
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) [${reason}]"
log "WARN" "[DRY-RUN] WÜRDE sperren: $client_ip (${count}x $domain in ${window}s via $protocol) [${reason}]"
fi
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run (${reason})" "${duration_display}"
log_ban_history "DRY" "$client_ip" "$domain" "$count" "dry-run (${reason})" "${duration_display}" "$protocol"
return 0
fi
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]]; then
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s) für ${duration_display} [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}] [${reason}]"
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) für ${duration_display} [${reason}]"
log "WARN" "SPERRE Client: $client_ip (${count}x $domain in ${window}s via $protocol) für ${duration_display} [${reason}]"
fi
# IPv4 oder IPv6 erkennen
@@ -370,16 +389,17 @@ BAN_DURATION=${effective_duration}
OFFENSE_LEVEL=$offense_level
IS_PERMANENT=$is_permanent
REASON=$reason
PROTOCOL=$protocol
EOF
# Ban-History Eintrag
local history_duration="${duration_display}"
[[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" ]] && history_duration="${duration_display} (Stufe ${offense_level})"
log_ban_history "BAN" "$client_ip" "$domain" "$count" "$reason" "$history_duration"
log_ban_history "BAN" "$client_ip" "$domain" "$count" "$reason" "$history_duration" "$protocol"
# Benachrichtigung senden
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
send_notification "ban" "$client_ip" "$domain" "$count" "$offense_level" "$duration_display" "$reason" "$window"
send_notification "ban" "$client_ip" "$domain" "$count" "$offense_level" "$duration_display" "$reason" "$window" "$protocol"
fi
}
@@ -389,11 +409,14 @@ unban_client() {
local reason="${2:-expired}"
local state_file="${STATE_DIR}/${client_ip//[:\/]/_}.ban"
# Domain aus State lesen bevor wir löschen
# Domain und Protokoll aus State lesen bevor wir löschen
local domain="-"
local protocol="-"
if [[ -f "$state_file" ]]; then
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2 || true)
protocol=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2 || true)
fi
[[ -z "$protocol" ]] && protocol="-"
log "INFO" "ENTSPERRE Client: $client_ip ($reason)"
@@ -406,7 +429,7 @@ unban_client() {
rm -f "$state_file"
# Ban-History Eintrag
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason"
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "$reason" "-" "$protocol"
if [[ "$NOTIFY_ENABLED" == "true" ]]; then
send_notification "unban" "$client_ip" "" ""
@@ -450,6 +473,7 @@ send_notification() {
local duration_display="${6:-}"
local reason="${7:-rate-limit}"
local window="${8:-$RATE_LIMIT_WINDOW}"
local protocol="${9:-DNS}"
# Ntfy benötigt keine Webhook-URL (nutzt NTFY_SERVER_URL + NTFY_TOPIC)
if [[ "$NOTIFY_TYPE" != "ntfy" && -z "$NOTIFY_WEBHOOK_URL" ]]; then
@@ -462,11 +486,11 @@ send_notification() {
local message
if [[ "$action" == "ban" ]]; then
if [[ "${PROGRESSIVE_BAN_ENABLED:-false}" == "true" && -n "$offense_level" ]]; then
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s, ${reason_label}). Sperre für **${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]."
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s via **$protocol**, ${reason_label}). Sperre für **${duration_display}** [Stufe ${offense_level}/${PROGRESSIVE_BAN_MAX_LEVEL:-0}]."
else
local simple_dur
simple_dur=$(format_duration "${BAN_DURATION}")
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s, ${reason_label}). Sperre für ${simple_dur}."
message="🚫 AdGuard Shield: Client **$client_ip** gesperrt (${count}x $domain in ${window}s via **$protocol**, ${reason_label}). Sperre für ${simple_dur}."
fi
elif [[ "$action" == "service_start" ]]; then
message="🟢 AdGuard Shield v${VERSION} wurde gestartet."
@@ -588,10 +612,11 @@ analyze_queries() {
entry_count=$(echo "$api_response" | jq '.data // [] | length' 2>/dev/null || echo "0")
log "INFO" "API-Abfrage: ${entry_count} Einträge erhalten, prüfe Zeitfenster ${RATE_LIMIT_WINDOW}s..."
# Extrahiere Client-IP + Domain Paare aus dem Zeitfenster
# Extrahiere Client-IP + Domain + Protokoll Paare aus dem Zeitfenster
# und zähle die Häufigkeit pro (client, domain) Kombination
# Unterstützt .question.name (alte API) und .question.host (neue API)
# Unterstützt Timestamps mit UTC ("Z") und Zeitzonen-Offset ("+01:00")
# Protokoll: client_proto aus der API → ""/dns = Plain DNS, doh, dot, doq, dnscrypt
local violations=""
violations=$(echo "$api_response" | jq -r --argjson window_start "$window_start" '
# ISO 8601 Timestamp zu Unix-Epoch konvertieren
@@ -620,18 +645,20 @@ analyze_queries() {
select((.time | to_epoch) >= $window_start) |
{
client: (.client // .client_info.ip // "unknown"),
domain: ((.question.name // .question.host // "unknown") | rtrimstr("."))
domain: ((.question.name // .question.host // "unknown") | rtrimstr(".")),
proto: (.client_proto // "")
}
] |
group_by(.client + "|" + .domain) |
map({
client: .[0].client,
domain: .[0].domain,
count: length
count: length,
protocols: ([.[].proto | if . == "" then "dns" else . end] | unique | join(","))
}) |
.[] |
select(.count > 0) |
"\(.client)|\(.domain)|\(.count)"
"\(.client)|\(.domain)|\(.count)|\(.protocols)"
') || {
log "ERROR" "jq Analyse fehlgeschlagen - API-Antwort-Format prüfen (ist AdGuard Home erreichbar?)"
return
@@ -643,18 +670,31 @@ analyze_queries() {
fi
# Prüfe jede Kombination gegen das Limit
while IFS='|' read -r client domain count; do
while IFS='|' read -r client domain count protocols; do
[[ -z "$client" || -z "$domain" || -z "$count" ]] && continue
log "INFO" "Client: $client, Domain: $domain, Anfragen: $count/$RATE_LIMIT_MAX_REQUESTS"
# Protokoll-Namen formatieren für die Anzeige
local proto_display=""
if [[ -n "$protocols" ]]; then
local -a proto_parts=()
IFS=',' read -ra raw_protos <<< "$protocols"
for p in "${raw_protos[@]}"; do
proto_parts+=("$(format_protocol "$p")")
done
proto_display=$(IFS=','; echo "${proto_parts[*]}")
else
proto_display="DNS"
fi
log "INFO" "Client: $client, Domain: $domain, Anfragen: $count/$RATE_LIMIT_MAX_REQUESTS, Protokoll: $proto_display"
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)"
log "INFO" "Client $client ist auf der Whitelist - keine Sperre (${count}x $domain via $proto_display)"
continue
fi
ban_client "$client" "$domain" "$count"
ban_client "$client" "$domain" "$count" "rate-limit" "$RATE_LIMIT_WINDOW" "$proto_display"
fi
done <<< "$violations"
}
@@ -717,7 +757,8 @@ analyze_subdomain_flood() {
{
client: (.client // .client_info.ip // "unknown"),
domain: $domain,
base_domain: $base
base_domain: $base,
proto: (.client_proto // "")
}
] |
# Nur Einträge mit echten Subdomains (domain != base_domain)
@@ -728,11 +769,12 @@ analyze_subdomain_flood() {
base_domain: .[0].base_domain,
unique_subdomains: ([.[].domain] | unique | length),
total_queries: length,
example_domains: ([.[].domain] | unique | .[0:3] | join(", "))
example_domains: ([.[].domain] | unique | .[0:3] | join(", ")),
protocols: ([.[].proto | if . == "" then "dns" else . end] | unique | join(","))
}) |
.[] |
select(.unique_subdomains > $max_unique) |
"\(.client)|\(.base_domain)|\(.unique_subdomains)|\(.total_queries)|\(.example_domains)"
"\(.client)|\(.base_domain)|\(.unique_subdomains)|\(.total_queries)|\(.example_domains)|\(.protocols)"
') || {
log "ERROR" "jq Subdomain-Flood-Analyse fehlgeschlagen"
return
@@ -744,13 +786,26 @@ analyze_subdomain_flood() {
fi
# Gefundene Verstöße verarbeiten
while IFS='|' read -r client base_domain unique_count total_count examples; do
while IFS='|' read -r client base_domain unique_count total_count examples protocols; do
[[ -z "$client" || -z "$base_domain" || -z "$unique_count" ]] && continue
log "WARN" "Subdomain-Flood erkannt: $client${unique_count} eindeutige Subdomains von $base_domain (${total_count} Anfragen, z.B. $examples)"
# Protokoll-Namen formatieren
local proto_display=""
if [[ -n "$protocols" ]]; then
local -a proto_parts=()
IFS=',' read -ra raw_protos <<< "$protocols"
for p in "${raw_protos[@]}"; do
proto_parts+=("$(format_protocol "$p")")
done
proto_display=$(IFS=','; echo "${proto_parts[*]}")
else
proto_display="DNS"
fi
log "WARN" "Subdomain-Flood erkannt: $client${unique_count} eindeutige Subdomains von $base_domain (${total_count} Anfragen via $proto_display, z.B. $examples)"
if is_whitelisted "$client"; then
log "INFO" "Client $client ist auf der Whitelist - keine Sperre (Subdomain-Flood: ${unique_count}x $base_domain)"
log "INFO" "Client $client ist auf der Whitelist - keine Sperre (Subdomain-Flood: ${unique_count}x $base_domain via $proto_display)"
continue
fi
@@ -761,7 +816,7 @@ analyze_subdomain_flood() {
continue
fi
ban_client "$client" "*.${base_domain}" "$unique_count" "subdomain-flood" "$window"
ban_client "$client" "*.${base_domain}" "$unique_count" "subdomain-flood" "$window" "$proto_display"
done <<< "$violations"
}
@@ -795,7 +850,7 @@ show_status() {
for state_file in "${STATE_DIR}"/*.ban; do
[[ -f "$state_file" ]] || continue
ban_count=$((ban_count + 1))
local s_ip s_domain s_level s_perm s_dur s_until s_reason s_count
local s_ip s_domain s_level s_perm s_dur s_until s_reason s_count s_proto
s_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2 || true)
s_domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2 || true)
s_level=$(grep '^OFFENSE_LEVEL=' "$state_file" | cut -d= -f2 || true)
@@ -804,7 +859,9 @@ show_status() {
s_until=$(grep '^BAN_UNTIL=' "$state_file" | cut -d= -f2 || true)
s_reason=$(grep '^REASON=' "$state_file" | cut -d= -f2 || true)
s_count=$(grep '^COUNT=' "$state_file" | cut -d= -f2 || true)
s_proto=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2 || true)
s_reason="${s_reason:-rate-limit}"
s_proto="${s_proto:-?}"
local reason_tag=""
[[ "$s_reason" == "subdomain-flood" ]] && reason_tag=" (Subdomain-Flood)"
@@ -818,12 +875,14 @@ show_status() {
fi
fi
local proto_tag=" via ${s_proto}"
if [[ "$s_perm" == "true" ]]; then
echo " 🚫 Gesperrt: $s_ip$s_domain [PERMANENT, Stufe ${s_level:-?}${count_info}]${reason_tag}"
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}]${reason_tag}"
echo " 🚫 Gesperrt: $s_ip$s_domain [Stufe ${s_level}, $(format_duration "${s_dur:-$BAN_DURATION}"), bis $s_until${count_info}${proto_tag}]${reason_tag}"
else
echo " 🚫 Gesperrt: $s_ip$s_domain [bis $s_until${count_info}]${reason_tag}"
echo " 🚫 Gesperrt: $s_ip$s_domain [bis $s_until${count_info}${proto_tag}]${reason_tag}"
fi
done
fi

View File

@@ -184,6 +184,29 @@ Bei einem Rate-Limit-Verstoß werden **alle** DNS-Protokoll-Ports für den Clien
| 853 | TCP | DNS-over-TLS (`tls://dns1.techniverse.net:853`) |
| 853 | UDP | DNS-over-QUIC (`quic://dns1.techniverse.net:853`) |
## Protokoll-Erkennung
AdGuard Shield erkennt **automatisch**, welches DNS-Protokoll ein Client verwendet. Diese Information wird aus dem Feld `client_proto` der AdGuard Home Query Log API extrahiert und an folgenden Stellen angezeigt:
- **Log-Datei**: Jede Anfrage wird mit dem verwendeten Protokoll geloggt
- **Ban-History**: Die Protokoll-Spalte zeigt, über welches Protokoll die Anfragen kamen
- **Status-Anzeige**: Aktive Sperren zeigen das verwendete Protokoll an
- **Benachrichtigungen**: Push-Nachrichten enthalten das Protokoll
### Unterstützte Protokolle
| API-Wert | Anzeige | Beschreibung |
|----------|---------|-------------|
| *(leer)* | `DNS` | Klassisches DNS über UDP/TCP (Port 53) |
| `doh` | `DoH` | DNS-over-HTTPS (Port 443) |
| `dot` | `DoT` | DNS-over-TLS (Port 853) |
| `doq` | `DoQ` | DNS-over-QUIC (Port 853/UDP) |
| `dnscrypt` | `DNSCrypt` | DNSCrypt-Protokoll |
Verwendet ein Client mehrere Protokolle gleichzeitig (z.B. DoH und DNS), werden alle erkannten Protokolle kommagetrennt angezeigt (z.B. `DNS,DoH`).
> **Wichtig:** Alle Protokolle werden gleichermaßen überwacht und gegen das Rate-Limit geprüft. Ein DoH-Flood wird genauso erkannt und gesperrt wie ein klassischer DNS-Flood die Erkennung basiert auf den AdGuard Home Logdaten, nicht auf Netzwerk-Traffic.
## Whitelist richtig pflegen
Die Whitelist sollte mindestens enthalten:

View File

@@ -53,15 +53,15 @@ log_ban_history() {
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"
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | 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" \
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
"$timestamp" "$action" "$client_ip" "-" "-" "$duration" "-" "$reason" \
>> "$BAN_HISTORY_FILE"
}

View File

@@ -29,17 +29,20 @@ log_ban_history() {
local domain="${3:-}"
local count="${4:-}"
local reason="${5:-}"
local protocol="${6:-}"
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"
echo "# Format: ZEITSTEMPEL | AKTION | CLIENT-IP | DOMAIN | ANFRAGEN | SPERRDAUER | PROTOKOLL | 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}" \
[[ -z "$protocol" ]] && protocol="-"
printf "%-19s | %-6s | %-39s | %-30s | %-8s | %-10s | %-10s | %s\n" \
"$timestamp" "$action" "$client_ip" "${domain:--}" "${count:--}" "-" "$protocol" "${reason:-expired}" \
>> "$BAN_HISTORY_FILE"
}
@@ -52,6 +55,7 @@ for state_file in "${STATE_DIR}"/*.ban; do
client_ip=$(grep '^CLIENT_IP=' "$state_file" | cut -d= -f2)
domain=$(grep '^DOMAIN=' "$state_file" | cut -d= -f2)
is_permanent=$(grep '^IS_PERMANENT=' "$state_file" | cut -d= -f2)
protocol=$(grep '^PROTOCOL=' "$state_file" | cut -d= -f2)
# Permanente Sperren nicht automatisch aufheben
if [[ "$is_permanent" == "true" || "$ban_until_epoch" == "0" ]]; then
@@ -69,7 +73,7 @@ for state_file in "${STATE_DIR}"/*.ban; do
fi
# Ban-History Eintrag
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "expired-cron"
log_ban_history "UNBAN" "$client_ip" "$domain" "-" "expired-cron" "${protocol:-}"
rm -f "$state_file"
unban_count=$((unban_count + 1))