# 🔬 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).