🔬 AmneziaWG: как устроен и что значит каждый параметр
Справочник для тех, кто настраивает обфускацию руками и хочет понимать, что делает каждое поле. Если задача проще — обновиться или починить соединение, — начните с практических страниц: обновление с 2.0 на 3.1 и версии и тонкая настройка.
Чем AmneziaWG отличается от WireGuard
AmneziaWG — форк WireGuard, который решает одну конкретную проблему: обычный WireGuard слишком легко опознать.
Его пакеты имеют фиксированный первый байт типа сообщения и предсказуемые размеры — 148 байт на handshake initiation и 92 на response. Поэтому DPI определяет протокол по первому же пакету и блокирует его целиком.
AmneziaWG добавляет слой обфускации поверх той же криптографии: случайные заголовки вместо фиксированных, паддинг переменной длины, мусорные пакеты перед сессией и имитацию чужих протоколов.
Криптография не меняется
Noise при этом не трогается — меняется только то, как соединение выглядит снаружи. Обфускация не делает туннель «более» или «менее» защищённым, она делает его неузнаваемым.
Что добавила каждая версия
| Версия | Что появилось |
|---|---|
| 1.0 | Базовая обфускация: junk-пакеты (Jc, Jmin, Jmax), паддинг S1 и S2, фиксированные магические заголовки H1–H4 |
| 1.5 | CPS-цепочки I1–I5 — работают только на стороне клиента |
| 2.0 | S3 и S4 (паддинг cookie- и транспортных пакетов); H1–H4 задаются диапазонами, а не одним числом — заголовок каждого пакета выбирается из диапазона случайно |
| 3.0 | HeaderProtectionKey (шифрование заголовков ChaCha20), ContentPaddingAddition (случайный паддинг транспорта) и рандомизация таймеров протокола |
| 3.1 | RandomTrailers — дописывает пакеты до размера MTU случайными значениями; DisableCookies — отключает ответы cookiereply на порту WireGuard |
Как определить версию по своему .conf — в разделе Как определить версию.
Какие параметры обязаны совпадать с сервером
Совпадать должны не все параметры, и это стоит разделить точно. Деление следует из того, как принимающая сторона разбирает пакет: в amneziawg-go функция DeterminePacketTypeAndPadding (device/receive.go) пробует опознать входящий пакет по двум признакам — длина должна равняться собственному S плюс известный размер сообщения, а четыре байта на позиции S должны попадать в собственный диапазон H. Не совпало — пакет получает тип Unknown и молча отбрасывается.
Отсюда три группы.
Общие — обязаны совпадать
S1–S4, H1–H4 и HeaderProtectionKey.
Получатель разбирает чужие пакеты своими значениями, поэтому расхождение означает, что пакет будет отброшен молча, без ошибки. Ключ защиты заголовков попадает сюда же: шифр строится из своего ключа и nonce, взятого из S-паддинга пришедшего пакета.
Молча — значит без диагностики
Это самый неприятный класс ошибок: соединение просто не устанавливается, и ни в логах, ни в приложении не появляется внятной причины. Если после ручной правки параметров туннель перестал подниматься — в первую очередь сверьте эту группу.
Клиентские — совпадать не обязаны
Jc, Jmin, Jmax, цепочка I1–I5 и ContentPaddingAddition.
Мусорные пакеты и I-цепочка уходят перед handshake initiation и на приёме не разбираются вовсе — они как раз и попадают в ветку Unknown, для того и сделаны. ContentPaddingAddition добавляет паддинг внутрь шифрованной нагрузки, а получатель отрезает лишнее по длине из IP-заголовка, поэтому знать величину ему не нужно.
Практический вывод
Разным устройствам полезно давать разные Jc, Jmin, Jmax и I1–I5. Одинаковый у сотни клиентов мусорный поезд — готовый шаблон для DPI; разный такого шаблона не даёт.
Локальные — у каждой стороны свои
Таймеры 3.0: RekeyAfterTime, RekeyTimeout, RejectAfterTime, KeepaliveTimeout, MaxHandshakeAttempts.
Договорённости они не требуют, но разводить их до крайностей не стоит: иначе одна сторона начнёт переустанавливать сессию, которую другая ещё считает живой.
Параметры по отдельности
Jc, Jmin, Jmax — мусорные пакеты
Перед началом сессии клиент отправляет Jc мусорных UDP-пакетов случайной длины между Jmin и Jmax байт.
Смысл в том, чтобы размазать временной и размерный профиль старта соединения: вместо чистого «148 байт, затем 92» DPI видит очередь пакетов разного размера, среди которых настоящий handshake не выделяется.
Платой идёт трафик и время на старте — каждый junk-пакет реально уходит в сеть. Jc от 3 до 7 обычно достаточно; большие значения заметно замедляют подключение, особенно на мобильной сети.
S1–S4 — паддинг пакетов
Количество случайных байт, дописываемых перед пакетом, чтобы сбить его характерный размер:
| Параметр | Для какого пакета |
|---|---|
S1 | handshake initiation |
S2 | handshake response |
S3 | cookie reply |
S4 | транспортные пакеты |
Итоговые размеры становятся 148 + S1 и 92 + S2 вместо фиксированных.
Ловушка: S1 + 56 = S2
Если S1 + 56 окажется равным S2, initiation и response снова станут одного размера — и вы вернёте ровно тот отпечаток, от которого уходили. Генератор такие совпадения отслеживает и не выпускает.
S4 ограничен 32 байтами на уровне протокола.
H1–H4 — заголовки
H1–H4 заменяют предсказуемые идентификаторы типа сообщения WireGuard (1, 2, 3, 4) на произвольные 32-битные значения: H1 — initiation, H2 — response, H3 — cookie reply, H4 — транспорт. В версии 2.0 и выше это диапазоны, и для каждого пакета значение берётся из диапазона случайно.
Пересекаться они не должны по простой причине: получатель определяет тип пакета именно по этому числу. Если диапазоны H1 и H4 накладываются, пакет из зоны перекрытия невозможно однозначно классифицировать, и он будет отброшен. Генератор разносит все четыре диапазона и проверяет это перед выдачей.
I1–I5 — цепочка CPS
До пяти пакетов, которые клиент отправляет перед handshake, чтобы начало сессии выглядело как чужой протокол. Содержимое описывается тегами:
| Тег | Что вставляет |
|---|---|
<b hex> | статические байты — например, шапка QUIC Initial |
<t> | 32-битная метка времени в сетевом порядке байт |
<r N> | N криптослучайных байт |
<rc N> | N случайных латинских букв |
<rd N> | N случайных цифр |
Обычно I1 несёт узнаваемую сигнатуру реального протокола, а I2–I5 добавляют энтропию, чтобы пачка не выглядела одинаково от сессии к сессии.
Какие теги доступны, решает движок, а не приложение. Пять перечисленных выше понимают оба движка: и amneziawg-go, и модуль ядра Linux. Сверх них у amneziawg-go есть d, ds, dz, а у модуля ядра — <c>, счётчик пакетов, которого в go нет вовсе.
Незнакомый тег отвергается вместе со всем пакетом
Поэтому генератор гасит тег, если движок выбранного клиента его не знает. Клиенты для Android, iOS, Windows, macOS и Linux работают на amneziawg-go — тега <c> в них нет.
Почему часть тегов недоступна
Потому что в текущем релизе они ничего не делают. В amneziawg-go v3.0.1 эти теги действительно разбираются парсером, но цепочки I1–I5 вызываются в коде отправки только с пустой полезной нагрузкой — так что теги, работающие с данными пакета, не получают ничего.
Судя по ветке feature/awg4 в amneziawg-tools, где семь ключей 3.0 заменены на DI, DR, DC и DT, это задел под маскировку транспортных пакетов в AmneziaWG 4.0. Пока фича не собрана целиком, выдавать её в конфиге — значит выдать неработающий конфиг.
Как выбрать MTU
| Значение | Когда подходит |
|---|---|
| 1500 | Стандартный Ethernet — большинство проводных подключений |
| 1420 | PPPoE и мобильные сети, где часть пакета съедает инкапсуляция |
| 1280 | Минимальный MTU, гарантированный IPv6 — когда соединение устанавливается, но крупные пакеты теряются |
Симптом слишком большого MTU узнаваемый
Пинг проходит, лёгкие страницы открываются, а тяжёлые сайты и загрузки зависают. Если картина такая — уменьшайте MTU.
Для AmneziaWG 3.1 рекомендуется сразу ставить MTU = 1280, см. обновление с 2.0 на 3.1.
Источники
Страница собрана из материалов сообщества, а не написана нами с нуля:
- FAQ генератора ARCHITECT — устройство протокола, разбор параметров и ссылки на места в коде, из которых следует описанное поведение.
- Инструкции Shidla — «Протокол AmneziaWG» — определение версий, признаки 3.1 и практические сценарии.
Спасибо авторам за то, что это вообще существует в письменном виде.
