Уточнения к разбору AmneziaWG 3.0: набор тегов CPS расходится между реализациями, плюс обновления после 4 августа #1

Open
opened 2026-08-17 20:24:59 +03:00 by ivan · 3 comments

Здравствуйте.

Я мейнтейнер amneziawg-installer. Сверил ваш разбор третьего поколения с исходниками и хочу предложить четыре уточнения. Первое относится к самому разбору, два следующих - обновления после 4 августа (на вашу дату всё было верно), последнее - мой замер, дополняющий ваше наблюдение по коду.

1. Набор тегов CPS расходится между реализациями

В internals сказано, что в коде зарегистрировано восемь тегов: <b>, <r>, <rc>, <rd>, <t> плюс недокументированные <d>, <ds>, <dz>. Это верно для amneziawg-go, но у модуля ядра набор другой, и расходятся они в обе стороны:

Тег Модуль ядра src/junk.c amneziawg-go device/obf.go
<b>, <r>, <rc>, <rd>, <t> есть есть
<c> (счётчик пакетов пира, u32 BE) есть нет в парсере
<d>, <ds>, <dz> нет, возвращает -EINVAL есть

Где смотреть: в модуле - jp_parse_tags() в src/junk.c, таблица через strcmp с финальным else return -EINVAL; тег c разбирается в parse_c_tag, а пишет счётчик уже функция-модификатор pkt_counter_modifier. В go - map obfBuilders и функция newObfChain лежат в device/obf.go, а вызывается она из device/uapi.go, отдельной строкой на каждый из i1-i5.

Практическое следствие: конфиг с <c> работает на ноде с модулем ядра и не поднимается на userspace, то есть в LXC и Docker, на роутерах, на macOS. И наоборот, <d>/<ds>/<dz> модуль ядра отвергает целиком. Отказ в обоих случаях жёсткий, но заметен по-разному. go возвращает наружу готовую строку failed to parse I1: unknown tag <c>. Модуль ядра возвращает -EINVAL, а поясняющую строку I1-packet invalid format печатает через net_dbg_ratelimited(), то есть в dmesg она видна только при включённом dynamic debug для модуля. Без него человек получает голый отказ без причины - и это, возможно, объясняет часть жалоб вида «интерфейс просто не поднимается, в логе пусто».

Отсюда правило, которое я держу у себя: любой рецепт I1, предлагаемый людям, должен использовать только пересечение {b, r, rc, rd, t}.

2. Вышла 3.1, PPA раздаёт её

12 августа внутри третьей линии вышел v3.1.20260812 с двумя новыми параметрами устройства:

  • RandomTrailers - случайный хвост к пакетам рукопожатия. Двусторонний: приёмник без флага удлинённый пакет не опознаёт, поэтому включение только на одной стороне рвёт рукопожатие. В amneziawg-go (device/receive.go) гейт стоит на init, response и cookie, а транспорт проверяется как size >= expectedSize уже без флага; в модуле ядра я этот механизм не проверял, но вывод про двусторонность от этого не меняется.
  • DisableCookies - сервер перестаёт слать cookie-ответы. Односторонний.

WG_GENL_VERSION не менялся, то есть утилиты 3.0 работают с модулем 3.1. PPA раздаёт эту сборку с 12 августа; на 17 августа там amneziawg-dkms 1.0.0-0~202608140352+4680320, где 4680320 - сокращённый хеш коммита 46803204e7ec, помеченного тегом v3.1.20260812.

К вашему чек-листу про ревизию модуля: версию загруженного модуля показывает cat /sys/module/amneziawg/version. Версия apt-пакета для этого не годится - она начинается с 1.0.0 (это версия упаковки из dkms.conf), и линию видно только по хешу в конце строки.

3. PR 2908 в amnezia-client смержен

В reference сказано, что поддержка self-hosted живёт в незакрытом PR #2908. Он смержен 6 августа: 9 коммитов, 40 файлов. Но смержено не выпущено: последний релиз приложения - 5.0.0.5 от 26 июля, то есть вышел раньше мержа.

4. Паддинг keepalive: побочный эффект, который я воспроизвёл

Вы отметили, что keepalive получает S4 и content padding. У этого поведения есть побочный эффект.

При ненулевом S4 keepalive классифицируется как пакет с данными, и это сбрасывает таймер, отвечающий за паузу перед новым рукопожатием. На простаивающем туннеле частота рукопожатий вырастает примерно в десять раз: на стенде медиана интервала составила 15,0 с против 147,0 с у контроля, причинность доказана A/B/A с однострочным патчем.

Границы замера: воспроизведено на модуле ядра. В проверенных версиях amneziawg-go дефект дал те же 15,0 с на v3.0.3, но не воспроизвёлся на v0.2.19, где медиана составила 132,0 с; линию 3.1 этим замером я не проверял. Для модуля ядра дефект разобран в issue #186 и исправлен PR #208, я прогнал фикс на стенде 6 августа - медиана вернулась к 147,0 с.

Что из этого следует для вашего текста: паддинг убирает постоянную длину keepalive, но в измеренном сценарии простаивающего туннеля дефект примерно десятикратно увеличивал частоту рукопожатий, а перед каждым уходят Jc мусорных и I1-I5 сигнатурных пакетов. Влияние этого на обнаруживаемость я отдельно не измерял.


Если понадобятся методика и сырые результаты замера по последнему пункту, приложу их сюда.

Здравствуйте. Я мейнтейнер `amneziawg-installer`. Сверил ваш разбор третьего поколения с исходниками и хочу предложить четыре уточнения. Первое относится к самому разбору, два следующих - обновления после 4 августа (на вашу дату всё было верно), последнее - мой замер, дополняющий ваше наблюдение по коду. ### 1. Набор тегов CPS расходится между реализациями В `internals` сказано, что в коде зарегистрировано восемь тегов: `<b>`, `<r>`, `<rc>`, `<rd>`, `<t>` плюс недокументированные `<d>`, `<ds>`, `<dz>`. Это верно для `amneziawg-go`, но у модуля ядра набор другой, и расходятся они в обе стороны: | Тег | Модуль ядра `src/junk.c` | amneziawg-go `device/obf.go` | |---|:--:|:--:| | `<b>`, `<r>`, `<rc>`, `<rd>`, `<t>` | есть | есть | | `<c>` (счётчик пакетов пира, u32 BE) | **есть** | **нет в парсере** | | `<d>`, `<ds>`, `<dz>` | **нет, возвращает `-EINVAL`** | есть | Где смотреть: в модуле - `jp_parse_tags()` в `src/junk.c`, таблица через `strcmp` с финальным `else return -EINVAL`; тег `c` разбирается в `parse_c_tag`, а пишет счётчик уже функция-модификатор `pkt_counter_modifier`. В go - map `obfBuilders` и функция `newObfChain` лежат в `device/obf.go`, а вызывается она из `device/uapi.go`, отдельной строкой на каждый из `i1`-`i5`. Практическое следствие: конфиг с `<c>` работает на ноде с модулем ядра и не поднимается на userspace, то есть в LXC и Docker, на роутерах, на macOS. И наоборот, `<d>`/`<ds>`/`<dz>` модуль ядра отвергает целиком. Отказ в обоих случаях жёсткий, но заметен по-разному. go возвращает наружу готовую строку `failed to parse I1: unknown tag <c>`. Модуль ядра возвращает `-EINVAL`, а поясняющую строку `I1-packet invalid format` печатает через `net_dbg_ratelimited()`, то есть в `dmesg` она видна только при включённом dynamic debug для модуля. Без него человек получает голый отказ без причины - и это, возможно, объясняет часть жалоб вида «интерфейс просто не поднимается, в логе пусто». Отсюда правило, которое я держу у себя: любой рецепт `I1`, предлагаемый людям, должен использовать только пересечение `{b, r, rc, rd, t}`. ### 2. Вышла 3.1, PPA раздаёт её 12 августа внутри третьей линии вышел `v3.1.20260812` с двумя новыми параметрами устройства: - `RandomTrailers` - случайный хвост к пакетам рукопожатия. Двусторонний: приёмник без флага удлинённый пакет не опознаёт, поэтому включение только на одной стороне рвёт рукопожатие. В `amneziawg-go` (`device/receive.go`) гейт стоит на init, response и cookie, а транспорт проверяется как `size >= expectedSize` уже без флага; в модуле ядра я этот механизм не проверял, но вывод про двусторонность от этого не меняется. - `DisableCookies` - сервер перестаёт слать cookie-ответы. Односторонний. `WG_GENL_VERSION` не менялся, то есть утилиты 3.0 работают с модулем 3.1. PPA раздаёт эту сборку с 12 августа; на 17 августа там `amneziawg-dkms 1.0.0-0~202608140352+4680320`, где `4680320` - сокращённый хеш коммита `46803204e7ec`, помеченного тегом `v3.1.20260812`. К вашему чек-листу про ревизию модуля: версию загруженного модуля показывает `cat /sys/module/amneziawg/version`. Версия apt-пакета для этого не годится - она начинается с `1.0.0` (это версия упаковки из `dkms.conf`), и линию видно только по хешу в конце строки. ### 3. PR 2908 в amnezia-client смержен В `reference` сказано, что поддержка self-hosted живёт в незакрытом PR [#2908](https://github.com/amnezia-vpn/amnezia-client/pull/2908). Он смержен 6 августа: 9 коммитов, 40 файлов. Но смержено не выпущено: последний релиз приложения - 5.0.0.5 от 26 июля, то есть вышел раньше мержа. ### 4. Паддинг keepalive: побочный эффект, который я воспроизвёл Вы отметили, что keepalive получает `S4` и content padding. У этого поведения есть побочный эффект. При ненулевом `S4` keepalive классифицируется как пакет с данными, и это сбрасывает таймер, отвечающий за паузу перед новым рукопожатием. На простаивающем туннеле частота рукопожатий вырастает примерно в десять раз: на стенде медиана интервала составила 15,0 с против 147,0 с у контроля, причинность доказана A/B/A с однострочным патчем. Границы замера: воспроизведено на модуле ядра. В проверенных версиях `amneziawg-go` дефект дал те же 15,0 с на `v3.0.3`, но не воспроизвёлся на `v0.2.19`, где медиана составила 132,0 с; линию 3.1 этим замером я не проверял. Для модуля ядра дефект разобран в [issue #186](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/186) и исправлен [PR #208](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/pull/208), я прогнал фикс на стенде 6 августа - медиана вернулась к 147,0 с. Что из этого следует для вашего текста: паддинг убирает постоянную длину keepalive, но в измеренном сценарии простаивающего туннеля дефект примерно десятикратно увеличивал частоту рукопожатий, а перед каждым уходят `Jc` мусорных и `I1`-`I5` сигнатурных пакетов. Влияние этого на обнаруживаемость я отдельно не измерял. --- Если понадобятся методика и сырые результаты замера по последнему пункту, приложу их сюда.
Owner

Спасибо исправили. Присылайте больше фактов будем только рады.

Спасибо исправили. Присылайте больше фактов будем только рады.
Author

Спасибо, что учли. Тогда одно уточнение к разбору тегов и пара наблюдений из поля.

Про LXC и Docker. У вас сказано, что конфигурация с <c> не поднимется на userspace-движке, «то есть в LXC и Docker, на роутерах, на macOS». С движками так и есть, а вот привязка к типу узла держится не всегда: в контейнере реализацию выбирает хост.

awg-quick из amneziawg-tools сначала пробует ядро и уходит в userspace только при неудаче - функция add_if() в src/wg-quick/linux.bash. Причём условие там написано так, что при существующем /sys/module/amneziawg отката не будет вовсе, команда просто упадёт. А WG_QUICK_USERSPACE_IMPLEMENTATION проверяется внутри той же ветки отката, так что штатного способа удержать контейнер на go, когда модуль на хосте есть, нет.

Модуль хоста контейнеру при этом виден: run_container.sh в amnezia-client запускает его с --privileged --cap-add=SYS_MODULE и монтирует /lib/modules. Образ собран от amneziavpn/amneziawg-go, а скрипты подготовки хоста модуль не ставят - потому он и живёт на go, пока модуля нет. А появится amneziawg-dkms - и тот же контейнер с нетронутой конфигурацией меняет допустимый набор тегов на противоположный. Поставить пакет при этом могло что угодно рядом, к контейнеру отношения не имеющее.

Наткнулся я на это, разбирая жалобу пользователя моего установщика: у него перестал принимать подключения контейнер AWG 2.0, а соседний, на обычном WireGuard, работал как ни в чём не бывало. Диагноз по исходникам. На стенде не воспроизводил - для чистого «до» нужен хост без модуля, а у меня он уже стоял.

Теперь наблюдения. Оба про 2.0, по третьему поколению отчётов у меня нет. Но для раздела про диагностику, наверное, сгодятся: в обоих случаях человек описывал это как «не работает на операторе X», а картина была разная.

26 июня, МТС. На 39743/udp туннель не поднимался, на 443/udp встал сразу - тот же конфиг, тот же сервер. Для контроля: на трёх домашних провайдерах в Москве этот же сервер работал на исходном порту, и потери в туннеле были как у обычного UDP до того же адреса. Почему так, не знаю: проверял только сам факт. Тогда же ещё один человек писал про похожее на МТС: не поднимается, пресет mobile не спасает. Его порт и чем всё кончилось, мне неизвестно.

6 августа, другой человек, тоже сотовая. Порт не трогал вообще, помогла смена IP: попросил у хостера другой адрес, подставил его на сервере и в клиентах, заработало и на сотовой, и дома. Он же проверил, что VPS в той же стране у другого хостера поднимается из коробки. Менялся, получается, адрес и всё, что с ним связано, а порт и обфускация оставались теми же; одной страной это не объяснить. Если повторится уже на новом адресе, стоит просить у хостера адрес из другой подсети, а не соседний из того же пула.

Там же нашлась деталь, из-за которой я и пишу этот пункт: вместе с туннелем не отвечали SSH и пинг до того же адреса. Значит дело было в самом адресе или маршруте до него, а не в AmneziaWG. Блокировка, фильтр у хостера, упавшая машина, маршрутизация - не разбирался.

С тех пор это у меня первый шаг: пока не проверил сам адрес, в том числе из другой сети, крутить обфускацию и порт рано. Если недоступен весь адрес, конфигурация не поможет - как раз то, про что у вас написано, что смена протокола такое не лечит.

Понадобятся подробности по любому из двух - скажите, приложу.

Ivan Bondarev (bivlked)

Спасибо, что учли. Тогда одно уточнение к разбору тегов и пара наблюдений из поля. **Про LXC и Docker.** У вас сказано, что конфигурация с `<c>` не поднимется на userspace-движке, «то есть в LXC и Docker, на роутерах, на macOS». С движками так и есть, а вот привязка к типу узла держится не всегда: в контейнере реализацию выбирает хост. `awg-quick` из `amneziawg-tools` сначала пробует ядро и уходит в userspace только при неудаче - функция `add_if()` в `src/wg-quick/linux.bash`. Причём условие там написано так, что при существующем `/sys/module/amneziawg` отката не будет вовсе, команда просто упадёт. А `WG_QUICK_USERSPACE_IMPLEMENTATION` проверяется внутри той же ветки отката, так что штатного способа удержать контейнер на go, когда модуль на хосте есть, нет. Модуль хоста контейнеру при этом виден: `run_container.sh` в `amnezia-client` запускает его с `--privileged --cap-add=SYS_MODULE` и монтирует `/lib/modules`. Образ собран от `amneziavpn/amneziawg-go`, а скрипты подготовки хоста модуль не ставят - потому он и живёт на go, пока модуля нет. А появится `amneziawg-dkms` - и тот же контейнер с нетронутой конфигурацией меняет допустимый набор тегов на противоположный. Поставить пакет при этом могло что угодно рядом, к контейнеру отношения не имеющее. Наткнулся я на это, разбирая жалобу пользователя моего установщика: у него перестал принимать подключения контейнер AWG 2.0, а соседний, на обычном WireGuard, работал как ни в чём не бывало. Диагноз по исходникам. На стенде не воспроизводил - для чистого «до» нужен хост без модуля, а у меня он уже стоял. **Теперь наблюдения.** Оба про 2.0, по третьему поколению отчётов у меня нет. Но для раздела про диагностику, наверное, сгодятся: в обоих случаях человек описывал это как «не работает на операторе X», а картина была разная. 26 июня, МТС. На `39743/udp` туннель не поднимался, на `443/udp` встал сразу - тот же конфиг, тот же сервер. Для контроля: на трёх домашних провайдерах в Москве этот же сервер работал на исходном порту, и потери в туннеле были как у обычного UDP до того же адреса. Почему так, не знаю: проверял только сам факт. Тогда же ещё один человек писал про похожее на МТС: не поднимается, пресет `mobile` не спасает. Его порт и чем всё кончилось, мне неизвестно. 6 августа, другой человек, тоже сотовая. Порт не трогал вообще, помогла смена IP: попросил у хостера другой адрес, подставил его на сервере и в клиентах, заработало и на сотовой, и дома. Он же проверил, что VPS в той же стране у другого хостера поднимается из коробки. Менялся, получается, адрес и всё, что с ним связано, а порт и обфускация оставались теми же; одной страной это не объяснить. Если повторится уже на новом адресе, стоит просить у хостера адрес из другой подсети, а не соседний из того же пула. Там же нашлась деталь, из-за которой я и пишу этот пункт: вместе с туннелем не отвечали SSH и пинг до того же адреса. Значит дело было в самом адресе или маршруте до него, а не в AmneziaWG. Блокировка, фильтр у хостера, упавшая машина, маршрутизация - не разбирался. С тех пор это у меня первый шаг: пока не проверил сам адрес, в том числе из другой сети, крутить обфускацию и порт рано. Если недоступен весь адрес, конфигурация не поможет - как раз то, про что у вас написано, что смена протокола такое не лечит. Понадобятся подробности по любому из двух - скажите, приложу. Ivan Bondarev (bivlked)
Owner

Переключение не мгновенное а так исправили.

Переключение не мгновенное а так исправили.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
zapretdiscordyoutube/todo#1
No description provided.