Migrate bbrkn from legacy ipset to nftables sets
The gateway (Archie) routes bbrkn domains via nft sets bbrkn_v4/bbrkn_v6 (inet filter), not the legacy ipset. bbrkn was still emitting dead `ipset=/domain/bbrkn` directives (no such ipset exists) plus a 92-resolve pointing at 8.8.8.8, both overridden by hand-maintained files on the host. This makes bbrkn the generator of record for the real scheme. generate-configs.sh: - emit `nftset=/domain/$NFTSET_SPEC` (default 4#inet#filter#bbrkn_v4, 6#inet#filter#bbrkn_v6) into 90-nftset.conf instead of ipset= into 91-ipset-bbrkn.conf - DNS_SERVER default 8.8.8.8 -> 127.0.0.1#5350 (host dnsmasq pihole delegates bbrkn domains to for VPN resolution + nftset capture) deploy-to-gateway.sh: two targets, two instances - 90-nftset.conf -> host /etc/dnsmasq.d (:5350), 92-resolve -> pihole - full restart of both (dnsmasq SIGHUP does NOT re-read nftset=/server=) - flush nft sets bbrkn_v4/bbrkn_v6 instead of `ipset flush bbrkn` - add end-to-end nftset-capture health check via :5350 - rollback restores both files and restarts both instances Makefile/workflow: rename IPSET_CONF->NFTSET_CONF, add NFTSET_TARGET_DIR and HOST_DNSMASQ_SVC, DNS_SERVER=127.0.0.1#5350 (escaped `\#` in Make, quoted in YAML), note runner is ephemeral (cold gekata crawl). Docs: README + new CLAUDE.md describe the two-dnsmasq / nft-set model; exit-node DPI failover (10.77.1.2/10.77.2.2) documented as external (wg-ha.service), not owned by bbrkn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ef30817cf8
commit
d81a4ab3b2
6 changed files with 271 additions and 111 deletions
89
README.md
89
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# bbrkn — DNS Bypass Config Generator
|
||||
|
||||
Автоматизирует генерацию конфигураций **Pi-hole / dnsmasq** для маршрутизации трафика заблокированных доменов через VPN-туннель (WireGuard).
|
||||
Автоматизирует генерацию конфигураций **Pi-hole / dnsmasq** для маршрутизации трафика заблокированных доменов через VPN-туннель (WireGuard) с помощью **nftables-сетов**.
|
||||
|
||||
## Как это работает
|
||||
|
||||
|
|
@ -8,25 +8,29 @@
|
|||
flowchart TD
|
||||
A[domains.txt] --> B[generate-configs.sh]
|
||||
B -->|кэш .cache/api| B
|
||||
B -->|MISS / EXPIRED| C[Chromium headless API]
|
||||
B -->|MISS / EXPIRED| C[gekata: Chromium headless API]
|
||||
C --> B
|
||||
B --> D[91-ipset-bbrkn.conf]
|
||||
B --> D[90-nftset.conf]
|
||||
B --> E[92-resolve-bbrkn.conf]
|
||||
D --> F[deploy-to-gateway.sh]
|
||||
E --> F
|
||||
F -->|backup + copy| G[Pi-hole dnsmasq.d/]
|
||||
F -->|docker restart| H[Pi-hole container]
|
||||
F -->|DNS health check| H
|
||||
H -->|rollback если упал| G
|
||||
F -->|backup + copy + systemctl restart| G[host dnsmasq :5350<br/>/etc/dnsmasq.d/]
|
||||
F -->|backup + copy + docker restart| H[Pi-hole :53]
|
||||
F -->|health check + nftset check| H
|
||||
F -.->|rollback если упал| G
|
||||
|
||||
subgraph "Трафик клиента"
|
||||
K[Клиент] -->|DNS запрос| H
|
||||
H -->|домен в ipset → mark| I[iptables]
|
||||
I --> J[WireGuard VPN]
|
||||
H -->|"server= → 127.0.0.1#5350"| G
|
||||
G -->|"резолв через VPN + nftset="| N["nft set bbrkn_v4 / bbrkn_v6"]
|
||||
N -->|ip daddr @bbrkn → mark| I[nft mangle]
|
||||
I -->|policy routing| J[WireGuard exit<br/>10.77.1.2 / 10.77.2.2]
|
||||
J --> L[Интернет]
|
||||
end
|
||||
```
|
||||
|
||||
> **Два инстанса dnsmasq.** Pi-hole (:53) отдаёт клиентов и делегирует bbrkn-домены host-инстансу на :5350 (`92-resolve`). Инстанс :5350 резолвит их через VPN и по директиве `nftset=` (`90-nftset.conf`) кладёт полученные IP в nft-сеты `bbrkn_v4`/`bbrkn_v6`. nft-mangle маркирует пакеты к этим IP, policy-routing уводит их в WireGuard. **Выбор из двух exit-нод (DPI-обход РКН) и failover — вне bbrkn**, ими управляет `wg-ha.service` на шлюзе.
|
||||
|
||||
---
|
||||
|
||||
## Классификация доменов
|
||||
|
|
@ -46,15 +50,26 @@ flowchart TD
|
|||
|
||||
Домены группируются по базовому домену с комментарием:
|
||||
|
||||
`90-nftset.conf` (наполняет nft-сеты):
|
||||
|
||||
```
|
||||
# example.com — 4 subdomains
|
||||
ipset=/example.com/bbrkn
|
||||
ipset=/static.example.com/bbrkn
|
||||
ipset=/api.example.com/bbrkn
|
||||
nftset=/example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6
|
||||
nftset=/static.example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6
|
||||
nftset=/api.example.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6
|
||||
...
|
||||
|
||||
# some-service.com — 0 subdomains
|
||||
ipset=/some-service.com/bbrkn
|
||||
nftset=/some-service.com/4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6
|
||||
```
|
||||
|
||||
`92-resolve-bbrkn.conf` (делегирует те же домены host-инстансу :5350):
|
||||
|
||||
```
|
||||
# example.com — 4 subdomains
|
||||
server=/example.com/127.0.0.1#5350
|
||||
server=/static.example.com/127.0.0.1#5350
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -93,10 +108,11 @@ make check # проверить синтаксис domains.txt
|
|||
| Переменная | По умолчанию | Описание |
|
||||
|---|---|---|
|
||||
| `DOMAINS_FILE` | `domains.txt` | Входной файл со списком доменов |
|
||||
| `IPSET_CONF` | `/tmp/91-ipset-bbrkn.conf` | Выходной файл правил ipset |
|
||||
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Выходной файл правил server= |
|
||||
| `CHROME_SERVER` | `http://127.0.0.1:3000` | Адрес Chromium headless API |
|
||||
| `DNS_SERVER` | `8.8.8.8` | DNS-сервер для резолвинга через VPN |
|
||||
| `NFTSET_CONF` | `/tmp/90-nftset.conf` | Выходной файл директив `nftset=` |
|
||||
| `NFTSET_SPEC` | `4#inet#filter#bbrkn_v4,6#inet#filter#bbrkn_v6` | Цель nftset: `<4\|6>#family#table#set` |
|
||||
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Выходной файл директив `server=` |
|
||||
| `CHROME_SERVER` | `http://127.0.0.1:3000` | Адрес gekata (Chromium headless API) |
|
||||
| `DNS_SERVER` | `127.0.0.1#5350` | Резолвер, которому pihole делегирует домены (host dnsmasq :5350) |
|
||||
| `IGNORE_PARTS` | _(пусто)_ | Подстроки через пробел — домены с совпадением игнорируются |
|
||||
| `CACHE_DIR` | `.cache/api` | Директория кэша API-ответов |
|
||||
| `CACHE_TTL_DAYS` | `15` | Время жизни кэша в днях; `0` — отключить кэш |
|
||||
|
|
@ -108,12 +124,17 @@ make check # проверить синтаксис domains.txt
|
|||
|
||||
| Переменная | По умолчанию | Описание |
|
||||
|---|---|---|
|
||||
| `TARGET_DIR` | `/opt/appdata/pihole/etc/dnsmasq.d` | Директория dnsmasq в Pi-hole |
|
||||
| `IPSET_CONF` | `/tmp/91-ipset-bbrkn.conf` | Сгенерированный файл ipset |
|
||||
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Сгенерированный файл server= |
|
||||
| `NFTSET_CONF` | `/tmp/90-nftset.conf` | Сгенерированный файл `nftset=` |
|
||||
| `NFTSET_TARGET_DIR` | `/etc/dnsmasq.d` | Директория host-инстанса dnsmasq (:5350) |
|
||||
| `RESOLVE_CONF` | `/tmp/92-resolve-bbrkn.conf` | Сгенерированный файл `server=` |
|
||||
| `TARGET_DIR` | `/opt/appdata/pihole/etc/dnsmasq.d` | Директория dnsmasq в Pi-hole (для `92-resolve`) |
|
||||
| `DOCKER_CONTAINER` | `pihole` | Имя Docker-контейнера Pi-hole |
|
||||
| `DNS_LISTEN_ADDR` | `127.0.0.1` | Адрес, на котором Pi-hole слушает DNS |
|
||||
| `DNS_CHECK_DOMAIN` | `google.com` | Домен для DNS health check после рестарта |
|
||||
| `HOST_DNSMASQ_SVC` | `dnsmasq` | systemd-сервис host-инстанса dnsmasq |
|
||||
| `NFT_SETS` | `inet filter bbrkn_v4;inet filter bbrkn_v6` | Сеты для flush после деплоя (через `;`) |
|
||||
| `DNS_LISTEN_ADDR` | `127.0.0.1` | Адрес, на котором Pi-hole слушает DNS (:53) |
|
||||
| `DNS_CHECK_DOMAIN` | `google.com` | Домен базового DNS health check |
|
||||
| `NFTSET_CHECK_DOMAIN` | `chatgpt.com` | bbrkn-домен для end-to-end проверки nftset-захвата |
|
||||
| `RESOLVER_ADDR` / `RESOLVER_PORT` | `127.0.0.1` / `5350` | Адрес host-инстанса для проверки захвата |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -125,7 +146,7 @@ make check # проверить синтаксис domains.txt
|
|||
- Если файл **старше** TTL → перезапрашивается и кэш обновляется.
|
||||
- Если кэш **повреждён** (невалидный JSON) → перезапрашивается автоматически.
|
||||
|
||||
На self-hosted runner workspace сохраняется между запусками, поэтому повторные CI-запуски значительно быстрее.
|
||||
> **Внимание:** forgejo-runner на шлюзе **эфемерный** — workspace (и `.cache/api`) между запусками не сохраняется. Поэтому первый CI-прогон после сброса кэша выполняет **холодный обход всех доменов через gekata** (~30-70 с на домен, последовательно) и работает долго. Для быстрых повторных прогонов держите тёплый `.cache/api` в постоянной директории (`CACHE_DIR`).
|
||||
|
||||
```bash
|
||||
# Сбросить кэш и запросить всё заново
|
||||
|
|
@ -139,15 +160,16 @@ CACHE_TTL_DAYS=0 make generate
|
|||
|
||||
## Деплой и автооткат
|
||||
|
||||
`deploy-to-gateway.sh` выполняет следующие шаги:
|
||||
`deploy-to-gateway.sh` деплоит в **два инстанса dnsmasq** и выполняет шаги:
|
||||
|
||||
1. Проверяет наличие сгенерированных конфигов.
|
||||
2. Создаёт timestamped-бэкапы текущих конфигов в `TARGET_DIR`.
|
||||
3. Копирует новые конфиги в `TARGET_DIR`.
|
||||
4. Перезапускает контейнер Pi-hole.
|
||||
5. Ждёт запуска (до 30 секунд, polling через `docker inspect`).
|
||||
6. Выполняет DNS health check через `dig` / `nslookup`.
|
||||
7. При ошибке на шагах 5 или 6 — **автоматически откатывается**: восстанавливает бэкапы и перезапускает контейнер со старой конфигурацией.
|
||||
1. Проверяет наличие обоих сгенерированных конфигов.
|
||||
2. Создаёт timestamped-бэкапы `90-nftset.conf` (host) и `92-resolve-bbrkn.conf` (pihole).
|
||||
3. Копирует `90-nftset.conf` → `NFTSET_TARGET_DIR`, `92-resolve-bbrkn.conf` → `TARGET_DIR`.
|
||||
4. Перезапускает **оба** инстанса: `systemctl restart $HOST_DNSMASQ_SVC` + `docker restart $DOCKER_CONTAINER`. Полный рестарт обязателен — dnsmasq по **SIGHUP не перечитывает** директивы `nftset=`/`server=`.
|
||||
5. Ждёт: host-инстанс слушает `:5350` (до 15 с) и pihole в состоянии `running` (до 30 с).
|
||||
6. Сбрасывает nft-сеты `bbrkn_v4`/`bbrkn_v6` (устаревшие IP иначе висят до timeout 24 ч).
|
||||
7. Health check: базовый DNS через pihole `:53` + end-to-end проверка nftset-захвата (резолв bbrkn-домена через `:5350` → сет наполнился).
|
||||
8. При ошибке на шагах 5–7 — **автоматически откатывается**: восстанавливает оба бэкапа и перезапускает оба инстанса со старой конфигурацией.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -226,10 +248,11 @@ some-domain.com → api_failed_added_without_subdomains
|
|||
| Инструмент | Где используется |
|
||||
|---|---|
|
||||
| `bash` 4.2+ | оба скрипта |
|
||||
| `curl` | запросы к Chromium API |
|
||||
| `curl` | запросы к gekata API |
|
||||
| `jq` | парсинг JSON-ответов |
|
||||
| `docker` | перезапуск Pi-hole |
|
||||
| `ipset` | сброс маршрутов после деплоя |
|
||||
| `nft` | flush nft-сетов + проверка захвата после деплоя |
|
||||
| `systemctl` | рестарт host-инстанса dnsmasq |
|
||||
| `dig` или `nslookup` | DNS health check (опционально) |
|
||||
|
||||
---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue