Imported from avatarDD/zapret-gui (
.claude/skills/awg/SKILL.md). Install upstream withnpx skills add avatarDD/zapret-gui --skill awg. Copyright stays with the author.
AmneziaWG (AWG) — справочник для zapret-gui
Единый источник истины о том, как AmneziaWG реально работает и как с ним
обращаться в zapret-gui. Читать перед тем, как трогать разбор/генерацию
.conf, менеджер туннелей, параметры обфускации, WARP, интеграцию с Keenetic
или объяснять пользователю «почему handshake есть, а трафика нет».
Источники истины (в порядке убывания авторитета):
- amnezia-vpn/amneziawg-go — userspace-реализация протокола на Go
(
README.md= спецификация параметров обфускации; сверено с v3.1.20260814); amnezia-vpn/amneziawg-tools — форкwireguard-tools(awg/awg-quick), сверено с v3.1.20260812. README на ветке 3.1 не изменился: два новых параметра (§2.1.2) описаны только кодом —device/uapi.goиsrc/config.c. Окончательный список ключей, которые вообще можно писать в.conf, — этоkey_matchв егоsrc/config.c, а НЕ документация: docs.amnezia.org описывает параметры протокола, часть которых парсер не принимает (см. §2.1 проJ1..J3/Itime). docs.amnezia.org — описание протокола и версий (1.0 / 1.5 / 2.0); поколение AWG 3+ документировано только в README amneziawg-go (§2.1.1). awg show <iface>/awg showconf <iface>— что демон реально принял. Если вshowconfнет вашихI1/S3/H*— значит доawg setconfони не дошли (см. §3, §12). Это первичная диагностика.- Наш код —
core/awg_config.py(парсер/генератор.conf, ключи),core/awg_manager.py(up/down/status/diagnostics),core/awg_platform.py(пути),core/awg_installer.py+core/awg_detector.py(бинари/арх),core/warp_generator.py+core/warp_importer.py+core/awg_warp_in_warp.py(WARP),core/ndms/wg_discovery.py(нативный WG Keenetic),core/awg_watchdog.py,core/awg_autostart_manager.py+core/awg_init_script.py,api/awg.py,web/js/pages/awg_*.js.
⚠️ Версия протокола — главный источник «handshake есть, трафика нет». AmneziaWG несовместим между мажорными версиями обфускации (§2.3). Если клиент шлёт data-пакеты в формате одной версии, а пир ждёт другую — handshake проходит (он на общих параметрах), а transport-пакеты дропаются. Классическая картина в
awg show:transfer: 92 B received, 20 KB sent— мы отправляем, сервер молчит. См. §12.
1. Как AWG используется в zapret-gui
Модель: один туннель = один сетевой интерфейс (как у WireGuard; в отличие
от sing-box, где инстанс = файл). Имя инстанса = имя .conf без расширения и =
имя интерфейса (конфиг awg0.conf → интерфейс awg0).
Мы НЕ используем awg-quick. Логику wg-quick (Address/MTU/Table/DNS/
PreUp…PostDown, маршруты из AllowedIPs) мы реализуем сами в
awg_manager._do_up, а ядро поднимаем напрямую:
amneziawg-go <iface> # форкает userspace-демон + TUN-устройство
awg setconf <iface> <filtered.conf> # заливает крипто + обфускацию в демон
ip link set dev <iface> mtu <MTU>
ip addr add <Address> dev <iface>
ip link set dev <iface> up
ip route add <AllowedIPs> dev <iface> table <id> # если Table != off
Почему так, а не awg-quick up: на Keenetic/Entware нет полноценного bash и
ряда утилит, которые тянет awg-quick; нам нужен контроль над таблицами
маршрутизации (Selective routing GUI) и устойчивый teardown. Поэтому
amneziawg-go (userspace) + awg setconf + ручной ip — наш базовый путь.
Поток:
- Конфиги лежат в
platform.config_dir(Keenetic:/opt/etc/amneziawg/). CRUD — черезapi/awg.pyповерхcore/awg_config.py. awg setconfполучает НЕ весь.conf, а отфильтрованный (толькоWG_INTERFACE_FIELDS+WG_PEER_FIELDS, §3) — wg-quick-поля демон не понимает.- Маршрутизация GUI поверх:
target_iface = <iface>, правила навешиваетcore/routing/applierпри подъёме интерфейса.
2. Параметры обфускации AmneziaWG (официальная истина)
AmneziaWG — форк WireGuard-Go, который убирает узнаваемые DPI-сигнатуры
WireGuard, сохраняя его крипто и производительность. Все «магия» —
дополнительные поля в секции [Interface] (в README апстрима эта секция
называется [Device]). Какие из них обязаны совпадать с пиром, а какие
задаются только на клиенте — см. таблицу «сторон» ниже: путать их дорого.
Если все параметры = 0 → ведёт себя как обычный WireGuard (для плавной
миграции), и «значение не задано» апстрим трактует именно как 0.
2.1 Таблица параметров (источник: amneziawg-go README + docs.amnezia.org)
| Параметр | Что делает | Диапазон/рекомендация | Версия |
|---|---|---|---|
| Jc | Кол-во junk-пакетов перед handshake (после I1–I5) | рекоменд. 4–12 (docs: 0–10) | 1.0 |
| Jmin | Мин. размер junk-пакета | байты; Jmin ≤ Jmax |
1.0 |
| Jmax | Макс. размер junk-пакета | байты; Jmax < MTU (иначе фрагментация); docs: 64–1024 |
1.0 |
| S1 | Префикс-padding handshake init | 0–64 байт | 1.0 |
| S2 | Префикс-padding handshake response | 0–64 байт | 1.0 |
| S3 | Префикс-padding handshake cookie | 0–64 байт | 2.0 |
| S4 | Префикс-padding transport (data) | 0–32 байта | 2.0 |
| H1 | Магический заголовок (тип) пакета init | 0…4294967295 | 1.0 |
| H2 | Магический заголовок пакета response | 0…4294967295 | 1.0 |
| H3 | Магический заголовок пакета cookie | 0…4294967295 | 1.0 |
| H4 | Магический заголовок transport (data) | 0…4294967295 | 1.0 |
| I1…I5 | Signature-пакеты перед handshake (мимикрия под реальный протокол); отправляются по порядку I1→I5, пропускаются, если значение не задано | последовательность тегов, §2.1.2 | 1.5 |
| J1…J3 | Доп. junk-параметры | — | 1.5, удалены в 2.0 |
| Itime | Тайминг signature-пакетов | — | 1.5, удалён в 2.0 |
Где параметр обязан совпадать с пиром. README amneziawg-go вводит явное
деление: server-side — значение ОБЯЗАНО быть одинаковым на обоих концах,
client-side — можно задавать только у клиента.
| Параметр | Сторона | Практический вывод |
|---|---|---|
Jc/Jmin/Jmax |
client-side | «Junk-пакеты не несут данных, задавать на обеих сторонах не требуется; общая рекомендация — только на клиенте» (README). Поэтому junk и работает против ванильного WireGuard-пира (§8.1) |
I1…I5 |
client-side | то же самое: signature-пакеты пир просто игнорирует |
S1–S4, H1–H4 |
должны совпадать | это формат САМИХ пакетов, пир обязан уметь их разобрать |
HeaderProtectionKey |
server-side | ключ защиты заголовка (§2.1.1) |
ContentPaddingAddition, тайминги, PersistentKeepalive |
client-side | «важно задать паддинг с обеих сторон, но строго не обязательно» |
Тип range,x (AWG 3): значение может быть "a-b", "a" или "(off)" —
например PersistentKeepalive = 22-30. Значение не задано = 0 для любого
параметра.
2.1.2 Синтаксис I1…I5 — это НЕ просто hex
Значение I1..I5 — последовательность тегов (README amneziawg-go, раздел
«Custom signature packets»):
| Тег | Что кладёт в пакет |
|---|---|
<b 0x[seq]> |
статические байты: [seq] — hex-строка чётной длины (2 знака на байт) |
<r [size]> |
[size] случайных байт |
<rd [size]> |
[size] случайных цифр [0-9] |
<rc [size]> |
[size] случайных букв [a-zA-Z] |
<t> |
4 байта текущего времени в формате UNIX |
Теги можно комбинировать в одной строке. Наш парсер (§3.2) заточен под самый
частый случай — многострочный <b …> — и остальные теги переносит как есть
(мы их не интерпретируем, это делает демон). Если правишь parse_conf, не
считай, что содержимое I* обязано быть hex: <r 32><t> — валидное значение.
Jmaxи MTU. README: «еслиJmax≥ системного MTU (не того, что задан в AWG), система нарежет пакет на фрагменты, что со стороны цензора выглядит подозрительно». То же про итоговый размер signature-пакета.
⚠️
J1..J3иItimeнельзя отдавать вawg setconf— вообще никогда. Их нет вkey_matchпарсера amneziawg-tools (src/config.c) ни в одном релизе, включая свежийv3.1.20260812. На неизвестном ключе парсер делаетgoto error: печатаетLine unrecognized: '<строка>'и отбрасывает конфиг целиком. То есть один лишний ключ из чужого.conf= интерфейс не поднимается вовсе, а наружу видно лишь невнятное «Unable to modify interface». Разбирать и сохранять их при round-trip мы обязаны (профиль из клиента Amnezia не должен терять поля), а в setconf — не отдаём:AWG_FIELDS_UNSUPPORTED_BY_TOOLS+_setconf_skip_fieldsвcore/awg_config.py, сторож —tests/test_awg_config.py.
2.1.1 AWG 3+: защита заголовка, паддинг содержимого, тайминги
Ветка amneziawg-go v3.x (текущая; сверено с README v3.1.20260814 —
на 3.1 он не менялся) добавила
поколение параметров, которое апстрим маркирует как [AWG 3+]. В .conf
они живут в секции устройства ([Device] в терминах README = [Interface] в
терминах wg-quick):
| Ключ | Тип / сторона | Назначение |
|---|---|---|
HeaderProtectionKey |
key,string, server-side |
Ключ шифрования низкоэнтропийных полей заголовка — тех, что WireGuard использует для аутентификации и собственного шифрования. Нонсом служит crypto-паддинг S1–S4. Генерируется awg genkey |
ContentPaddingAddition |
uint32,range, client-side |
Дополнительный паддинг содержимого |
RekeyAfterTime |
uint32,range, сек |
Через сколько клиент пробует сделать handshake |
RekeyTimeout |
uint32,range, сек |
Таймаут, после которого handshake повторяется |
RejectAfterTime |
uint32,range, сек |
Когда клиент форсирует handshake и отвергает входящие данные |
KeepaliveTimeout |
uint32,range, сек |
Через сколько после последней отправки данных шлётся keepalive |
MaxHandshakeAttempts |
uint32,range |
Предел попыток handshake |
⚠️ Защита заголовка требует
S1–S4не меньше 12 (README, «Header protection»): паддинг служит нонсом шифра.HeaderProtectionKeyприS*< 12 — нерабочая связка.
Все семь ключей принимаются парсером tools — они есть в key_match
(src/config.c, v3.0.20260730 и новее), в отличие от J1..J3/Itime.
То есть в awg setconf их отдавать можно и нужно.
Наш статус: поддержаны — константа AWG3_INTERFACE_FIELDS в
core/awg_config.py входит в WG_INTERFACE_FIELDS, поэтому поля переживают
round-trip и доходят до демона. validate() проверяет их как тип range
("a-b" / "a" / "(off)" — числом их валидировать нельзя),
HeaderProtectionKey — как base64-ключ на 32 байта, и отдельно ругается,
если при заданном ключе минимальный из S1..S4 меньше
AWG3_HEADER_PROTECTION_MIN_S (12).
⚠️ Раньше этих полей в списке не было, и AWG3-профиль молча терял их при
render_setconf: демон поднимался без защиты заголовка, а пир, который её ждёт, дропал data-пакеты — тот самый «92 B in / 20 KB out» (§12). Тесты-сторожа:TestAwg3Fieldsвtests/test_awg_config.py.
2.1.2 AWG 3.1: случайные хвосты и отказ от cookie-ответов
Пара amneziawg-go v3.1.20260814 + amneziawg-tools v3.1.20260812
добавила ровно два ключа в секцию устройства. В README их нет — источник
истины здесь только код: device/uapi.go (UAPI-ключи) и src/config.c
(key_match).
| Ключ | UAPI-ключ | Тип | Назначение |
|---|---|---|---|
RandomTrailers |
random_trailers |
bool | Дописывать служебным пакетам (init / response / cookie) хвост случайной длины — до размера UDP-окна пира. Размер пакета перестаёт быть константой, по которой его опознаёт DPI |
DisableCookies |
disable_cookies |
bool | Не отвечать cookie-reply на отброшенный handshake. Cookie-ответ — заметная сигнатура WireGuard, но это же и штатная анти-DoS-защита |
⚠️
RandomTrailersсимметричен — включать надо на ОБОИХ концах. Приёмник опознаёт служебный пакет по точному размеру и допускает больший только при включённом флаге (DeterminePacketTypeAndPaddingвdevice/receive.go). Включён у клиента и выключен у сервера — handshake не проходит вовсе: пакет просто не распознаётся как init.
⚠️ Значение пишется как
on/offили число, но НЕtrue/false.parse_boolвsrc/config.cзнает толькоon/off(без учёта регистра) и десятичное число (0= выкл). Наtrueон печатает «Boolean value is neither on/off nor 0/1» и, как на любой ошибке разбора, отбрасывает конфиг целиком. Нашvalidate()ловит это на импорте, пока ещё видно, какое поле виновато.
⚠️ Это отдельная планка поколения, а не «ещё два поля AWG 3+». Движок
v3.0.xна этих UAPI-ключах отвечаетinvalid UAPI device key→ EINVAL → знакомое «Unable to modify interface: Invalid argument». Поэтомуrequired_generation()поднимает такой профиль до 3.1, а не до 3.0: подсказка «нужен AWG 3.0» пользователю, у которого как раз 3.0, увела бы совсем не туда.
Наш статус: поддержаны — AWG31_INTERFACE_FIELDS в core/awg_config.py
входит в WG_INTERFACE_FIELDS, поэтому поля переживают round-trip и доходят
до awg setconf.
2.1.2а Поле [Peer] AdvancedSecurity
В key_match секции [Peer] у tools, кроме стандартных PublicKey,
PresharedKey, AllowedIPs, Endpoint, PersistentKeepalive, есть ещё
AdvancedSecurity (флаг «продвинутой безопасности» для конкретного пира).
Он есть и в нашем WG_PEER_FIELDS, так что импортированный профиль его не
теряет.
PersistentKeepalive в AWG 3+ — тоже тип range: валидны 25, 22-30 и
(off). validate() принимает все три формы и проверяет границы 0..65535
для числовых.
Junk vs padding vs header — не путать.
Jc/Jmin/Jmax— отдельные мусорные пакеты ДО рукопожатия.S1–S4— случайный префикс ВНУТРИ реальных пакетов каждого типа.H1–H4— подменяют первый байт (тип сообщения), чтобы DPI не видел сигнатуру WireGuard.
2.2 H1–H4: ограничения
- Стандартный WireGuard использует типы сообщений 1, 2, 3, 4 (init/
response/cookie/transport).
H1–H4их заменяют — но это работает, только если AmneziaWG-обфускацию понимает и пир. Cloudflare WARP — это ВАНИЛЬНЫЙ WireGuard, онH1–H4 ≠ 1,2,3,4не понимает и дропает handshake. Поэтому для WARPwarp_generatorоставляетH1=1, H2=2, H3=3, H4=4(иS1=S2=0), а обфускацию даёт только junk-пакетами (Jc/Jmin/Jmax) — их ванильный WireGuard просто игнорирует. НестандартныеH*(5 … 0x7FFFFFFF) уместны лишь для AmneziaWG-aware сервера, не для чистого WARP (core/warp_generator.py). Проверено локально: против ванильного WG-пира handshake проходит ТОЛЬКО приS1=S2=0иH1..H4=1,2,3,4; рандомныеH*илиS>0→ handshake не идёт. - В AmneziaWG 2.0 у
H1–H4появилась поддержка диапазонов (range): значения выбираются случайно в заданном окне и не должны перекрываться. В.confэто строка видаH1 = 123-456.parse_confхранитH*как строки, аvalidate()принимает дляH1–H4либо одиночный uint, либо диапазонN-M(N<=M); прочие числовые поля обфускации остаются строгими int. amneziawg-tools (src/config.c) хранитH*как opaque-строки (parse_awg_string) — так что это лишь GUI-санити-чек формата.
2.3 Версии протокола и совместимость (КРИТИЧНО)
| Версия | Что добавляет | Параметры |
|---|---|---|
| 1.0 | базовая обфускация: junk + magic-headers + padding init/response | Jc, Jmin, Jmax, S1, S2, H1–H4 |
| 1.5 | мимикрия под обычные UDP-протоколы (QUIC, DNS…) через signature-пакеты | + I1–I5, J1–J3, Itime |
| 2.0 | «полная мимикрия»: меняющиеся заголовки и размеры пакетов и в data-фазе | + S3, S4; range для H1–H4; − J1–J3, Itime |
3.0 (AWG 3+) |
шифрование полей заголовка, кастомный паддинг содержимого, настраиваемые тайминги WireGuard | + HeaderProtectionKey, ContentPaddingAddition, RekeyAfterTime, RekeyTimeout, RejectAfterTime, KeepaliveTimeout, MaxHandshakeAttempts; тип range у таймингов и PersistentKeepalive (§2.1.1) |
| 3.1 | случайная длина служебных пакетов, отказ от cookie-ответов | + RandomTrailers, DisableCookies (bool, on/off; §2.1.2) |
Нумерация версий ≠ нумерация релизов, но с 3.0 они совпали.
amneziawg-goпрыгнул сv0.2.19сразу наv3.0.0, и[AWG 3+]в README — это ровно поколение параметров этой ветки. Пользователь со старым бинарём (v0.2.x) AWG3-полей не понимает: демон их не применит, аawgиз старогоamneziawg-tools(v1.0.2026*) на них ещё и отбросит весь конфиг (Line unrecognized). То есть «профиль AWG3 + старая пара бинарей» = туннель не поднимется вовсе, а не деградирует.
Версия должна совпадать на обоих концах. Подключиться по 2.0 к старому пиру нельзя; в старом AmneziaVPN профиль 2.0 даже не покажется, хотя сервер его поддерживает (docs.amnezia.org). Это и есть инженерная причина "92 B in / 20 KB out": handshake собирается на общих параметрах и проходит, а data-пакеты пир дропает, потому что ждёт другой набор обфускации (например, на сервере включены S3/S4/I1, а в нашем конфиге их нет — или наоборот, мы их потеряли при парсинге, см. §3).
3. Формат .conf и наш парсер (core/awg_config.py)
API модуля: parse_conf(text) -> {"interface": {...}, "peers": [{...}]},
render_conf(cfg) -> text, validate(cfg) -> [errors], generate_keypair().
3.1 Какие поля куда идут
| Группа | Константа | Куда применяется |
|---|---|---|
| Крипто + ListenPort + FwMark + вся обфускация | WG_INTERFACE_FIELDS |
в демон через awg setconf |
Address, DNS, MTU, Table, PreUp, PostUp, PreDown, PostDown, SaveConfig |
WGQUICK_INTERFACE_FIELDS |
наша wg-quick-логика в _do_up (демон их НЕ видит) |
PublicKey, PresharedKey, AllowedIPs, Endpoint, PersistentKeepalive |
WG_PEER_FIELDS |
в демон через awg setconf |
WG_INTERFACE_FIELDS содержит: PrivateKey, ListenPort, FwMark, обфускацию v1
(Jc, Jmin, Jmax, S1, S2, H1, H2, H3, H4) и расширенную (S3, S4, I1–I5, J1–J3, Itime). Числовые валидируются как int (AWG_OBFUSCATION_FIELDS), а
H1–H4 — как int ИЛИ диапазон N-M (§2.2); hex-blob I1–I5, J1–J3 — отдельно
(AWG_V2_BLOB_FIELDS).
Голого поля
Iв AmneziaWG нет — signature-пакеты этоI1…I5(подтверждено парсером amneziawg-toolssrc/config.c:key_matchтолькоI1..I5). РаньшеIошибочно числился в наших списках как «v1»-параметр; убрано (см. CHANGELOG), чтобы случайныйI=не ушёл вawg setconf— иначе тулза отбросила бы весь конфиг. Расширенный набор у нас исторически назван «v2», хотя по docs.amnezia.orgJ1–J3/Itimeотносятся к 1.5 и удалены в 2.0 — мы их просто пропускаем как есть, решение принимает движок.
3.2 Парсинг hex-blob I1–I5 (многострочный <b …>) — частый баг
I1…I5 в .conf приходят как бинарный blob в одной из форм:
# однострочная
I1 = <b 0xf6ab...>
# многострочная (закрывается '>' или новым 'Key=' или пустой строкой)
I1 = <b
0xf6ab34c1...
9d2e...
>
parse_conf накапливает hex-куски (pending_*/flush_pending) и склеивает их.
Без этой обработки парсер брал только <b, и I1 терялся при
render_setconf → handshake проходил, а сервер дропал data → ровно «92 B in /
20 KB out». Если меняешь парсер — не сломай blob-склейку; тесты —
tests/test_awg_config.py.
3.3 Прочее
- Голым адресам без маски
parse_confдобавляет/32//128, чтобыawg/ipих приняли. _is_base64_keyпроверяет ключи: 44 символа, заканчивается=, декодится в 32 байта.validate()ругается на кривые ключи/Endpoint/AllowedIPs.render_confпишет полный.conf(для хранения/показа), а вawg setconfуходит отфильтрованный временный файл (только setconf-поля).
4. Инструменты AmneziaWG (CLI)
Форк wireguard-tools, CLI идентичен — просто awg вместо wg:
| Команда | Назначение | Используем мы? |
|---|---|---|
awg show [<iface>] [dump] |
статус: handshake, transfer, endpoint, allowed-ips | да (status/diagnostics; dump — таб-разделённый машинный вид) |
awg showconf <iface> |
дамп активного конфига демона | да (диагностика — что реально принято) |
awg setconf <iface> <file> |
залить конфиг в демон (replace) | да (основной путь конфигурации) |
awg syncconf <iface> <file> |
применить дельту без сброса peers | нет (мы делаем setconf при up) |
awg genkey / pubkey / genpsk |
генерация ключей | через наш generate_keypair (Python) |
awg set <iface> … |
точечная правка | нет |
awg-quick up/down <conf> |
bash-обёртка (Address/MTU/Table/routes/DNS) | НЕТ — реализуем сами (§1) |
amneziawg-go <iface> |
запустить userspace-демон + TUN | да (это и есть «движок») |
Альтернативы userspace-демону: модуль ядра amneziawg (DKMS /
amneziawg-linux-kernel-module). Мы ставим/детектим userspace amneziawg-go
(§7) — он не требует сборки под ядро роутера.
5. Жизненный цикл туннеля (awg_manager)
5.1 Подъём — _do_up
validate()конфига; если ошибки — не стартуем.PreUp-хуки.amneziawg-go <iface>(subprocess) — форкает демон + TUN. PID находим черезpgrep, пишем в<run_dir>/awg-<iface>.pid.- короткая пауза (UAPI-сокет
/var/run/wireguard/<iface>.sockдолжен подняться). awg setconf <iface> <filtered.conf>— крипто + обфускация.ip link set dev <iface> mtu <MTU>(если задан).ip addr add <Address>(каждый адрес).ip link set dev <iface> up.- маршруты из
AllowedIPs→table <id>(еслиTable != off).id— стабильный хэш имени интерфейса в диапазоне 100…999. - снимок состояния
<run_dir>/awg-<iface>.last_up.json+ список добавленных маршрутов<run_dir>/awg-<iface>.routes.json(чтобы корректно снять при down). PostUp-хуки →core.routing.applier.apply_all_on_interface_up().
5.2 Снятие — _do_down
Обратный порядок: PreDown → снять правила роутинга → удалить добавленные
маршруты → ip link down → ip link delete dev <iface> → SIGTERM (затем
SIGKILL) демону по PID → удалить pid-файл и UAPI-сокет → PostDown →
откат auto-dnsmasq (если это был последний AWG-интерфейс).
restart = down → up. status(name) парсит awg show <iface> dump
(per-peer: latest_handshake unix-ts, rx_bytes, tx_bytes).
6. Платформенные пути (awg_platform)
| Keenetic/Entware | OpenWrt | Generic Linux | |
|---|---|---|---|
| bin | /opt/usr/sbin |
/usr/sbin |
/usr/local/bin |
| config | /opt/etc/amneziawg |
/etc/amneziawg |
/etc/amneziawg |
| run | /opt/var/run/awg |
/var/run/awg |
/var/run/awg |
| init | /opt/etc/init.d |
/etc/init.d |
/etc/systemd/system |
Файлы в run_dir: awg-<iface>.pid, awg-<iface>.routes.json,
awg-<iface>.last_up.json.
UAPI-сокет демона лежит НЕ в нашем run_dir — путь задаёт сам движок:
| Бинарь | Каталог сокета |
|---|---|
amneziawg-go v3.x |
/var/run/amneziawg/<iface>.sock (ipc/uapi_unix.go: var socketDirectory = "/var/run/amneziawg") |
awg (tools v3.1.20260812) |
тот же: SOCK_PATH RUNSTATEDIR "/amneziawg/" (src/ipc-uapi-unix.h) |
унаследованный от wireguard-go (старые сборки v0.2.x) |
/var/run/wireguard/<iface>.sock |
Единый список каталогов — awg_platform.UAPI_SOCKET_DIRS (v3-путь первым);
platform.uapi_paths(iface) отдаёт все кандидаты, uapi_path(iface) —
существующий, иначе путь текущей ветки. Подчистка в
awg_manager._cleanup_iface() и поиск интерфейсов в awg_detector обходят
оба каталога: на роутере вполне может стоять и старый amneziawg-go.
Раньше оба места были захардкожены на
/var/run/wireguard/, и с бинарями v3 сокет после SIGKILL оставался лежать, а обнаружение интерфейсов по сокетам не находило ничего. На путь ЗАПУСКА это не влияло и не влияет —_do_upсокет намеренно НЕ ждёт (был регресс, ронявший AWG на Keenetic, см. комментарий в_do_up). Сторож —TestUapiSocketDirsвtests/test_awg_manager_lifecycle.py.
awg_detector.CONFIG_DIR_CANDIDATES ищет чужие конфиги и в
/opt/etc/amnezia/{amneziawg,awg}, /opt/etc/AmneziaWG, */wireguard и т.д. —
импорт из существующих установок Amnezia.
7. Установка и детект (awg_installer / awg_detector)
- Что ставим:
amneziawg-go(userspace-демон) и утилитуawg(при отсутствии — fallback наwg). Источник — GitHub-релизы (манифест черезapi/awg/manifest). - Архитектура (
detect_architecture()): сначалаopkg print-architecture(берём приоритетный не-all/не-noarch), fallback —uname -m+/proc. Артефактные архи:mipsel-softfloat,mips-softfloat,aarch64,armv7,x86_64. Endianness MIPS определяется поsys.byteorder(т.к.uname -mна mips неоднозначен). - Версия бинаря —
<bin> --version/-v, regexv?(\d+(\.\d+){1,3}…); fallbackopkg status <pkg>для Entware. - Битый бинарь (
_probe_binary): ловимexec format error,cannot execute binary,syntax error— типичная ситуация при неверной арх/endianness.
8. Cloudflare WARP и WARP-in-WARP
8.1 Генерация WARP (warp_generator.py)
- Регистрация:
POST https://api.cloudflareclient.com/v0a2483/reg(генерим пару ключей, регистрируем публичный, получаем peer/endpoint). - Дефолты: endpoint
engage.cloudflareclient.com:2408, peer-pubkeybmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=. - Обфускация для WARP (vanilla-WG-совместимая):
Jc 4..12,Jmin=40,Jmax=70,S1=S2=0,H1=1, H2=2, H3=3, H4=4. Cloudflare-сервер — обычный WireGuard, поэтому единственная допустимая обфускация — junk-пакеты (отдельные UDP-датаграммы перед handshake, которые пир игнорирует). ПаддингS1..S4>0и нестандартныеH1..H4ломают разбор пакета у Cloudflare → туннель не поднимается (это была реальная регрессия: генератор ставил рандомныеH*иS1/S2 15..100, и «чистый WARP» не работал — теперь только junk). Готовые amnezia-wg-WARP конфиги «в дикой природе» используют ровно этот профиль (H=1,2,3,4,S=0,Jc>0, опц. signature-I1).
8.2 Импорт WARP (warp_importer.py)
Распознаёт готовый .conf как WARP по: endpoint в диапазонах Cloudflare
(162.159.192.0/24, 162.159.193.0/24, …), AllowedIPs = 0.0.0.0/0/::/0,
наличию AWG-обфускации.
8.3 WARP-in-WARP (двойной туннель, awg_warp_in_warp.py)
Состояние в config["awg"]["warp_in_warp"]. Подъём:
- Резолв endpoint внутреннего туннеля ДО подъёма (пока маршрут «наружу» ещё прямой).
- поднять внешний туннель (если не запущен).
- добавить
/32(или/128) маршрут к IP внутреннего endpoint через внешний интерфейс (чтобы внутренний handshake пошёл сквозь внешний). - поднять внутренний туннель.
- сохранить флаги «кого подняли мы» (чтобы при teardown не уронить чужое).
Статус = оба туннеля живы + маршрут к inner-endpoint существует + handshake свежий.
9. Нативный WireGuard Keenetic (NDMS) — core/ndms/wg_discovery.py
На Keenetic есть свой WireGuard (Wireguard0, Wireguard1, …), которым
рулит прошивка через NDMS/RCI, а не наш amneziawg-go.
wg_discoveryперечисляет такие интерфейсы через RCI (имя, описание, state, address,type: wireguard,source: ndms), кэш 30 c.awg_manager.list_interfaces()показывает их рядом с нашими userspace-AWG.- Для NDMS-нативных интерфейсов
status()делегирует чтение в NDMS (should_delegate_monitoring()), а не дёргаетawg show— иначе получим пусто (демон-то не наш).
Не пытайся поднимать/опускать
WireguardNчерезip/awg— это сломает состояние прошивки. Управление ими — через NDMS-команды (core/ndms/).
10. Watchdog и автозапуск
Watchdog (awg_watchdog.py) — перезапуск при «протухшем» handshake:
handshake_timeout_sec=180, check_interval_sec=30, cooldown_sec=300,
max_restarts_per_hour=6, опциональный TCP-probe (host:port сквозь туннель).
decide_restart(): рестарт, если возраст handshake ≥ timeout или probe
падает N раз; учитывает per-iface cooldown и rate-limit.
Autostart (awg_autostart_manager + awg_init_script): флаг на конфиг в
config["awg"]["autostart"]. Init-скрипт:
- Entware:
/opt/etc/init.d/S51amneziawg-gui(start|stop|restart); - OpenWrt: procd (
start_service/stop_service); - systemd:
awg-gui.service.
Все вызывают python3 app.py --apply-awg-autostart / --stop-awg-autostart
(поднимает включённые конфиги + восстанавливает WARP-in-WARP).
11. API (api/awg.py)
| Метод | Путь | Назначение |
|---|---|---|
| GET | /api/awg/environment |
отчёт детектора (платформа, арх, TUN, бинари) |
| POST | /api/awg/environment/refresh |
пере-сканировать |
| GET | /api/awg/manifest |
манифест GitHub-релизов |
| POST | /api/awg/install, /api/awg/uninstall |
бинари |
| GET | /api/awg/configs |
список конфигов |
| GET/PUT/DELETE | /api/awg/configs/<name> |
CRUD конфига |
| POST | /api/awg/configs/<name>/up|down|restart |
жизненный цикл |
| GET | /api/awg/configs/<name>/status |
статус интерфейса |
| POST | /api/awg/warp/import, /api/awg/warp/generate |
WARP |
| GET/POST | /api/awg/warp-in-warp |
двойной туннель |
| GET/POST | /api/awg/watchdog |
настройки watchdog |
| GET/POST | /api/awg/autostart |
автозапуск |
| POST | /api/awg/subscription/import|preview |
импорт подписки |
12. Диагностика «не работает» (чек-лист)
awg show <iface>— есть ли peer и свежийlatest handshake?- нет handshake вообще → не туда
Endpoint/PublicKey, фаервол/маршрут до endpoint (на Keenetic — проверь, что трафик к endpoint не заворачивается в сам туннель; для WARP-in-WARP нужен/32-маршрут, §8.3), не тотListenPort, либо H1–H4/обфускация не совпадает с пиром (DPI/сервер дропают и сам handshake). - handshake есть, но
transfer: ~92 B received, ~20 KB sent→ классика рассинхрона версии/параметров обфускации (§2.3). Мы шлём data, сервер молчит. Проверь: a) совпадают лиS1–S4,H1–H4,I1–I5,Jc/Jmin/Jmaxс серверным конфигом (версия 1.0/1.5/2.0 одинаковая!); b) не потерялся лиI1при парсинге (§3.2) — сравниawg showconf <iface>с исходным.conf; вawg_managerесть_compute_i1_lengths(), который сверяет длину blob из конфига с тем, что эхо-итawg show(несовпадение = blob недопарсился или демон старый, не понимает 2.0).
- нет handshake вообще → не туда
awg showconf <iface>— реально ли в демоне вашиS3/S4/I1/H*? Если их там нет — они не дошли черезsetconf(фильтрацияWG_INTERFACE_FIELDSили баг парсера) или бинарь старый и игнорирует новые поля.- версия бинаря
amneziawg-go --version/awg --version— поддерживает ли он поля своего поколения?S3/S4и range-H*— это 2.0; AWG3-поля (HeaderProtectionKey,ContentPaddingAddition, тайминги, §2.1.1) — только веткаv3.x, аRandomTrailers/DisableCookies(§2.1.2) — толькоv3.1.20260814и новее. Старый демон молча работает в 1.x, а старые tools на незнакомом ключе печатаютLine unrecognizedи отбрасывают весь конфиг — разные симптомы, не перепутай. Нашrender_setconfAWG3-поля пропускает (§2.1.1), так что их отсутствие вawg showconfна свежем бинаре — это про сам бинарь, а не про фильтрацию у нас. - MTU/фрагментация —
Jmax/S*не должны раздувать пакет за MTU; дефолтMTU=1420. Симптом: handshake ок, мелкое работает, крупное/TLS виснет. - битый бинарь (неверная арх/endianness MIPS) —
_probe_binary, «exec format error». Переустановить под верную арх (§7). awg_manager.diagnostics()даёт полный снимок: бинари, конфиг (privkey замаскирован),awg show,ip rule/ip route, маршрут до каждого endpoint, состояние фаервола/dnsmasq.- Keenetic native WG не управляется нами — статус берётся из NDMS (§9), не
из
awg show.
13. Layout (где что)
- Парсер/генератор
.conf, ключи:core/awg_config.py. - Жизненный цикл, статус, диагностика:
core/awg_manager.py. - Пути/раскладка:
core/awg_platform.py. - Установка/детект бинарей и арх:
core/awg_installer.py,core/awg_detector.py. - WARP:
core/warp_generator.py,core/warp_importer.py,core/awg_warp_in_warp.py. - Нативный WG Keenetic:
core/ndms/wg_discovery.py. - Watchdog/автозапуск:
core/awg_watchdog.py,core/awg_autostart_manager.py,core/awg_init_script.py,core/awg_keenetic_setup.py. - API:
api/awg.py. UI:web/js/pages/awg_{setup,dashboard,configs,routing,warp}.js. - Тесты:
tests/test_awg_*.py,tests/test_api_awg.py.