fix: harden production topology and runtime state
This commit is contained in:
+28
-11
@@ -127,7 +127,7 @@ awg2-harness validate-runtime
|
||||
|
||||
При `swap.enabled = false` управляемый файл удаляется. **Остаются на месте**: сам swapfile, его активность в ядре и запись в `/etc/fstab` — то есть после перезагрузки swap снова включится. Удаляется только persistence `vm.swappiness`. Runtime-значение `vm.swappiness` при этом намеренно не меняется: назначать произвольное значение означало бы выдумать политику, о которой оператор не просил. После перезагрузки действует kernel default. Активный swap при отключении модуля тоже не выключается — деструктивное действие не должно запускаться самим фактом отключения функции.
|
||||
|
||||
`net.ipv4.tcp_congestion_control` и `net.core.default_qdisc` попадают в целевое состояние только при активном BBR.
|
||||
`net.ipv4.tcp_congestion_control` и `net.core.default_qdisc` попадают в целевое состояние только когда BBR желателен по policy. Фактическая активность диагностируется отдельно по runtime `net.ipv4.tcp_congestion_control`.
|
||||
|
||||
## 3. Структура модулей
|
||||
|
||||
@@ -152,7 +152,15 @@ traffic_core/src/
|
||||
│ ├── host.rs owned iptables/ip6tables INPUT chains (host namespace)
|
||||
│ └── antitorrent.rs таблица inet awg2_harness (namespace по конфигу)
|
||||
├── sysctl_tuning.rs управляемый sysctl baseline
|
||||
├── swap.rs управляемый swap
|
||||
├── swap/
|
||||
│ ├── mod.rs facade и orchestration ensure_swap
|
||||
│ ├── model.rs модели desired/runtime/status и replacement state
|
||||
│ ├── path.rs syntax/path-chain safety и state directory
|
||||
│ ├── ownership.rs ownership record и legacy sidecar
|
||||
│ ├── runtime.rs swapon/swapoff/mkswap и parser runtime state
|
||||
│ ├── replacement.rs создание, resize и rollback транзакции
|
||||
│ ├── persistence.rs /etc/fstab, swappiness и legacy migration
|
||||
│ └── status.rs inspect_swap и drift aggregation
|
||||
├── status.rs сборка и печать статуса
|
||||
├── lock.rs файловые блокировки
|
||||
└── error.rs типы ошибок
|
||||
@@ -273,11 +281,13 @@ struct PeerTrafficStatus {
|
||||
awg show all allowed-ips
|
||||
```
|
||||
|
||||
Правила разбора:
|
||||
Правила разбора и общий `PeerTopology` contract:
|
||||
|
||||
- строки разбираются структурно на public key peer и его allowed IPs;
|
||||
- в этой команде allowed ips одного peer разделены **пробелом**, в `show all dump` — запятой, в обычном `show` — `", "`; нужно потреблять все токены начиная с третьей колонки, иначе peers с несколькими allowed IP получат shaping только по первому адресу;
|
||||
- оставляются только IPv4 `/32` внутри `amnezia.peer_ipv4_cidr`;
|
||||
- в этой команде allowed ips разделены **пробелом**, в `show all dump` — запятой, в обычном `show` — `", "`; parser обязан проверить весь список, а не молча взять первый адрес;
|
||||
- поддерживается ровно один IPv4 `/32` внутри `amnezia.peer_ipv4_cidr` на peer; `(none)` допустим и не создаёт peer class;
|
||||
- IPv6-only, IPv4+IPv6, несколько allowed IPs, маска не `/32`, невалидный CIDR или IPv4 вне configured peer CIDR возвращают typed `UnsupportedTopology`;
|
||||
- topology проверяется один раз на startup/periodic/event cycle и передаётся traffic и anti-torrent. При ошибке ни одна из этих подсистем не начинает mutation и не сообщает успешный reconcile;
|
||||
- offline peers включаются: online-состояние не является требованием для shaping;
|
||||
- перед присвоением class id строки peer IP сортируются лексикографически, чтобы сохранить действующий контракт class-id.
|
||||
|
||||
@@ -287,7 +297,7 @@ awg show all allowed-ips
|
||||
awg show
|
||||
```
|
||||
|
||||
Fallback-парсер разбирает текстовый вывод, извлекая строки `allowed ips:`. Он используется, если основная команда завершилась ошибкой или вернула пустой результат.
|
||||
Fallback-парсер разбирает текстовый вывод, извлекая строки `allowed ips:`. Он используется, если основная команда завершилась ошибкой или вернула пустой результат, и применяет тот же строгий topology contract.
|
||||
|
||||
Классификация строк `show all dump`:
|
||||
|
||||
@@ -365,7 +375,7 @@ apply_firewall():
|
||||
apply_antitorrent_firewall(config) # namespace по anti_torrent.apply_in_amnezia_netns
|
||||
```
|
||||
|
||||
Каждый из firewall-путей сначала инспектирует фактическое состояние и выходит без изменений, если drift отсутствует. Anti-torrent таблица заменяется одной атомарной nft-транзакцией. Host firewall сначала строит и проверяет inactive chain отдельно для IPv4/IPv6, затем переключает помеченный jump; сбой IPv6 после IPv4 switch откатывает IPv4. После apply выполняется обязательная повторная инспекция.
|
||||
Каждый из firewall-путей сначала инспектирует фактическое состояние и выходит без изменений, если drift отсутствует. Anti-torrent таблица заменяется одной атомарной nft-транзакцией и при включённом guard требует успешно проверенный `PeerTopology`; host firewall от server-side peer topology не зависит. Host firewall сначала строит и проверяет inactive chain отдельно для IPv4/IPv6. Новый jump всегда вставляется через `-I INPUT 1`; старый остаётся вторым до успешного переключения обеих IP-family и затем удаляется по полной rule specification. Numeric `-R/-D` для managed jumps не используется. Сбой IPv6 удаляет новый IPv4 jump по точной specification, поэтому старый IPv4 jump всё время остаётся рабочим. После apply выполняется обязательная повторная инспекция.
|
||||
|
||||
Для production контролируемая полная пересборка при drift — единственный поддерживаемый путь.
|
||||
|
||||
@@ -407,6 +417,8 @@ apply_firewall():
|
||||
|
||||
Host firewall не открывает published Amnezia UDP port в `INPUT`: Docker bridge publish проходит через `FORWARD`/NAT. Собственная chain разрешает loopback, established/related, configured SSH и optional ICMP/ICMPv6, затем завершает обработку configured `DROP`/`ACCEPT` verdict. Поле `amnezia.expected_host_udp_port` относится только к диагностике port path.
|
||||
|
||||
Жизненный цикл managed jump использует только exact rule spec с comment `awg2-harness:host-firewall` и target `AWG2H_A`/`AWG2H_B`. Чужое правило, вставленное другим firewall manager между inspect и switch, не может быть заменено или удалено по изменившемуся номеру строки.
|
||||
|
||||
## 11. Anti-torrent firewall guard
|
||||
|
||||
Это часть production-контракта AWG2. Модуль вписывается в мульти-оркестраторную модель, потому что сервер выделенный и бинарю разрешено владеть host/network правилами, нужными VPN-продукту. Правило всё равно обязано быть узко ограниченным и наблюдаемым.
|
||||
@@ -542,7 +554,8 @@ System:
|
||||
interface: <iface>
|
||||
Network tuning:
|
||||
enabled, apply_on_daemon_start, tcp_bbr_mode, conf_path, modules_load_path,
|
||||
значения управляемых ключей, bbr_available, bbr_active, drift[, reason][, warning]
|
||||
значения управляемых ключей, bbr_available, bbr_desired,
|
||||
bbr_runtime_active, drift[, reason][, warning]
|
||||
Peers:
|
||||
total: N
|
||||
- <peer ip>
|
||||
@@ -560,6 +573,7 @@ Host firewall:
|
||||
enabled, ssh_port, input_policy, drift[, reason]
|
||||
Docker/Amnezia port path:
|
||||
expected_host_udp_port, docker_userland_proxy, published_udp_port,
|
||||
host_bindings (все HostIp:HostPort, включая routed binding без HostPort),
|
||||
awg_listen_udp_port, port_contract, host_forward_policy_drop,
|
||||
docker_forward_rules_present, note[, warning]
|
||||
Anti-torrent:
|
||||
@@ -693,7 +707,7 @@ inode = 123456
|
||||
|
||||
### Перезапись `/etc/fstab`
|
||||
|
||||
Это самая разрушительная операция пакета, поэтому она транзакционная и вынесена в отдельный модуль `fstab.rs`. Контракт:
|
||||
Это самая разрушительная операция пакета, поэтому она транзакционная и изолирована в `swap/persistence.rs`. Контракт:
|
||||
|
||||
- ошибка чтения `/etc/fstab` **никогда** не интерпретируется как пустой файл. `PermissionDenied`, ошибка ввода-вывода и отсутствие файла различаются, и все три прекращают операцию. Отсутствие `/etc/fstab` на Debian-хосте считается аномалией, а не основанием создать его с нуля;
|
||||
- пустой или состоящий из пробелов кандидат отклоняется;
|
||||
@@ -714,8 +728,8 @@ inode = 123456
|
||||
|
||||
- валидация конфига, включая форму `amnezia.wg_binaries`;
|
||||
- разбор `config.example.toml` в актуальную схему с точным default-порядком контейнеров, `awg`/`wg` и anti-torrent baseline `500`;
|
||||
- парсер `awg|wg show all allowed-ips`, включая несколько allowed ips через пробел;
|
||||
- fallback-парсер `awg|wg show`;
|
||||
- парсер `awg|wg show all allowed-ips`: один IPv4 `/32` проходит, IPv4+IPv6, IPv6-only, несколько маршрутов, маска не `/32` и адрес вне configured CIDR дают explicit `UnsupportedTopology`;
|
||||
- fallback-парсер `awg|wg show` и dump-парсер применяют тот же topology contract; `(none)` остаётся допустимым;
|
||||
- классификация peer/interface строк `awg|wg show all dump` против двух 30-полевых AWG 3.1 fixtures, AWG 3.0 с включённой и выключенной защитой заголовка, AWG 2.x, чистого WireGuard, а также конфигурации AWG 2.0, прочитанной tools AWG 3.0;
|
||||
- разбор dump с несколькими интерфейсами, с интерфейсом без peers и с повреждёнными строками;
|
||||
- порядок выбора бинаря wg и ошибка при отсутствии всех кандидатов;
|
||||
@@ -727,6 +741,9 @@ inode = 123456
|
||||
- packet statistics из Debian-подобного `tc -s class`;
|
||||
- отказ удалять foreign IFB/root qdisc;
|
||||
- typed Docker readiness retry/timeout/lower-priority event;
|
||||
- Docker UDP bindings: dual-stack HostIp, routed binding с пустым HostPort и неоднозначные/divergent host ports;
|
||||
- BBR decision: доступный BBR при runtime `cubic` означает `desired=yes`, `runtime_active=no`; mode `off` не маскирует уже активный runtime BBR;
|
||||
- host firewall rollback удаляет новый jump по exact specification и не затрагивает чужое правило в начале `INPUT`;
|
||||
- conntrack count из Amnezia namespace и global max с host;
|
||||
- diff drift;
|
||||
- расчёт окна перезагрузки;
|
||||
|
||||
Reference in New Issue
Block a user