# 🔬 AmneziaWG 3.0: внутреннее устройство протокола > [!info] О чём заметка > Побайтовый разбор третьего поколения AmneziaWG по исходному коду: как собирается пакет, что именно шифрует защита заголовков, откуда берётся одноразовое число, как приёмная сторона опознаёт тип сообщения, не расшифровав его целиком, и какие следы протокол всё ещё оставляет в сети. Обзорная часть — назначение, доступность, история версий — в заметке [[amnezia-3-0/reference|AmneziaWG 3.0]]; параметры предыдущего поколения (`Jc`, `S1`–`S4`, `H1`–`H4`, язык CPS) подробно разобраны в [[amnezia-2-0/reference|справочнике по AmneziaWG 2.0]] и здесь не повторяются. ## TL;DR - Третье поколение добавляет к обфускации ровно три механизма: **защита заголовков** (ChaCha20 поверх готовых сообщений WireGuard), **content padding** (случайное удлинение полезной нагрузки вместо выравнивания по 16 байт) и **диапазонные тайминги** (новое случайное значение при каждом взводе таймера). - Одноразовое число (nonce) для шифра **не передаётся отдельно** — им служат первые 12 байт того самого случайного паддинга `S1`–`S4`, который в 2.0 был просто мусором. Отсюда требование `S1`–`S4` ≥ 12 при включённой защите. - Приёмная сторона использует изящный приём: шифрует блок нулей, получая первые 4 байта потока ключа, и этим «хэшем типа» расшифровывает только поле типа — чтобы опознать сообщение до разбора остального пакета. - **Тихое улучшение, не отмеченное нигде в документации**: keepalive-пакеты теперь получают и паддинг `S4`, и content padding. В [[amnezia-2-0/reference|AWG 2.0]] они шли без `S4` и имели постоянный размер 32 байта — это была заметная сигнатура. - **Что 3.0 не закрывает**: размеры рукопожатий остаются постоянными для конкретной конфигурации (`S1`+148 и `S2`+92 байта), потому что content padding применяется только к транспортным пакетам. Пара «фиксированный запрос → фиксированный ответ → поток» по-прежнему видна наблюдателю. ## Как проверялись факты Всё ниже прочитано в исходниках движка [amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go) на теге **`v3.0.3`** (коммит `cf9d2dd`, 31 июля 2026 года) — это последняя на 4 августа 2026 года версия третьего поколения. Ключевые файлы: `device/noise-types.go` (константы и тип диапазона), `device/noise-protocol.go` (выдача шифра), `device/send.go` (сборка исходящих пакетов), `device/receive.go` (разбор входящих), `device/uapi.go` (параметры и их проверка), `device/timers.go` (тайминги). > [!warning] Первоисточник здесь — код, а не документация > Официального описания третьего поколения не существует: раздел документации Amnezia описывает версии 1.5 и 2.0, обещанная статья в блоге на 4 августа 2026 года не вышла. README репозитория покрывает список параметров, но не механику. Поэтому разбор ниже — чтение исходников, и любые расхождения с будущей официальной документацией следует трактовать в её пользу. Отдельно предупреждение о ходящем по сети «разборе под капотом» из GitHub Discussions: значительная часть его утверждений кодом не подтверждается, подробности — в [[amnezia-3-0/reference|обзорной заметке]]. ## Полный список параметров устройства Третье поколение AmneziaWG (*AWG 3, «амнезия 3.0», «АмнезияВГ 3»*) не переизобретает конфигурацию, а дописывает к ней восемь новых ключей. Полная картина того, что понимает движок `v3.0.3` (имена в конфигурационном файле и соответствующие им ключи внутреннего интерфейса UAPI, через который утилиты общаются с движком): | В конфиге | Ключ UAPI | Тип | Появился | Сторона | |---|---|---|---|---| | `Jc`, `Jmin`, `Jmax` | `jc`, `jmin`, `jmax` | int | 1.0 | клиентская | | `S1`–`S4` | `s1`–`s4` | int | 1.0 (`S3`, `S4` — в 2.0) | серверная | | `H1`–`H4` | `h1`–`h4` | диапазон uint32 | 1.0 (диапазоны — в 2.0) | серверная | | `I1`–`I5` | `i1`–`i5` | строка на языке CPS | 1.5 | клиентская | | **`HeaderProtectionKey`** | `header_protection_key` | 32 байта, base64 | **3.0** | серверная | | **`ContentPaddingAddition`** | `content_padding_addition` | диапазон uint32 | **3.0** | клиентская | | **`RekeyAfterTime`** | `rekey_after_time` | диапазон uint32, секунды | **3.0** | клиентская | | **`RekeyTimeout`** | `rekey_timeout` | диапазон uint32, секунды | **3.0** | клиентская | | **`RejectAfterTime`** | `reject_after_time` | диапазон uint32, секунды | **3.0** | клиентская | | **`KeepaliveTimeout`** | `keepalive_timeout` | диапазон uint32, секунды | **3.0** | клиентская | | **`MaxHandshakeAttempts`** | `max_handshake_attempts` | диапазон uint32, попытки | **3.0** | клиентская | | `PersistentKeepalive` | `persistent_keepalive_interval` | стал диапазоном | изменён в **3.0** | клиентская | Разделение на «стороны» в терминологии README означает буквально следующее: **серверные** параметры обязаны совпадать на обоих концах туннеля, иначе стороны не поймут друг друга; **клиентские** можно задавать только на одной стороне — они влияют на то, как эта сторона отправляет, и не требуют согласования. Проще говоря: ключ защиты заголовков и значения паддинга должны быть одинаковыми у клиента и сервера, а мусорные пакеты, content padding и тайминги каждый настраивает под себя. Формат диапазона — `a-b`, либо одиночное число (тогда границы совпадают), либо `(off)`. Разбирается он в тип `UintRange`, где обе границы упакованы в одно 64-битное число: младшие 32 бита — нижняя граница, старшие — верхняя. Такая упаковка позволяет менять диапазон атомарно, без блокировок, прямо во время работы туннеля. ## Слои обфускации: как собирается исходящий пакет Полезно держать в голове порядок, в котором данные обрастают слоями. Для транспортного пакета он такой: 1. **Прикладные данные** приходят из виртуального сетевого интерфейса. 2. **Content padding** — в хвост дописываются нулевые байты: случайное количество из диапазона `ContentPaddingAddition`, а если параметр не задан — ровно столько, чтобы длина стала кратна 16 (штатное поведение WireGuard). 3. **Шифрование полезной нагрузки** — ChaCha20-Poly1305 сеансовым ключом, штатный механизм WireGuard. На выходе получается шифртекст плюс 16-байтовая метка подлинности. 4. **Заголовок WireGuard** — 16 байт: тип сообщения (значение из диапазона `H4`), индекс получателя, счётчик пакетов. 5. **Криптопаддинг `S4`** — перед заголовком в буфере лежат `S4` случайных байт. 6. **Защита заголовков** — 16 байт заголовка шифруются ChaCha20 с одноразовым числом из первых 12 байт паддинга. Ключевая перестановка относительно [[amnezia-2-0/reference|второго поколения]] — в шаге 5. Раньше паддинг `S4` дописывался в самом конце, сдвигом уже готового зашифрованного пакета вправо по буферу. Теперь место под него резервируется заранее (`elem.padding` выставляется при создании исходящего элемента), и заполняется он до шифрования заголовка — иначе неоткуда было бы взять одноразовое число. Для рукопожатия порядок другой и полностью сохраняет логику 2.0: сначала уходят сигнатурные пакеты `I1`–`I5` (каждый — отдельная датаграмма), затем `Jc` мусорных пакетов, и лишь потом само сообщение инициации. Всё это отправляется одним системным вызовом. ## Header protection побайтово ### Ключ Параметр `HeaderProtectionKey` — 32 байта, в конфигурационном файле записывается в base64, как обычный ключ WireGuard; шестнадцатеричная форма используется только на внутреннем интерфейсе UAPI. Генерируется командой `awg genkey`. Ключ хранится в устройстве под отдельной блокировкой чтения-записи и **живёт всё время работы интерфейса**: он не участвует в выработке сеансовых ключей и не меняется при обновлении сессии каждые пару минут. Это осознанный размен — заголовки нужно уметь расшифровать до того, как станет понятно, к какой сессии относится пакет. Если ключ нулевой, функция выдачи шифра возвращает пустое значение, и весь механизм отключается — движок ведёт себя ровно как AWG 2.0. Именно поэтому «несовместимость с 2.0» на самом деле означает «несовместимость конфигураций с включённой защитой заголовков»: сам движок умеет работать в обоих режимах. ### Одноразовое число Шифр — ChaCha20 в варианте IETF, без аутентификации (`chacha20.NewUnauthenticatedCipher`). Его одноразовое число занимает 12 байт, и берётся оно не из отдельного поля, а **из начала криптопаддинга**: ``` Отправка (сообщение инициации): buf = [ S1 байт ] ← crypto/rand.Read по всей длине [ 148 байт сообщения ] nonce = buf[:12] ← первые 12 байт паддинга cip = ChaCha20(key, nonce) cip.XORKeyStream(packet, packet) ← шифруется всё сообщение целиком ``` Отсюда единственное жёсткое ограничение третьего поколения: при непустом ключе **все четыре значения `S1`–`S4` должны быть не меньше 12**, иначе одноразовое число просто не поместится. Проверка стоит в обработчике настроек и отвергает всю конфигурацию целиком. Обратите внимание на побочный эффект: `S4` ≥ 12 означает, что 12 с лишним лишних байт добавляются **к каждому транспортному пакету**, а не только к рукопожатиям. Отключить паддинг для потока данных, сохранив защиту заголовков, нельзя. ### Что именно шифруется | Тип сообщения | Размер | Что покрывает шифр | |---|---|---| | Инициация рукопожатия | 148 байт | всё сообщение, включая MAC1 и MAC2 | | Ответ на рукопожатие | 92 байта | всё сообщение целиком | | Ответ с cookie | 64 байта | всё сообщение целиком | | Транспортный пакет | 16 байт заголовка | только заголовок: тип, индекс получателя, счётчик | Полезная нагрузка транспортных пакетов вторым слоем не шифруется, и это правильно: она уже зашифрована ChaCha20-Poly1305 и статистически неотличима от случайных данных, так что второй проход дал бы нулевой выигрыш в маскировке при заметной трате процессора. Проще говоря: третье поколение прячет не содержимое — оно и так было спрятано, — а **служебную разметку**, по которой пакет опознавался как WireGuard. ### Приём: трюк с «хэшем типа» Здесь самая любопытная часть реализации. Проблема очевидна: чтобы расшифровать заголовок, нужно знать длину паддинга, а она зависит от типа сообщения, который сам лежит в зашифрованном заголовке. Замкнутый круг. Решение опирается на свойство потокового шифра — шифрование блока нулей даёт чистый поток ключа: ``` Приём (любой пакет): nonce = packet[:12] ← первые 12 байт датаграммы cip = ChaCha20(key, nonce) typeHash = cip.XORKeyStream(0x00000000) ← первые 4 байта потока ключа для каждого кандидата (init / response / cookie / transport): если размер пакета == S_i + размер_сообщения: тип = packet[S_i : S_i+4] XOR typeHash ← расшифровка только поля типа если тип попадает в диапазон H_i → это оно далее поток ключа продолжается с 5-го байта: cip.XORKeyStream(packet[4:конец_заголовка]) ``` Такая конструкция даёт две вещи. Во-первых, приёмник расшифровывает четыре байта вместо целого пакета и только потом решает, стоит ли возиться дальше. Во-вторых, поток ключа расходуется строго последовательно и в точности повторяет порядок, в котором шифровала отправляющая сторона: первые 4 байта ушли на поле типа, остальное — на всё, что за ним. Отдельная приятная деталь: при выключенной защите заголовков «хэш типа» состоит из нулей, а операция «исключающее ИЛИ» с нулями ничего не меняет. Один и тот же код работает в обоих режимах без ветвлений — источник целого класса ошибок здесь просто отсутствует. ## Как выглядит пакет на проводе **Транспортный пакет (третье поколение, защита включена):** ``` ┌───────────┬────────────────┬──────────────────────┬───────────────────────────┬───────────┐ │ nonce │ остаток S4 │ заголовок (16 байт) │ шифртекст полезной │ метка │ │ 12 байт │ S4-12 байт │ ЗАШИФРОВАН ChaCha20 │ нагрузки + content padding│ Poly1305 │ │ случайных │ случайных │ тип│получатель│счётчик│ │ 16 байт │ └───────────┴────────────────┴──────────────────────┴───────────────────────────┴───────────┘ └── открытым текстом, но неотличимо от шума ──┘ └── и до, и после: сплошная псевдослучайность ──┘ ``` **Сообщение инициации рукопожатия:** ``` ┌───────────┬───────────────┬──────────────────────────────────────────────┐ │ nonce │ остаток S1 │ 148 байт сообщения, ЗАШИФРОВАННЫХ ЦЕЛИКОМ │ │ 12 байт │ S1-12 байт │ (тип, отправитель, ключи, метка времени, │ │ │ │ MAC1, MAC2 — всё) │ └───────────┴───────────────┴──────────────────────────────────────────────┘ Итоговый размер: S1 + 148 байт — постоянный для данной конфигурации ``` Для наблюдателя со стороны сети датаграмма целиком выглядит равномерным шумом: ни одного поля с предсказуемым значением, ни одной структуры, за которую можно зацепиться сигнатурой. В версии 2.0 первые четыре байта после паддинга были осмысленным числом из диапазона `H1`–`H4`, а дальше шли постоянный индекс получателя и монотонно растущий счётчик. ## Content padding: как считается добавка Механизм устроен проще, чем можно подумать по названию, и умещается в один вспомогательный расчёт: - если `ContentPaddingAddition` не задан, возвращается признак «нет добавки», и работает обычное выравнивание длины до кратности 16; - если задан, из диапазона берётся случайное число; - добавка ограничивается сверху свободным местом до MTU (максимального размера передаваемого блока), чтобы не спровоцировать фрагментацию; - полученное количество **нулевых** байт дописывается в конец открытого текста, после чего всё вместе шифруется. Два следствия, важных на практике. Первое: добавка попадает **внутрь** зашифрованной части, наблюдатель видит только изменившуюся длину пакета — сами байты паддинга он отличить от данных не может. Второе: заданный content padding **отменяет** штатное выравнивание по 16 байт. Именно в этом смысл механизма — предсказуемая сетка длин, кратных шестнадцати, сама по себе является признаком, по которому поток можно отнести к WireGuard-подобным. > [!note] Keepalive тоже паддится — это скрытое улучшение > Служебные пакеты поддержания соединения проходят тот же путь, что и обычные: паддинг `S4` им назначается при создании исходящего элемента, а content padding считается от нулевого размера полезной нагрузки. В [[amnezia-2-0/reference|версии 2.0]] keepalive шёл в обход `S4` и всегда весил ровно 32 байта — постоянный размер, повторяющийся строго по таймеру, то есть отличный опознавательный признак. В третьем поколении проверка на keepalive осталась в коде ровно одна и служит другой цели: отличить служебный пакет от полезного при обновлении таймеров. Ни в README, ни в анонсах это изменение не упомянуто. ## Тайминги: где берётся случайное значение, а где граница Шесть таймеров стали диапазонами, но применяются они не одинаково, и разница принципиальна для устойчивости туннеля. **Случайное значение при каждом взводе** (`PickOne` берёт новое число из диапазона всякий раз): интервал переустановки ключей, пауза перед повтором рукопожатия, таймер отправки keepalive, интервал `PersistentKeepalive`, максимальное число попыток рукопожатия. Именно эти вызовы и размывают ритм соединения — два подряд рукопожатия не совпадут по времени. **Границы диапазона вместо случайного значения** — там, где протокол обязан сохранять внутренние инварианты. Например, срок жизни связки ключей вычисляется по **верхней** границе, а минимальный интервал между попытками рукопожатия — по **нижней**. Логика понятна: если бы движок брал случайные значения и здесь, он мог бы, например, посчитать ключ протухшим раньше, чем истёк допустимый срок его использования, и туннель начал бы рвать сам себя. Проще говоря: случайность добавлена там, где она видна снаружи и не ломает протокол, а во внутренних проверках используются осторожные крайние значения. ## Что изменилось относительно AWG 2.0 | Аспект | AWG 2.0 | AWG 3.0 | |---|---|---| | Поле типа сообщения | значение из диапазона `H1`–`H4`, открыто | зашифровано | | Индекс получателя и счётчик | открыты, предсказуемы | зашифрованы | | MAC1 и MAC2 в рукопожатиях | открыты | зашифрованы вместе со всем сообщением | | Паддинг `S1`–`S4` | чистый мусор | мусор плюс источник одноразового числа | | Минимум `S1`–`S4` | 0 (любое) | 12 при включённой защите заголовков | | Момент добавления `S4` | сдвиг готового пакета вправо | место резервируется заранее | | Keepalive | без `S4`, ровно 32 байта | с `S4` и content padding | | Длина полезной нагрузки | выравнивание до 16 байт | случайная добавка из диапазона | | Тайминги | константы WireGuard | диапазоны, новое значение на каждый взвод | | Мусорные и сигнатурные пакеты | `Jc`/`Jmin`/`Jmax`, `I1`–`I5`, язык CPS | без изменений | | Криптография WireGuard | не менялась | не менялась | Последнюю строку стоит проверить отдельно, поскольку вокруг неё много домыслов: файл с криптографическими примитивами (`device/noise-helpers.go`) в теге `v3.0.3` **побайтно совпадает** с оригиналом из `wireguard-go`. Рукопожатие Noise IKpsk2, эллиптическая кривая Curve25519, хэш Blake2s — всё нетронуто. Защита заголовков надстроена поверх готовых сообщений и в выработке ключей не участвует. ## Анализ: что протокол закрывает, а что оставляет видимым Раздел ниже — разбор по коду, а не позиция команды Amnezia: официального описания модели угроз третьего поколения не публиковалось. ### Что закрыто **Сигнатуры по содержимому пакета.** После шифрования заголовков в датаграмме не остаётся ни одного поля с предсказуемым или медленно меняющимся значением. Правило вида «четыре байта в начале равны 4, следующие четыре постоянны в рамках потока, дальше восьмибайтовый счётчик растёт на единицу» — а это и есть классический способ опознать WireGuard — больше не срабатывает. **Сигнатура keepalive.** Постоянный 32-байтовый пакет, приходящий строго по таймеру, был удобной зацепкой даже при полностью зашифрованном содержимом: важен не смысл байтов, а сам факт периодического повтора одинакового размера. Теперь размер плавает за счёт `S4` и content padding, а момент отправки — за счёт диапазонного таймера. **Сетка длин, кратных 16.** Content padding убирает регулярность, которая выдавала внутреннее выравнивание. ### Что осталось **Постоянные размеры рукопожатий.** Content padding применяется только к транспортным пакетам; сообщения инициации и ответа собираются в буфер фиксированной длины — `S1`+148 и `S2`+92 байта соответственно. Для конкретной конфигурации это две константы, и характерный обмен «датаграмма размера X → в ответ датаграмма размера Y → следом поток» никуда не делся. Универсальной сигнатуры здесь нет, поскольку `S1` и `S2` у каждой установки свои, но **структурный** признак — короткий двухшаговый обмен фиксированных размеров перед началом потока — наблюдаемый. **Молчание в ответ на активное зондирование.** Пакет, не подошедший ни под один размер и диапазон заголовков, просто отбрасывается без ответа. Для системы, которая проверяет подозрительный адрес отправкой мусора, «сервер, который не отвечает вообще ничем на порт UDP» — тоже поведенческий признак. Механизм отката на локальный веб-сервис, который решал бы эту задачу по образцу REALITY, в репозитории существует, но живёт в экспериментальной ветке и в релиз третьего поколения не вошёл. **Профиль потока данных.** Объём трафика, ритм пакетов приложения, длительность сессий — обфускация уровня протокола на это не влияет. В описанной разработчиком Amnezia балльной системе, где сервер блокируется по сумме признаков, объём трафика назван одним из ключевых факторов. **Блокировка по адресу.** Если адрес сервера уже в списках, смена версии протокола не поможет. Это ограничение общее для всех решений такого класса, см. [[DPI/ru-network-blocklists|обзор сетевых блокировок]] и [[DPI/vpn-blocking-wave-forecast-summer-2026|прогноз по волнам блокировок]]. ### Криптографические заметки Не уязвимости, а свойства конструкции, о которых стоит знать. Ключ защиты заголовков **статичен** на всё время жизни интерфейса, а одноразовое число имеет длину 96 бит и берётся из криптостойкого генератора случайных чисел. Повтор одноразового числа означал бы повторное использование потока ключа — для потокового шифра это ведёт к утечке: исключающее ИЛИ двух заголовков раскрывается. Оценка запаса по «парадоксу дней рождения»: пятидесятипроцентная вероятность совпадения достигается примерно после 2⁴⁸ пакетов — это порядка 10¹⁴ штук, то есть годы непрерывной работы канала на гигабитной скорости. Практического риска нет, но и бесконечным запас не является, а ротации этого ключа в протоколе не предусмотрено. Шифр применяется **без аутентификации**, и это корректно: подлинность обеспечивают штатные механизмы WireGuard (MAC1 и MAC2 в рукопожатиях, метка Poly1305 в транспортных пакетах), которые остались на месте. Расшифровка заголовка сама по себе ничего не подтверждает — она лишь позволяет опознать пакет, а решение о доверии принимается ниже по конвейеру. ### Накладные расходы К каждому транспортному пакету добавляется минимум 12 байт паддинга плюс случайная добавка content padding. На типичном пакете в 1400 байт минимальный обязательный оверхед составляет около 0,9% от полосы, что для практических целей несущественно; настроенный на широкий диапазон content padding способен добавить заметно больше — это осознанный размен скорости на маскировку. Вычислительная нагрузка мала: ChaCha20 обрабатывает 16 байт заголовка на пакет, а рукопожатия происходят раз в пару минут. ## Ловушки, замеченные в коде > [!warning] Сообщение об ошибке нумерует параметры с нуля > Если движок отвергает конфигурацию, он пишет `S0 must be more then 12 to use headerProtection`. Параметра `S0` не существует: проверка перебирает четыре значения в цикле и подставляет индекс массива, начинающийся с нуля, так что `S0` в сообщении означает **`S1`**, `S1` означает `S2` и так далее. При диагностике прибавляйте единицу. В той же строке живёт опечатка «more then» вместо «more than», а формулировка «больше 12» неточна — проверка отвергает значения **меньше** 12, само число 12 допустимо. Ещё три момента, на которых легко споткнуться: **Требование к паддингу распространяется на все четыре значения сразу.** Даже если вы, скажем, не собираетесь получать cookie-ответы под нагрузкой, `S3` всё равно обязан быть не меньше 12 — проверка не смотрит, какие типы сообщений реально используются. **README до 31 июля 2026 года указывал порог 8.** В коде порог всегда был 12; расходились именно документация и текст ошибки, исправленные коммитом `ce7cf103` в составе тега `v3.0.3`. Конфигурации, собранные по более ранней инструкции, работать не будут. **Недокументированных тегов сигнатур по-прежнему три.** README перечисляет пять тегов языка CPS, а в коде зарегистрировано восемь: помимо описанных `<b>`, `<r>`, `<rc>`, `<rd>` и `<t>` существуют `<d>`, `<ds>` и `<dz>`. В пакетах `I1`–`I5` они почти бесполезны, поскольку получают на вход пустые данные; полный разбор поведения каждого — в [[amnezia-2-0/reference|справочнике по AmneziaWG 2.0]]. В третьем поколении набор тегов не изменился. ## Практика: как это отлаживать - [ ] Сгенерировать ключ защиты заголовков: `awg genkey` из пакета `amneziawg-tools` версии `v3.0.20260730` или новее — более старые утилиты не знают новых ключей конфигурации и молча их не передадут. - [ ] Проверить, что все четыре значения `S1`–`S4` не меньше 12; при ошибке про `S0` смотреть на `S1`. - [ ] Убедиться, что `HeaderProtectionKey` побайтно одинаков на сервере и клиенте — при расхождении соединение не установится, а в журнале не будет ничего осмысленного: пакеты просто отбрасываются как неопознанные. - [ ] Поднять уровень журналирования переменной окружения `LOG_LEVEL=debug` — сообщения о неопознанных пакетах и об инициализации шифра идут именно туда. - [ ] На Linux с модулем ядра брать ревизию не ниже `v3.0.20260731-04`: в более ранних сборка падает на ядрах старше 6.7, а ключ защиты заголовков мог молча не применяться. - [ ] Не копировать значения `I1`–`I5` из примеров: повторяющаяся у тысяч установок сигнатура работает против вас, чему посвящён отдельный разбор в [[amnezia-3-0/reference|обзорной заметке]]. ## 📚 См. также - [[amnezia-3-0/reference|AmneziaWG 3.0]] — обзор: зачем выпущен, кому доступен, история поколений и разбор мифов вокруг релиза. - [[amnezia-2-0/reference|AmneziaWG 2.0: справочник параметров]] — база, поверх которой надстроено третье поколение: мусорные пакеты, паддинг, диапазонные заголовки, язык CPS и все восемь его тегов. - [[amnezia-3-0/client-5-0-0-5|AmneziaVPN 5.0.0.5]] — приложение, в котором третье поколение приехало пользователям. - [[DPI/statistical-morphing-concept|Статистический морфинг трафика]] — общая идея правки длин и таймингов, частным случаем которой является content padding. - [[protocols/00-overview|Карта протоколов обхода блокировок]] — место AmneziaWG среди остальных решений. - 🔗 [amneziawg-go, тег v3.0.3](https://github.com/amnezia-vpn/amneziawg-go/tree/v3.0.3) — исходники, по которым сделан разбор. --- > [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ > При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны на GitHub: [исходник этой заметки](https://github.com/youtubediscord/todo/blob/main/amnezia-3-0/internals.md) · [весь репозиторий](https://github.com/youtubediscord/todo/tree/main).