release: prepare 0.2.0 for sing-box 1.14

This commit is contained in:
2026-09-08 19:26:00 +05:00
parent 81ea89f7fa
commit 76dce5b47a
42 changed files with 1634 additions and 549 deletions
+51 -70
View File
@@ -2,97 +2,78 @@
## Границы ответственности
Hysteria2 URI содержит только переносимые реквизиты VPN:
Hysteria2 URI содержит переносимые реквизиты: auth, hostname/ports, SNI,
`insecure`, тип obfs и obfs password. Production HY2XS выдаёт Gecko URI.
- authentication;
- hostname и port/port ranges;
- SNI и `insecure`;
- `salamander` и obfs password.
`policy.json` содержит локальную инфраструктурную политику:
- `eth0` — upstream;
- `eth1` — VPN LAN;
- `tun-sb0`, `172.19.0.1/30`, MTU 1400;
- TUN routes, nftables marks и rule/table indexes;
- bootstrap DNS и DoH через `hy2-out`;
- bandwidth hints и healthcheck.
Renderer не патчит существующий JSON. Полная конфигурация каждый раз строится
из typed model. Это устраняет config drift и исторические endpoint `/32`.
`policy.json` хранит локальную инфраструктуру: `eth0`, `eth1`, TUN CIDR и MTU,
route exclusions, таблицу 2022, marks, NFQUEUE, DNS, bandwidth и healthcheck.
Renderer никогда не патчит старый JSON, а строит полный deterministic config из
typed model.
## Поток применения
```text
URI + policy
├─ strict parsing / schema validation
├─ exact sing-box version and build-tag gate
├─ deterministic render
─ secure candidate in /etc/sing-box
└─ sing-box check -c candidate
├─ strict validation
├─ exact 1.14.0 + build-tag gate
├─ renderer_1_14_0
─ sing-box check на private candidate
redacted comparison
unchanged ─────── changed
│ │
▼ ├─ last-good backup
no restart ├─ atomic replace
unchanged ───── changed
│ │
▼ ├─ проверка provenance текущего config
state.json ├─ versioned last-good backup
├─ atomic replace
├─ systemctl restart
└─ bounded local connectivity healthcheck
fail ┴ success
│ │
▼ ▼
rollback state.json
└─ bounded healthcheck
```
Все операции изменения сериализованы `flock`-совместимой блокировкой. Поэтому
ручной `import` и запоздалый event от systemd.path не могут применять два
candidate одновременно.
Первое применение после чистой установки не создаёт `last-good`: предыдущего
управляемого конфига нет. При следующих изменениях backup разрешён только если
SHA установленного config совпадает со state этого же релиза и sing-box 1.14.0.
## Почему endpoint IP не нужен
## Gecko и DNS
Outbound сохраняет DNS hostname и содержит `bind_interface=eth0`. В 1.13.19
`route.auto_detect_interface` не применяется к outbound с явным
`bind_interface`. Bootstrap DNS также привязан к `eth0`. Endpoint IP поэтому не
является частью TUN policy.
Renderer добавляет для Gecko `min_packet_size=512` и
`max_packet_size=1200`. Эти параметры являются частью локального HY2XS
compatibility profile и не читаются из URI.
После смены A-записи следующая Hysteria2-сессия разрешает имя заново через
`bootstrap-dns`. `doctor` дополнительно проверяет, что текущие A-записи не
попали в exclusions.
В sing-box 1.14.0 TUN по умолчанию использует `dns_mode=hijack`, что меняет
настройки интерфейса и platform-level interception. Проект явно задаёт
`dns_mode=disabled` и сохраняет собственное route action `port 53 ->
hijack-dns`. Bootstrap UDP DNS привязан к `eth0`; remote DoH использует detour
`hy2-out`.
## Endpoint routing
Hysteria outbound хранит hostname и `bind_interface=eth0`, а его
`domain_resolver` указывает на bootstrap DNS через `eth0`. Публичный IP сервера
не добавляется в `route_exclude_address`; смена A-записи не требует изменения
policy.
## Файловая модель
```text
/etc/vpn-egress/ root:root 0700
├── policy.json root:root 0600
└── hysteria2.uri root:root 0600
/etc/vpn-egress/ root:root 0700
├── policy.json root:root 0600
└── hysteria2.uri root:root 0600
/var/lib/vpn-egress/ root:root 0700
├── state.json без секретов, 0600
├── last-good.json содержит secrets, 0600
── backups/ ограниченная история, 0700/0600
/var/lib/vpn-egress/ root:root 0700
├── state.json без секретов, 0600
├── last-good.json содержит secrets, 0600
── last-good.meta.json версия и SHA-256, 0600
└── backups/ config + соседний .meta, 0700/0600
/etc/sing-box/
└── config.json root:root 0600
/etc/sing-box/config.json root:root 0600
```
Service sing-box уже имеет `CAP_DAC_READ_SEARCH`, поэтому сохраняется текущая
рабочая модель `root:root 0600`.
Metadata не обеспечивает доверие от root-компрометации. Её задача — исключить
случайное смешивание релизов и повреждённых backup-файлов.
Guard читает имена интерфейсов из того же policy и устанавливает всю nftables
таблицу одной batch-транзакцией. Если новый ruleset некорректен, nft не оставляет
систему с частично заменённой таблицей.
## Граница установки
Package-managed drop-in добавляет для `sing-box.service` зависимости
`Requires=` и `After=` от guard. Поэтому при совместном запуске ошибка guard
блокирует старт sing-box. Уже активный oneshot не является watchdog: ручное
удаление nftables-таблицы обнаруживает `doctor`, но не systemd dependency.
## Версионная граница
В коде существует только `renderer_1_13_19.py`. Renderer для 1.14 не является
пустой заготовкой: он появится только в отдельной миграции после аудита схемы,
маршрутизации и полных интеграционных тестов.
0.2.0 не содержит кода миграции 0.1.0. `debian/preinst` отклоняет in-place
upgrade, а `postinst` отклоняет legacy policy/state или unmanaged
`/etc/sing-box/config.json`. Same-release reinstall допускается, если state и
config согласованы.
+47 -44
View File
@@ -2,73 +2,76 @@
## Policy schema version 1
Production-образец находится в `config/policy.json`. Неизвестные и отсутствующие
ключи являются ошибкой.
Production-образец находится в `config/policy.json`. Все секции имеют строгий
набор ключей; неизвестные и отсутствующие значения являются ошибкой.
### `sing_box`
- `binary`: абсолютный путь к бинарнику;
- `config_path`: production JSON;
- `service`: systemd service;
- `required_version`: допускается только `1.13.19`.
- `config_path`: генерируемый production JSON;
- `service`: имя systemd service;
- `required_version`: только `1.14.0`.
### `runtime`
- `uri_path`: desired-state secret;
- `state_dir`: state и backups;
- `uri_path`: защищённый desired-state URI;
- `state_dir`: state, last-good и backups;
- `lock_path`: межпроцессная блокировка;
- `backup_keep`: число timestamped backups, от 1 до 100.
- `backup_keep`: число same-release backups, от 1 до 100.
### `network`
Здесь явно фиксируются интерфейсы, TUN CIDR, MTU, exclusions, таблица 2022,
начальный rule index 9000, marks `0x2023`/`0x2024`/`0x2025`, NFQUEUE 100 и
fallback rule 32768.
Публичные адреса не должны добавляться в `route_exclude_address`. Локальные
подсети `10.20.0.0/24`, `10.30.0.0/24` и loopback сохраняются.
Policy фиксирует `eth0`, `eth1`, `tun-sb0`, `172.19.0.1/30`, MTU 1400,
таблицу 2022, rule index 9000, marks `0x2023/24/25`, NFQUEUE 100 и fallback
rule 32768. Публичные endpoint IP не добавляются в exclusions.
### `dns`
Текущий контракт:
- UDP bootstrap `1.1.1.1:53`, bind `eth0`;
- bootstrap `1.1.1.1:53`, bind `eth0`;
- DoH `1.1.1.1:443/dns-query`, SNI `cloudflare-dns.com`;
- DoH detour `hy2-out`;
- только `ipv4_only`.
- стратегия только `ipv4_only`.
### `healthcheck`
### `bandwidth`
По умолчанию выполняется HTTPS-запрос к Cloudflare trace и ожидается HTTP 200 с
маркером `ip=`. Этот локальный post-activation connectivity healthcheck идёт
после запуска sing-box и проверяет состояние systemd и исходящую связность
самого шлюза. Он не заменяет acceptance-тест forwarding с workload за `eth1`.
`up_mbps=50` и `down_mbps=200` выбирают Hysteria congestion control. Поэтому
renderer не задаёт `bbr_profile`: BBR profile применяется только когда bandwidth
values не заданы.
`url: null` оставляет только проверку `systemctl is-active`. Это допустимо для
изолированного стенда, но слабее production-проверки.
## Hysteria2 URI
## Поддерживаемая часть Hysteria2 URI
Production-форма HY2XS:
Поддерживаются:
```text
hysteria2://<peer-secret>@<host>:<port>/?insecure=0&obfs=gecko&obfs-password=<secret>&sni=<domain>
```
- `hysteria2://` и `hy2://`;
- percent-encoded auth, включая `username:password`;
- DNS, IPv4, bracketed IPv6 и IDNA;
- port 443 по умолчанию;
- одиночный port и официальный multi-port/ranges;
- `sni`, `insecure=0|1`;
- `obfs=salamander&obfs-password=...`;
- fragment как необязательное display name.
Поддерживаются `hysteria2://`/`hy2://`, auth/userpass, DNS/IPv4/bracketed IPv6,
IDNA, port hopping, `sni`, `insecure=0|1`, fragment, Gecko и Salamander.
Отклоняются `gecko`, `pinSHA256`, `ech`, client modes, неизвестные и
повторяющиеся параметры. Причина для `pinSHA256`: Hysteria URI и sing-box
1.13.19 используют разные виды certificate hash. `ech` будет добавлен только
после доказанного преобразования формата config list.
Правила obfs:
Специальные символы в auth должны быть percent-encoded: сырой `@` отклоняется,
а `%40` декодируется в `@`.
```text
нет obfs -> допустимо для внешнего generic endpoint
obfs=gecko -> требуется obfs-password; профиль всегда 512/1200
obfs=salamander -> требуется obfs-password; compatibility fallback
другой obfs -> ошибка
password без obfs -> ошибка
```
## Bandwidth
Для HY2XS допустимым production-режимом считается только Gecko. Salamander не
включается на сервере и не выбирается автоматически.
`up_mbps=50` и `down_mbps=200` являются локальной политикой и намеренно не
принимаются из URI.
Query parser применяет RFC percent-decoding, но не form decoding: сырой `+`
сохраняется как `+`, `%2B` также превращается в `+`, а `%26`/`%3D` безопасно
остаются частью значения после разбора разделителей.
`pinSHA256`, `ech`, Realm URI, client modes, неизвестные и повторяющиеся
параметры отклоняются. Custom Gecko packet sizes через URI не принимаются:
официальная Hysteria2 URI schema их не переносит.
## Healthcheck
По умолчанию проверяется Cloudflare trace: HTTP 200 и маркер `ip=`. Это
проверяет локальную post-activation связность шлюза, но не заменяет E2E с
workload за `eth1`. `url: null` допустим только для изолированного стенда.
+79 -116
View File
@@ -1,71 +1,72 @@
# Установка и миграция
# Чистая установка версии 0.2.0
Инструкция рассчитана на Debian 13 внутри `vpn-egress-gw`.
## Поддерживаемая модель
## 1. Резервная копия
In-place upgrade 0.1.0 → 0.2.0, автоматическая миграция policy/state и запуск
старого config под новым ядром не поддерживаются. Новый package `preinst`
отклоняет upgrade. Установка выполняется через удаление старого пакета,
архивирование его данных и настройку 0.2.0 с нуля.
До установки сохранить:
Описанные действия выполнять из console/maintenance-доступа. До успешного E2E
anti-leak guard должен оставаться в nftables.
## 1. Остановить автоматическое применение
```bash
install -d -m 0700 /root/vpn-egress-migration
cp -a /etc/sing-box/config.json /root/vpn-egress-migration/config.json.before
cp -a /root/render-singbox-hy2.sh /root/vpn-egress-migration/
cp -a /usr/local/sbin/vpn-egress-guard.sh /root/vpn-egress-migration/
nft list ruleset > /root/vpn-egress-migration/nft.before.rules
ip -4 rule show > /root/vpn-egress-migration/ip-rule.before.txt
ip -4 route show table all > /root/vpn-egress-migration/ip-route.before.txt
```
## 2. Обновление sing-box
Пакет имеет строгую зависимость `sing-box (= 1.13.19)`.
```bash
apt-get update
apt-get install sing-box=1.13.19
sing-box version
```
Если репозиторий SagerNet ещё не публикует 1.13.19, миграцию не продолжать и не
обходить dependency/version gate.
## 3. Установка пакета
```bash
dpkg -i vpn-egressctl_0.1.0_all.deb
install -m 0600 -o root -g root \
/usr/share/vpn-egressctl/policy.json \
/etc/vpn-egress/policy.json
systemctl daemon-reload
```
Проверить локальные значения policy до первого применения.
## 4. Guard до VPN
Старый unit находится в `/etc/systemd/system` и перекрывает package unit из
`/usr/lib`. Сначала обратимо убрать старое определение:
```bash
systemctl stop vpn-egress-guard.service
mv /etc/systemd/system/vpn-egress-guard.service \
/root/vpn-egress-migration/vpn-egress-guard.service.disabled
systemctl daemon-reload
systemctl enable --now vpn-egress-guard.service
systemctl status vpn-egress-guard.service --no-pager
systemctl disable --now vpn-egress-sync.path || true
systemctl stop sing-box.service
systemctl mask --runtime sing-box.service
nft list table inet vpn_egress_guard
```
Новый unit имеет `Before=sing-box.service`, а package-managed drop-in для
`sing-box.service` добавляет `Requires=` и `After=` от guard. Это закрывает boot
window и блокирует запуск sing-box, если совместно запущенный guard завершился с
ошибкой. Зависимость не является watchdog для ручного удаления nftables-таблицы;
текущее runtime-состояние проверяет `vpn-egressctl doctor`.
Если guard отсутствует, сначала восстановить защиту. Не продолжать переход с
работающим прямым forwarding `eth1 -> eth0`.
## 5. Dry run и первый import
## 2. Удалить старый package и архивировать данные
Для первого `check` URI ещё должен существовать. Создать файл без попадания
secret в argv:
```bash
apt-get remove vpn-egressctl
install -d -m 0700 /root/vpn-egress-0.1-archive
test ! -e /etc/vpn-egress || \
mv /etc/vpn-egress /root/vpn-egress-0.1-archive/etc-vpn-egress
test ! -e /var/lib/vpn-egress || \
mv /var/lib/vpn-egress /root/vpn-egress-0.1-archive/var-lib-vpn-egress
test ! -e /etc/sing-box/config.json || \
mv /etc/sing-box/config.json /root/vpn-egress-0.1-archive/config.json
```
Архив содержит secrets и должен оставаться `root:root 0700/0600`. Не копировать
старые `policy.json`, `state.json` или `last-good.json` обратно в 0.2.0.
## 3. Установить exact sing-box 1.14.0
```bash
apt-get update
apt-get install sing-box=1.14.0
sing-box version
```
Вывод должен содержать exact `1.14.0`, Linux environment, `with_quic` и
`with_gvisor`. Runtime mask не снимать: старый config уже архивирован, но новая
policy ещё не настроена.
## 4. Установить vpn-egressctl 0.2.0
```bash
dpkg -i vpn-egressctl_0.2.0_all.deb
systemctl daemon-reload
```
Если postinst сообщает о legacy state или unmanaged config, не обходить
проверку: архивировать указанный объект и повторить `dpkg --configure
vpn-egressctl`.
## 5. Настроить новую policy и Gecko URI
Редактировать новый `/etc/vpn-egress/policy.json` вручную. Из старой policy
можно перенести осознанно проверенные локальные значения, но нельзя заменять ею
новый файл целиком.
```bash
install -m 0600 -o root -g root /dev/null /etc/vpn-egress/hysteria2.uri
@@ -75,75 +76,37 @@ unset URI
vpn-egressctl check
vpn-egressctl diff
vpn-egressctl sync
```
Более простой вариант для интерактивного применения:
Проверить, что URI содержит `obfs=gecko`. `check` не запускает service.
## 6. Активировать защиту и config
```bash
vpn-egressctl import --stdin
systemctl enable --now vpn-egress-guard.service
nft list table inet vpn_egress_guard
systemctl unmask --runtime sing-box.service
vpn-egressctl sync
vpn-egressctl doctor
```
Команда сама запросит URI без echo, выполнит check/apply/healthcheck и при
неуспехе восстановит старые URI и config.
После первого успешного sync `last-good` ещё отсутствует — это ожидаемо.
## 6. Включение watcher
## 7. Acceptance и watcher
С workload за `eth1` проверить DNS, TCP, UDP, внешний VPN IP и отсутствие
прямого WAN при остановленном sing-box. Серверный Gecko E2E выполняется отдельно
владельцем HY2XS.
```bash
systemctl enable --now vpn-egress-sync.path
vpn-egressctl status --json
vpn-egressctl doctor
```
## 7. Вывод старого renderer из эксплуатации
## Возврат к 0.1.0
После успешного canary и rollback-теста:
```bash
mv /root/render-singbox-hy2.sh \
/root/vpn-egress-migration/render-singbox-hy2.sh.disabled
```
Удалять старый файл в день миграции не нужно: перемещение остаётся обратимым.
Старый `/usr/local/sbin/vpn-egress-guard.sh` можно архивировать после проверки,
что package-managed unit использует `vpn-egressctl guard-apply`.
## 8. Acceptance
Обязательные проверки:
```bash
vpn-egressctl status
vpn-egressctl doctor
sing-box check -c /etc/sing-box/config.json
ip -4 rule show
ip -4 route show table 2022
nft list table inet vpn_egress_guard
nft list table inet sing-box
systemctl show sing-box.service -p Requires -p After
```
В `config.json` и nftables не должно быть ни старого `185.156.108.141`, ни
текущего `85.208.119.160`.
С настоящего workload в `10.30.0.0/24` обязательно проверить:
1. DNS через шлюз;
2. TCP и UDP через VPN;
3. HTTPS-запрос к контролируемому endpoint или Cloudflare trace;
4. соответствие наблюдаемого public IP ожидаемому VPN egress;
5. повтор тех же проверок после restart sing-box.
Anti-leak проверяется только из консоли canary/staging, чтобы не потерять
удалённый доступ к production:
```bash
# На шлюзе:
systemctl stop sing-box.service
# На workload за eth1: запрос наружу должен завершиться ошибкой, а не пойти напрямую.
curl --fail --connect-timeout 5 https://www.cloudflare.com/cdn-cgi/trace
# На шлюзе:
systemctl start sing-box.service
vpn-egressctl doctor
```
0.2.0 не выполняет downgrade. Возврат — отдельная ручная чистая установка:
удалить 0.2.0, архивировать её состояние, установить exact старые packages и
только после этого восстановить согласованный snapshot 0.1.0. Нельзя запускать
config одной версии под бинарником другой.
+23 -38
View File
@@ -1,21 +1,19 @@
# Эксплуатация
## Ротация credential
Интерактивно:
## Ротация URI
```bash
sudo vpn-egressctl import --stdin
```
Из защищённого файла:
Либо из защищённого файла режима 0600:
```bash
sudo vpn-egressctl import --file /run/credentials/new-hysteria2.uri
```
Файл должен иметь режим `0600`. Не используйте positional argument, environment
variable, shell history или URL в тикете/логе.
Production URI HY2XS должен использовать Gecko. Не передавать URI через argv,
environment, shell history, тикет или журнал.
## Изменение policy
@@ -26,52 +24,39 @@ vpn-egressctl diff
vpn-egressctl sync
```
Path unit следит только за URI. Policy применяется явным `sync`, чтобы случайное
редактирование инфраструктурных параметров не вызвало неожиданный restart.
Path unit следит только за URI; policy применяется явным `sync`.
## Состояние
## State и rollback
```bash
vpn-egressctl status
vpn-egressctl status --json
```
State содержит только hashes, endpoint без auth, версию, время и результат.
## Rollback
```bash
vpn-egressctl rollback
```
Команда проверяет last-good реальным sing-box, атомарно меняет конфиги местами,
перезапускает сервис и выполняет тот же healthcheck. Повторный rollback возвращает
конфигурацию, которая была активна до первого rollback.
Rollback относится только к конфигам, успешно управлявшимся этой же версией
0.2.0 с sing-box 1.14.0. `last-good.meta.json` обязан совпадать по controller
version, engine version и SHA-256. Legacy backup отклоняется.
После ручного rollback desired URI остаётся прежним. `status` показывает
`status=rolled_back`, а `doctor` сообщает `config-drift`, потому что production
config отличается от заново отрендеренного desired state. Перед новым `sync`
нужно либо исправить URI, либо осознанно вернуть desired state.
Первое clean apply не имеет предыдущего конфига и не создаёт `last-good`.
Последующие успешные изменения сохраняют backup. Ручной rollback меняет current
и last-good местами, поэтому повторная команда возвращает предыдущий current.
## Реакция systemd.path
После rollback desired URI не меняется; `doctor` показывает config drift. До
следующего `sync` нужно исправить URI либо осознанно вернуть desired state.
`PathChanged` запускает oneshot `vpn-egress-sync.service`. Одновременный ручной
import сериализуется lock-файлом. Если новый URI некорректен, production config
не меняется, а unit завершается с ошибкой, видимой в journal.
## Watcher
`PathChanged` запускает `vpn-egress-sync.service`. Все операции сериализованы
lock-файлом. Некорректный URI не меняет production config.
```bash
journalctl -u vpn-egress-sync.service -n 100 --no-pager
systemctl reset-failed vpn-egress-sync.service
```
## Плановое обновление
## Обновления package/engine
Обновление policy или пакета внутри 1.13.19:
1. `vpn-egressctl check`;
2. `vpn-egressctl diff`;
3. backup артефакта пакета;
4. обновление;
5. `vpn-egressctl sync`;
6. `vpn-egressctl doctor`;
7. контролируемый rollback-тест на staging.
0.2.0 не объявляет совместимость с другими версиями приложения или sing-box.
Любой следующий переход выполняется по отдельному release runbook как clean
install. `apt upgrade` не считается допустимой процедурой обновления этого
control plane.
+36 -40
View File
@@ -2,57 +2,53 @@
## Секреты
Секретами считаются Hysteria authentication, obfs password, URI целиком,
generated config и все backups.
Секретами считаются auth, obfs password, URI целиком, generated config и все
backup-файлы.
- CLI не принимает URI в argv.
- Dataclass скрывает credentials из `repr()`.
- Ошибки parser не включают исходное значение.
- diff заменяет secret values на `<REDACTED>`.
- state хранит только SHA-256 source/config и безопасный endpoint label.
- Policy/URI/config/state/backups имеют `root:root 0600`, каталоги —
`root:root 0700`; `doctor` проверяет этот ограниченный набор объектов.
- subprocess вызывается массивом аргументов без shell.
- CLI не принимает URI позиционным аргументом;
- dataclass скрывает credentials из `repr()`;
- parser errors не содержат исходный URI;
- diff заменяет secret values на `<REDACTED>`;
- state хранит hashes, версию и endpoint без auth;
- URI/config/backups имеют `0600`, каталоги — `0700`;
- subprocess запускается массивом аргументов без shell.
После попадания действующего URI в чат, issue, shell history или journal оба
credential следует перевыпустить.
При попадании URI в чат, issue, history или journal следует заменить оба
credential.
## Fail closed
До изменения production проверяются:
До изменения production проверяются strict policy, URI, exact 1.14.0,
`with_quic`, `with_gvisor`, deterministic candidate и настоящий `sing-box
check`. Неизвестные параметры не игнорируются.
- policy schema;
- URI syntax и поддерживаемые параметры;
- точная версия, `with_quic` и `with_gvisor` для `stack=mixed`;
- deterministic candidate;
- `sing-box check`.
In-place package upgrade запрещён. Legacy state/config требует ручного
архивирования; автоматической миграции или cross-version rollback нет.
Неизвестные параметры никогда не игнорируются. Production bypass для другой
версии отсутствует.
## Same-release backup provenance
Текущий config можно сохранить как last-good только если его SHA и engine
version подтверждены state версии 0.2.0. Каждый автоматический backup имеет
соседний `.meta`, а last-good — `last-good.meta.json`. Несовпадение checksum или
версии блокирует rollback до изменения production.
Metadata защищает от операционной ошибки, но не от атакующего с root-доступом.
## Anti-leak
Отдельная таблица `inet vpn_egress_guard` отклоняет forwarding с `eth1` напрямую
на `eth0`. Она не принадлежит sing-box и остаётся отдельной от динамической
таблицы `inet sing-box`.
Отдельная таблица `inet vpn_egress_guard` отклоняет forwarding `eth1 -> eth0`.
Она не принадлежит sing-box и остаётся независимой от таблицы `inet sing-box`.
Package drop-in требует успешного запуска guard перед sing-box.
Guard запускается до sing-box. Drop-in `sing-box.service` одновременно задаёт
requirement и ordering dependency: ошибка запуска guard блокирует sing-box.
Остановка, удаление guard unit или ручное изменение его nftables-таблицы является
security-sensitive операцией и не выполняется CLI автоматически.
Systemd dependency не является watchdog для ручного удаления nftables table;
runtime-состояние проверяет `vpn-egressctl doctor`.
Имена интерфейсов поступают из уже провалидированного policy, subprocess не
использует shell, а замена таблицы выполняется одной nft batch-транзакцией.
## DNS и TLS
## Healthcheck
`dns_mode=disabled` запрещает новому TUN-механизму 1.14 менять native DNS и
platform interception. DNS контролируется явной route rule. Chrome QUIC
parroting включён (`disable_chrome_parrot=false`); production acceptance обязан
подтвердить совместимый RSA/ECDSA/ACME certificate, поскольку Ed25519 с этим
режимом несовместим.
HTTPS healthcheck подтверждает локальную post-activation связность шлюза, но не
весь forwarded path `eth1 -> TUN -> HY2`. Он раскрывает проверочному endpoint факт
обращения с VPN egress IP. URL можно заменить внутренним контролируемым endpoint.
Отключение URL ослабляет проверку до состояния systemd.
## Ограничения URI 1.13.19
`pinSHA256` не преобразуется в `certificate_public_key_sha256`, потому что это
разные fingerprint semantics. `ech` не преобразуется без проверенного PEM/config
list adapter. Silent downgrade TLS запрещён.
`pinSHA256` и `ech` отклоняются: молчаливое ослабление TLS запрещено.
+47 -11
View File
@@ -1,15 +1,51 @@
# Почему sing-box 1.14 не поддерживается
# Контракт sing-box 1.14.0
Ветка 1.14 меняет конфигурационный контракт TUN, TLS/QUIC и Hysteria2. Проект не
пытается угадать совместимость и не содержит `--allow-unsupported`.
Проект поддерживает ровно `sing-box 1.14.0`. RC, alpha, другие patch-релизы и
будущие версии отклоняются до записи файлов.
Отдельная миграция на 1.14 потребует:
## Принятые изменения 1.14
- аудита release notes и tagged schema;
- нового renderer с отдельными golden fixtures;
- проверки удалённых/deprecated полей;
- повторного TUN/nftables/strict-route canary;
- тестов Hysteria2 reconnect, DNS и rollback;
- отдельного package release и runbook.
### Gecko
До завершения этой работы любой 1.14.x отклоняется до записи файлов.
```json
{
"type": "gecko",
"password": "...",
"min_packet_size": 512,
"max_packet_size": 1200
}
```
512/1200 совпадает с profile HY2XS и текущими upstream defaults, но рендерится
явно. Hysteria отмечает Gecko как experimental; проект осознанно принимает его
как единственный production obfs сервера HY2XS.
### TUN DNS
Новый default `dns_mode=hijack` не используется. Renderer задаёт `disabled`,
потому что проект уже имеет явную DNS route rule и отдельные bootstrap/DoH
маршруты. Это решение должно подтверждаться privileged Linux acceptance.
### Chrome QUIC parroting
Renderer явно задаёт `disable_chrome_parrot=false`. Серверный certificate должен
быть RSA/ECDSA/ACME, не Ed25519.
### Congestion control
Policy задаёт `up_mbps/down_mbps`, поэтому используется Hysteria CC. Поле
`bbr_profile` не добавляется: оно предназначено для режима BBR при пустых
bandwidth values.
## Не включено
Realm, mimic, ECH, certificate pinning, random hop interval, TLS spoof, custom
Gecko sizes и новые UDP NAT knobs не входят в контракт 0.2.0.
## Официальные источники
- <https://github.com/SagerNet/sing-box/releases/tag/v1.14.0>
- <https://sing-box.sagernet.org/configuration/outbound/hysteria2/>
- <https://sing-box.sagernet.org/configuration/inbound/tun/>
- <https://v2.hysteria.network/docs/developers/URI-Scheme/>
- <https://v2.hysteria.network/docs/advanced/Full-Client-Config/>
+32 -36
View File
@@ -2,55 +2,51 @@
## Уровни
1. Unit: URI, policy, version, renderer, redaction.
2. Failure injection: candidate rejection, restart failure, URI/config rollback,
idempotency и manual rollback swap.
3. Real binary: `sing-box 1.13.19 check` на Linux и schema/format validation на Windows.
4. Privileged Linux: TUN, nftables, systemd и локальный HTTP connectivity healthcheck.
5. Canary: DNS A change без endpoint CIDR и credential rotation.
6. Reboot: guard ordering, persisted config и path watcher.
1. Unit: URI, policy, version, renderer, redaction и metadata.
2. Failure injection: candidate rejection, restart failure, URI/config restore,
unmanaged config, corrupted/legacy last-good.
3. Packaging: MPL-2.0, exact dependency, upgrade rejection и postinst guard.
4. Real binary: официальный sing-box 1.14.0 для Gecko и Salamander configs.
5. Privileged Linux: TUN, systemd, nftables, routing и DNS.
6. HY2XS acceptance: Gecko TCP/UDP/DNS, failures, reconnect и reboot.
## Unit tests
На Linux:
```bash
make check
```
Тест с реальным бинарником включается явно:
На Windows с isolated Python:
```bash
SING_BOX_1_13_19=/usr/bin/sing-box make test
```powershell
E:\python-31312\python.exe -c "import sys,unittest; sys.path[:0]=[r'F:\projects\singbox_glue',r'F:\projects\singbox_glue\src']; s=unittest.defaultTestLoader.discover('tests',top_level_dir='.'); r=unittest.TextTestRunner(verbosity=2).run(s); raise SystemExit(not r.wasSuccessful())"
```
Он предварительно проверяет exact version, `with_quic` и `with_gvisor`.
Windows-бинарник не может создать Linux auto-redirect и поэтому выполняет
`format` полной схемы; обязательный `check` остаётся в Linux CI/Incus.
## Real binary
## Privileged acceptance
```bash
SING_BOX_1_14_0=/usr/bin/sing-box make test
```
В disposable Incus-контейнере с двумя NIC:
Тест сначала проверяет exact version/tags. На Linux выполняется `check`; на
Windows — schema decoding через `format`, поскольку Windows binary не может
создать Linux auto-redirect.
1. установить sing-box 1.13.19 и пакет;
2. применить test URI;
3. проверить таблицу 2022 и marks;
4. подтвердить, что `eth1 -> eth0` отклоняется без sing-box;
5. подтвердить TCP/UDP/DNS через TUN при рабочем HY2;
6. сломать credential и проверить автоматический rollback;
7. изменить A-запись endpoint, не меняя URI/config;
8. перезагрузить контейнер и повторить doctor.
## Privileged Debian acceptance
Отдельный dependency failure test выполняется только в disposable Incus:
В disposable Incus/VM с двумя NIC проверить:
1. остановить sing-box и guard;
2. временно задать в test policy отсутствующий upstream interface;
3. убедиться, что `systemctl start sing-box.service` завершается ошибкой из-за
неуспешного guard;
4. восстановить policy и подтвердить успешный совместный запуск;
5. проверить drop-in командой `systemd-analyze verify` и содержимое собранного
Debian-пакета через `dpkg-deb -c`.
1. чистую установку и отказ in-place upgrade;
2. Gecko config и отсутствие last-good после первого apply;
3. table 2022, rule 9000/32768, marks и NFQUEUE 100;
4. bootstrap DNS через `eth0`, DoH через `hy2-out`;
5. TCP/UDP/DNS с workload за `eth1`;
6. отсутствие прямого WAN при остановленном sing-box;
7. credential failure и same-release rollback;
8. отказ legacy/mismatched metadata;
9. A-record change и reboot recovery.
Локальный HTTPS healthcheck не заменяет запросы DNS/TCP/UDP с реального workload
за `eth1`. При остановленном sing-box такой workload не должен получить прямой
доступ через `eth0`.
Production A-запись не используется для эксперимента: нужен staging hostname.
Server-side Gecko E2E выполняет владелец HY2XS. Salamander проверяется только на
disposable test server и не добавляется в production server configuration.
+28 -34
View File
@@ -9,54 +9,48 @@ vpn-egressctl status --json
## Unsupported sing-box version
Установлена не `1.13.19`. Конфигурация не изменялась. Проверить:
Установлена не exact `1.14.0` либо отсутствуют `with_quic`/`with_gvisor`.
Конфигурация не изменялась. Не обходить version gate.
```bash
sing-box version
apt-cache policy sing-box
```
## In-place upgrades are not supported
Не использовать ручной обход version gate.
Пакет 0.2.0 устанавливается поверх старого релиза. Старый package остаётся
установленным; выполнить clean-install runbook из `docs/migration.md`.
## State from another release / unmanaged configuration
Обнаружены сохранённые файлы 0.1.0 или конфиг вне управления 0.2.0. Остановить
sing-box, убедиться в наличии guard, переместить старые данные в защищённый архив
и повторить `dpkg --configure vpn-egressctl`. Не редактировать старый state так,
чтобы обойти проверку.
## sing-box rejected generated configuration
Candidate удалён, production не менялся. Проверить package version, policy и
логи oneshot. Секреты в отчёт не прикладывать.
Candidate удалён, production не менялся. Проверить exact package version, URI и
policy. Не прикладывать URI/config к отчёту без удаления secrets.
## Apply failed, previous configuration restored
Автоматический rollback успешен. Проверить:
Same-release rollback успешен. Проверить service/journal и выполнить doctor.
```bash
systemctl status sing-box --no-pager
journalctl -u sing-box -u vpn-egress-sync.service -n 200 --no-pager
vpn-egressctl doctor
```
## Automatic rollback also failed
## Critical rollback failed
Если это первое clean apply, предыдущего управляемого config нет — восстановить
нечего. sing-box должен оставаться остановленным, а guard активным. При наличии
last-good проверить его `.meta`; не запускать файл вручную при несовпадении
версии или SHA.
Не выполнять новый sync. Использовать `/var/lib/vpn-egress/last-good.json` или
timestamped backup из консоли контейнера, затем `sing-box check` и restart.
## Configuration metadata checksum does not match
Backup и metadata рассогласованы или повреждены. Автоматический rollback
правильно заблокирован. Использовать другой version-matched backup только через
консоль и после `sing-box check`.
## Endpoint exclusion
Если doctor сообщает, что A-запись endpoint находится в TUN exclusions, удалить
публичный CIDR из policy и выполнить `check`, `diff`, `sync`. Не заменять его на
новый IP.
Удалить публичный endpoint CIDR из policy. Не заменять его новым A-адресом.
## Healthcheck request failed
Проверить DNS, handshake Hysteria2, доступность health URL и nftables. Временно
ставить `url: null` на production нельзя без отдельного решения: это скрывает
неработающую локальную post-activation связность. Даже успешный healthcheck не
заменяет отдельную проверку forwarded path с workload за `eth1`.
## Watcher failed
Некорректный файл URI не меняет рабочий config. Исправить его безопасным
`vpn-egressctl import --stdin`, затем:
```bash
systemctl reset-failed vpn-egress-sync.service
systemctl status vpn-egress-sync.path --no-pager
```
Проверить DNS, Gecko password, auth, SNI, certificate, UDP path и nftables.
Успешный локальный healthcheck не заменяет forwarding E2E с `eth1`.