Compare commits

...
Sign in to create a new pull request.

13 commits

Author SHA1 Message Date
ae8295546b Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 1h7m54s
2026-08-28 20:59:23 +03:00
19fbb5c1a2 ADD Google Video domains
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 18m57s
2026-08-17 07:39:55 +03:00
47f91b5191 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 1h0m51s
2026-08-16 20:49:35 +03:00
5643ba5615 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 46m30s
2026-08-08 15:24:08 +03:00
0ac0aa0cc4 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 17m57s
2026-08-08 12:24:13 +03:00
660a31c35e Add warm-nftset.sh — periodic nftset warmer
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>
2026-08-05 20:58:33 +03:00
38d5bdf0ce Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 23m52s
2026-08-01 22:31:10 +03:00
c9c1e8b1ee Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 16m58s
2026-08-01 18:57:11 +03:00
0865df3de3 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 43m32s
2026-08-01 14:20:27 +03:00
20c484dc59 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 45m10s
2026-07-31 16:54:09 +03:00
a3e755c0a4 Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 1h37m47s
2026-07-28 00:50:44 +03:00
9acd54ff2a Update domains.txt
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 1h37m50s
2026-07-25 12:54:15 +03:00
b4df267892 Merge pull request 'Migrate bbrkn from legacy ipset to nftables sets' (#12) from migrate-ipset-to-nftset into main
All checks were successful
Deploy DNS Configuration / deploy (push) Successful in 1h33m43s
Reviewed-on: #12
2026-07-24 23:58:41 +03:00
4 changed files with 282 additions and 3 deletions

View file

@ -18,7 +18,7 @@ CACHE_TTL_DAYS ?= 15
export NFTSET_CONF RESOLVE_CONF CHROME_SERVER DOMAINS_FILE DNS_SERVER NFTSET_TARGET_DIR CACHE_DIR CACHE_TTL_DAYS
# --- Основные цели ---
.PHONY: all clean cache-clean check generate deploy
.PHONY: all clean cache-clean check generate deploy warm
all: generate deploy
@echo "✅ Конфиги успешно сгенерированы и задеплоены"
@ -55,3 +55,10 @@ deploy:
@echo "🚀 Деплой конфигов в систему"
@chmod +x scripts/deploy-to-gateway.sh
@./scripts/deploy-to-gateway.sh
# Прогрев nft-сетов. Запускать на шлюзе — читает задеплоенный конфиг и
# ходит в host-инстанс dnsmasq на 127.0.0.1:5350.
warm:
@echo "🔥 Прогрев nft-сетов"
@chmod +x scripts/warm-nftset.sh
@./scripts/warm-nftset.sh

View file

@ -82,7 +82,8 @@ bbrkn/
├── Makefile # управление сборкой и деплоем
├── scripts/
│ ├── generate-configs.sh # генерация dnsmasq-конфигов
│ └── deploy-to-gateway.sh # деплой в Pi-hole + rollback
│ ├── deploy-to-gateway.sh # деплой в Pi-hole + rollback
│ └── warm-nftset.sh # прогрев nft-сетов по cron (на шлюзе)
└── .forgejo/workflows/deploy.yaml # CI/CD на self-hosted runner
```
@ -97,6 +98,7 @@ make deploy # только задеплоить в Pi-hole
make clean # удалить сгенерированные конфиги из /tmp
make cache-clean # очистить кэш API-ответов (.cache/api/)
make check # проверить синтаксис domains.txt
make warm # прогреть nft-сеты (запускать на шлюзе)
```
---
@ -173,6 +175,54 @@ CACHE_TTL_DAYS=0 make generate
---
## Прогрев nft-сетов (`warm-nftset.sh`)
Элементы сетов `bbrkn_v4`/`bbrkn_v6` живут по `timeout` (сейчас `1d`) и обновляются **только** когда запрос доходит до host-инстанса `:5350` — именно там применяются директивы `nftset=`. Если домен долго никто не открывал (или ответ отдал кэш pihole FTL), элемент истекает, и трафик к этому домену уходит мимо туннеля напрямую в WAN.
`scripts/warm-nftset.sh` закрывает эту дыру: заново резолвит **все** домены из задеплоенного `90-nftset.conf` (база + связанные субдомены) **напрямую** через `dig @127.0.0.1 -p 5350`, минуя кэш FTL, — каждый запрос гарантированно доходит до тегирующего инстанса.
> **Запускать только на шлюзе.** Инстанс `:5350` слушает `listen-address=127.0.0.1` и с других хостов LAN недоступен. Список доменов берётся из задеплоенного конфига, так что скрипт всегда работает с актуальным после последнего деплоя списком.
```bash
# разово, вручную
make warm
./scripts/warm-nftset.sh
# ежечасно по cron (пользователь на шлюзе, sudo без пароля для nft)
crontab -e
17 * * * * /path/to/bbrkn/scripts/warm-nftset.sh >/dev/null 2>&1
```
### Переменные окружения
| Переменная | По умолчанию | Описание |
|---|---|---|
| `NFTSET_CONF` | `/etc/dnsmasq.d/90-nftset.conf` | Задеплоенный конфиг — источник списка доменов |
| `RESOLVER` / `RESOLVER_PORT` | `127.0.0.1` / `5350` | Host-инстанс dnsmasq, применяющий `nftset=` |
| `PARALLEL` | `8` | Одновременных запросов к `:5350` |
| `DIG_TIMEOUT` / `DIG_TRIES` | `3` / `2` | Таймаут и число попыток на домен |
| `WARM_PING` | `0` | `1` — дополнительно пинговать первый A-адрес (только для отладки) |
| `STATE_DIR` | `~/.local/state/bbrkn` | Лог `warm.log` и lock-файл |
| `LOG_MAX_BYTES` | `4194304` | Порог ротации лога в `warm.log.1` |
Параллельные запуски блокируются через `flock` — медленный прогон не наложится на следующий по cron.
### Лог
```
2026-08-05T20:46:57+03:00 START domains=1059 conf=/etc/dnsmasq.d/90-nftset.conf v4=526 v6=305
2026-08-05T20:48:44+03:00 DONE ok=1036 noping=0 dnsfail=23 v4=526->2203 v6=305->1440
2026-08-05T20:48:44+03:00 dnsfail: azureedge.net cdn-telegram.org kinozal.tv ...
```
`v4=X->Y` — число элементов в сете до и после прогона (считается через `sudo -n nft list set`; без прав sudo вместо числа будет `?`, на сам прогрев это не влияет). Элементов больше, чем доменов — у CDN на один домен приходится несколько адресов. Домены в `dnsfail` не резолвятся вообще (мёртвые записи, NXDOMAIN) — кандидаты на удаление из `domains.txt`.
> `WARM_PING=1` на шлюзе почти всегда даёт `noping`: пакеты, рождённые на самом шлюзе, идут через `OUTPUT` и не проходят цепочку `prerouting_mangle`, то есть уходят в WAN без метки. Это диагностический режим, не показатель проблемы.
Полный прогон 1059 доменов при `PARALLEL=8` занимает ~1 мин 45 с.
---
## Запуск вручную
```bash

View file

@ -193,4 +193,98 @@ netlify.com
netlifydns.com
netlifyusercontent.com
www.ubuntu-touch.io
ubports.com
ubports.com
4pda.to
www.patreon.com
registry-1.docker.io
docker.io
downloads.claude.ai
claude.com
30bf3790.obrut.show
amedia.online
anime1.best
animego.me
animevost.org
api.anilibria.app
api.anilibria.tv
api.apbugall.org
api.bhcesh.me
api.cdnlibs.org
api.filmix.tv
api.g1.plvideo.ru
api.lumex.space
api.luxembd.ws
api.moonanime.art
api.rgshows.me
api.rstprgapipt.com
api.srvkp.com
api.vokino.org
apivb.com
base.ashdi.vip
cdnmovies-stream.online
chaturbate.com
coldcdn.xyz
ee.bongacams.com
embed.su
eneyida.tv
filmixapp.cyou
getstv.com
go.zet-flix.online
hdrezka.me
iframe.video
in.tizam.info
iptv.online
kinobase.org
kinogo.luxury
kinogo.media
kinoukr.com
kinovibe.vip
kodikapi.com
m.hqporner.com
megaoblako.com
moviesapi.club
myfanserial.net
plapi.cdnvideohub.com
player.autoembed.cc
player.smashystream.com
player.videasy.net
portal.lumex.host
redheadsound.studio
rt.pornhub.com
rt.pornhubpremium.com
ru.spankbang.com
ru.xhamster.com
rus.runetki5.com
vibix.org
videoseed.tv
vidfast.pro
vidlink.pro
vidora.su
vidsrc.cc
www.ebalovo.pro
www.eporner.com
www.porntrex.com
www.xnxx.com
www.xvideos.com
www.xvideos.red
gemini.google.com
nakarte.me
a-api.anthropic.com
youtube.com
youtu.be
youtube-nocookie.com
ytimg.com
ggpht.com
googlevideo.com
gvt1.com
gvt2.com
gvt3.com
googleusercontent.com
gstatic.com
jnn-pa.googleapis.com
play.googleapis.com
youtubei.googleapis.com
googleapis.com
api.bambulab.com
bambulab.com
e.bambulab.com

128
scripts/warm-nftset.sh Executable file
View 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