Add warm-nftset.sh — periodic nftset warmer
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 14m41s
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 14m41s
Elements in bbrkn_v4/bbrkn_v6 carry a 1d timeout and are only refreshed when a query reaches the host dnsmasq on :5350, where the nftset= directives are applied. Idle domains — or ones answered from the pihole FTL cache — age out of the sets and their traffic silently falls back to the plain WAN route instead of the tunnel. warm-nftset.sh re-resolves every domain from the deployed 90-nftset.conf (base + related subdomains) directly against 127.0.0.1:5350, bypassing the FTL cache. Sourcing the domain list from the deployed config means there is no second list to keep in sync. Ping is off by default: packets originating on the gateway itself go through OUTPUT, never prerouting_mangle, so they leave unmarked and almost all time out — a misleading signal, not a real failure. Run on the gateway via `make warm` or hourly cron. Measured: 1059 domains in ~1m45s at PARALLEL=8, v4 526->2203, v6 305->1440 elements. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
38d5bdf0ce
commit
660a31c35e
3 changed files with 187 additions and 2 deletions
128
scripts/warm-nftset.sh
Executable file
128
scripts/warm-nftset.sh
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
# ==============================
|
||||
# Прогрев nft-сетов (запускать НА ШЛЮЗЕ)
|
||||
# ==============================
|
||||
# Заново резолвит все домены, которые dnsmasq помечает директивами nftset=,
|
||||
# обращаясь напрямую к host-инстансу 127.0.0.1#5350. Прямой запрос обходит
|
||||
# кэш pihole FTL: запрос, попавший в кэш FTL, до :5350 не доходит и элементы
|
||||
# в inet filter bbrkn_v4/bbrkn_v6 не обновляет — они молча истекают по timeout
|
||||
# и домен уходит мимо туннеля.
|
||||
#
|
||||
# Источник списка доменов — сам задеплоенный конфиг, поэтому отдельный список
|
||||
# синхронизировать не нужно: скрипт всегда работает с тем, что реально
|
||||
# загружен в dnsmasq после последнего успешного деплоя.
|
||||
|
||||
# --- Конфигурация через переменные окружения ---
|
||||
# Задеплоенный конфиг с директивами nftset= (источник списка доменов)
|
||||
NFTSET_CONF="${NFTSET_CONF:-/etc/dnsmasq.d/90-nftset.conf}"
|
||||
# Host-инстанс dnsmasq, который применяет nftset=
|
||||
RESOLVER="${RESOLVER:-127.0.0.1}"
|
||||
RESOLVER_PORT="${RESOLVER_PORT:-5350}"
|
||||
|
||||
# Состояние и лог
|
||||
STATE_DIR="${STATE_DIR:-${HOME}/.local/state/bbrkn}"
|
||||
LOG="${LOG:-${STATE_DIR}/warm.log}"
|
||||
LOCK="${LOCK:-${STATE_DIR}/warm.lock}"
|
||||
LOG_MAX_BYTES="${LOG_MAX_BYTES:-4194304}" # ротация лога, 4 МиБ
|
||||
|
||||
# Параметры опроса
|
||||
PARALLEL="${PARALLEL:-8}" # одновременных запросов к :5350
|
||||
DIG_TIMEOUT="${DIG_TIMEOUT:-3}" # секунд на попытку
|
||||
DIG_TRIES="${DIG_TRIES:-2}"
|
||||
|
||||
# Пинг выключен по умолчанию: пакеты, рождённые на самом шлюзе, идут через
|
||||
# OUTPUT и не проходят цепочку prerouting_mangle — уходят в обычный WAN без
|
||||
# метки и почти все отваливаются по таймауту. Включать только для отладки.
|
||||
WARM_PING="${WARM_PING:-0}"
|
||||
PING_TIMEOUT="${PING_TIMEOUT:-2}"
|
||||
|
||||
NFT="${NFT:-/usr/sbin/nft}"
|
||||
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# Один инстанс: медленный прогон не должен наложиться на следующий по cron.
|
||||
exec 9>"$LOCK"
|
||||
if ! flock -n 9; then
|
||||
echo "$(date -Is) SKIP предыдущий запуск ещё выполняется" >>"$LOG"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log() { echo "$(date -Is) $*" >>"$LOG"; }
|
||||
|
||||
# Количество элементов в сете. У каждого элемента есть timeout, поэтому
|
||||
# "expires" встречается ровно один раз на элемент. Ошибка здесь не фатальна —
|
||||
# это только метрика прогресса.
|
||||
set_count() {
|
||||
sudo -n "$NFT" list set inet filter "$1" 2>/dev/null | grep -c 'expires' || echo "?"
|
||||
}
|
||||
|
||||
if [ ! -r "$NFTSET_CONF" ]; then
|
||||
log "FATAL не читается $NFTSET_CONF"
|
||||
echo "Ошибка: не читается $NFTSET_CONF (скрипт запускается на шлюзе)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t HOSTS < <(sed -n 's|^nftset=/\([^/]*\)/.*|\1|p' "$NFTSET_CONF" | sort -u)
|
||||
if [ "${#HOSTS[@]}" -eq 0 ]; then
|
||||
log "FATAL в $NFTSET_CONF не найдено ни одной директивы nftset="
|
||||
echo "Ошибка: в $NFTSET_CONF не найдено ни одной директивы nftset=" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BEFORE_V4=$(set_count bbrkn_v4)
|
||||
BEFORE_V6=$(set_count bbrkn_v6)
|
||||
log "START domains=${#HOSTS[@]} conf=$NFTSET_CONF v4=$BEFORE_V4 v6=$BEFORE_V6"
|
||||
|
||||
warm_one() {
|
||||
local host="$1" a aaaa
|
||||
a=$(dig +short +timeout="$DIG_TIMEOUT" +tries="$DIG_TRIES" \
|
||||
@"$RESOLVER" -p "$RESOLVER_PORT" A "$host" 2>/dev/null \
|
||||
| grep -E '^[0-9]+\.' | head -1)
|
||||
aaaa=$(dig +short +timeout="$DIG_TIMEOUT" +tries="$DIG_TRIES" \
|
||||
@"$RESOLVER" -p "$RESOLVER_PORT" AAAA "$host" 2>/dev/null \
|
||||
| grep -E '^[0-9a-fA-F]*:' | head -1)
|
||||
|
||||
if [ -z "$a" ] && [ -z "$aaaa" ]; then
|
||||
echo "dnsfail $host"
|
||||
return
|
||||
fi
|
||||
|
||||
# Пинг только подтверждает проходимость маршрута; в сет IP попал уже на
|
||||
# шаге резолва выше.
|
||||
if [ "$WARM_PING" = "1" ] && [ -n "$a" ]; then
|
||||
if ping -n -q -c1 -W "$PING_TIMEOUT" "$a" >/dev/null 2>&1; then
|
||||
echo "ok $host $a"
|
||||
else
|
||||
echo "noping $host $a"
|
||||
fi
|
||||
else
|
||||
echo "ok $host ${a:-$aaaa}"
|
||||
fi
|
||||
}
|
||||
export -f warm_one
|
||||
export RESOLVER RESOLVER_PORT DIG_TIMEOUT DIG_TRIES WARM_PING PING_TIMEOUT
|
||||
|
||||
RESULTS="$(printf '%s\n' "${HOSTS[@]}" \
|
||||
| xargs -r -P "$PARALLEL" -I{} bash -c 'warm_one "$@"' _ {})"
|
||||
|
||||
OK=$(grep -c '^ok ' <<<"$RESULTS" || true)
|
||||
NOPING=$(grep -c '^noping ' <<<"$RESULTS" || true)
|
||||
DNSFAIL=$(grep -c '^dnsfail ' <<<"$RESULTS" || true)
|
||||
|
||||
AFTER_V4=$(set_count bbrkn_v4)
|
||||
AFTER_V6=$(set_count bbrkn_v6)
|
||||
log "DONE ok=$OK noping=$NOPING dnsfail=$DNSFAIL v4=${BEFORE_V4}->${AFTER_V4} v6=${BEFORE_V6}->${AFTER_V6}"
|
||||
if [ "$DNSFAIL" -gt 0 ]; then
|
||||
log "dnsfail: $(grep '^dnsfail ' <<<"$RESULTS" | cut -d' ' -f2 | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
echo "✔ Прогрето доменов: $OK, не резолвится: $DNSFAIL; bbrkn_v4 ${BEFORE_V4}->${AFTER_V4}, bbrkn_v6 ${BEFORE_V6}->${AFTER_V6}"
|
||||
|
||||
# Ротация лога
|
||||
if [ -f "$LOG" ] && [ "$(stat -c%s "$LOG")" -gt "$LOG_MAX_BYTES" ]; then
|
||||
mv -f "$LOG" "${LOG}.1"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue