LxBox v2.23.0
L×Box v2.23.0
AmneziaWG 3.0/3.1 arrives. An Amnezia export with header protection,
content padding, random trailers and ranged timings now imports as it is and
connects — with the bundled core updated to 1.14.0-lx.34. Plus workspaces
(named sets of settings you switch from the main screen), Get WARP through a
host that is reachable from Russia, a MASQUE entry-point list that matches the
transport, a support feed that stays offline until you allow update checks,
and the “Settings changed” banner that no longer sticks forever.
Пришёл AmneziaWG 3.0/3.1. Экспорт Amnezia с защитой заголовка, паддингом
содержимого, случайными хвостами и диапазонными таймингами импортируется как
есть и соединяется — ядро в сборке обновлено до 1.14.0-lx.34. Плюс
workspaces (именованные наборы настроек, переключаются с главного экрана),
Get WARP через хост, доступный из России, список точек входа MASQUE по
транспорту, support-лента, которая не ходит в сеть без согласия на проверку
обновлений, и плашка «Settings changed», которая больше не висит вечно.
🇬🇧 English
🔐 AmneziaWG 3.0/3.1 (docs/spec/tasks/421)
Amnezia exports an AWG 3.x server as the amnezia-awg2 container with
protocol_version: "3.1". Its .conf carries, on top of the AWG 2.0 set,
HeaderProtectionKey, ContentPaddingAddition, RekeyAfterTime,
RekeyTimeout, RejectAfterTime, KeepaliveTimeout, MaxHandshakeAttempts,
RandomTrailers, DisableCookies, and a ranged PersistentKeepalive = 25-35.
The node looked configured, but the handshake with a server that encrypts the
header could never succeed. All import paths are covered: .conf,
wireguard:// / awg://, vpn://, sing-box JSON.
| Before | Now |
|---|---|
| The nine AWG 3.x keys were dropped silently | Reach the core as the same-named snake_case keys on the endpoint root; N-M as a string, N as a number, booleans emitted as true only when on |
PersistentKeepalive = 25-35 was lost (int.tryParse) |
persistent_keepalive_interval: "25-35" |
last_config.mtu of the export was not read; a node with AWG 3.x keys only did not count as AmneziaWG |
MTU is taken from last_config.mtu when [Interface] has none; an AWG 3.x marker alone makes the node AmneziaWG — the 1280 default and clamp apply as for AWG 2.0 (with the export's 1376 no data flowed through the owner's server), an explicitly lower value is respected |
| — | Error policy mirrors the desktop launcher (SPEC 123): a malformed timing or boolean drops the field (awg3_field_invalid); a header protection key that is not 32 bytes of base64, or s1–s4 below 12 next to it, drops the node (awg3_header_key_invalid, awg3_padding_too_short) — the core would reject the whole config; random_trailers together with a magic-header range 65536 or wider earns an info (awg3_random_trailers_wide_headers) |
Level badges awg / awg1.5 / awg2 |
Plus awg3 (any AWG 3.x key or a ranged keepalive) and awg3.1 (random_trailers / disable_cookies); the + suffix for masquerade as before |
A share link and a .conf export write the booleans back as on; the round
trip is lossless. The header protection key is read with its + preserved —
the query decoder used to turn it into a space. Verified on a device with the
owner's AWG 3.1 export: the awg3.1 badge, a completed handshake, and
1.1.1.1/cdn-cgi/trace answering with the server's address.
🗂 Workspaces — named sets of settings (docs/spec/features/417)
A workspace is a saved copy of everything the app knows: subscriptions with
their cached node lists, Directions, chains, rules, DNS, tunnel apps, the
operating mode, app settings. Keep a “Home” set and a “Work” set and switch
between them from the main screen — the button to the right of “L×Box” shows
the current workspace and opens the menu.
- Load — the current state is saved under its own name first, so nothing
is lost; then the chosen one is copied in, the settings are re-read in place
(no app restart) and the config is rebuilt. A running VPN is stopped for the
switch and started again with the new config. - Save as… — the current state under a new name, or over an existing one.
- ⋮ on a row — rename or delete; the row shows the slot's size. The current
workspace cannot be deleted: it is where the auto-save goes. - An interrupted load (the app killed mid-copy) is finished on the next start
from a journal; until you save anything, nothing changes on disk.
⚙️ WARP
Get WARP through api.devices.cloudflare.com (docs/spec/tasks/418)
api.cloudflareclient.com does not answer TCP from Russia at all, and “Get
WARP” died on a timeout. The API hosts now live in the warp_endpoints.json
asset (api.hosts): api.devices.cloudflare.com first (same path, headers and
response, reachable directly), the old host as a fallback on a network error or
timeout; the per-host timeout is 5 s (was 15). Every step of one registration
(MASQUE enroll, WARP+ licence) goes to the host that answered. The first MASQUE
POST now carries a real X25519 key instead of random bytes — the new host
checks it. The host was pointed out in PR #101 by @eleutherifer.
MASQUE entry points by transport (docs/spec/tasks/420)
Live tunnels showed that in the Cloudflare blocks 162.159.198.* and
162.159.199.* the .1 addresses serve h3 (QUIC) only, .2 both h3 and h2,
the rest h2 only. The “Endpoint IP” list in the WARP wizard was one for all
transports, so .1 under HTTP/2 produced a dead node, and the recommended
entry was consumer-masque.cloudflareclient.com, which has no DNS record.
| Before | Now |
|---|---|
| One address list for every transport | The list depends on the transport; switching it resets a preset that no longer fits |
| Recommended: a name without an A record | Recommended: 162.159.198.2 (the same one registration returns) |
| The dice and the generator could pick an h3-only address for h2 | They cannot; the old experiment JSON still reads as before |
SNI pools
deepseek.com, mail.ru, max.ru, vk.ru added to the WARP SNI pools
(WireGuard/AWG and MASQUE) — allow-list domains DPI cuts less often (idea from
PR #101). The recommended MASQUE
SNI is unchanged.
📣 Support feed and support links
The feed goes online only with update checks allowed (docs/spec/tasks/422)
Answer “Skip” to the first-run question and the app makes no request for the
feed at all. Prompted by an F-Droid reviewer's question (#61).
| Before | Now |
|---|---|
GET raw.githubusercontent.com/…/support.json on every process start with the tunnel up, regardless of the flag |
Only with auto_check_updates; without it — the cache of the last successful download |
| No network and no cache — the feed is silent | The feed is bundled into the APK at build time, so the queue and thresholds work from the first start; it is not written to the cache, a fresh one arrives once consent is given |
Source docs/support.json, separate from the app |
One file, app/assets/support.json: bundled and served from GitHub; docs/support.json removed — versions ≤ 2.22.0 live on their cache from now on |
One source for the support links (docs/spec/tasks/423)
The same scheme for app/assets/donate.json: bundled and served from GitHub,
docs/donate.json removed (the copies had drifted). No gate needed — About →
Support reads it only on tap; it never made background requests.
🧰 Fixes
The “Settings changed” banner no longer sticks forever (docs/spec/tasks/419)
| Before | Now |
|---|---|
A resolver (dns.final / default_domain_resolver) pointing at a DNS server of a preset that was later disabled or deleted failed every rebuild on validation: the config was not saved, the banner stayed, a tap on it showed nothing; the auto-reset fired only when DNS Settings was opened |
The dangling reference is healed inside the build itself (template default dns_shield, the value is written to the settings); if a rebuild still fails, the reason is shown in a snackbar |
🧬 Core: 1.14.0-lx.34
lx.30 → lx.34, two steps in one release.
lx.32/lx.33 — AmneziaWG 3.x. lx.32 is the first core with the AWG 3.x
fields on the wireguard endpoint; lx.33 fixes receiving data packets under
random_trailers. A core up to lx.31 rejects a config with any AWG 3.x key as
a whole, so the pin and the parser moved in one commit.
lx.34 — the upstream 1.14.0 stable base (plus 16 post-release commits).
Configs and the wire format do not change; what you feel:
- A URL test can no longer hang on a node that accepts the connection and never
answers — each probe now runs under its own 15 s deadline. - A manual test of a group probes every node (upstream
forcesemantics)
and recurses into nested groups; the periodic ticker keeps the lazy check. - Resolver-discovery DNS queries (
_dns.*SVCB) are answered with an empty
NOERROR, so a browser cannot learn an upstream DoH endpoint and go around the
tunnel. Inverted DNS rules with address filters from a rule set match again. - QUIC throughput on TUIC and naive no longer collapses after an idle period.
- The system stack keeps a separate TCP NAT port table per address family, and
a Wi-Fi ↔ cellular switch no longer blocks the interface monitor.
The Java surface is additive only (javap over 253 classes) — no binding changes.
🧪 Tests
flutter analyze (0 issues), the full test set, all six checkers. The shared
contract corpus with the desktop launcher is green on both sides, including the
new awg3_full_params, amnezia_vpn_awg3 and awg3_header_key_short_dropped
fixtures.
🇷🇺 Русский
🔐 AmneziaWG 3.0/3.1 (docs/spec/tasks/421)
Amnezia экспортирует AWG 3.x сервер контейнером amnezia-awg2 с
protocol_version: "3.1". В его .conf кроме набора AWG 2.0 стоят
HeaderProtectionKey, ContentPaddingAddition, RekeyAfterTime,
RekeyTimeout, RejectAfterTime, KeepaliveTimeout, MaxHandshakeAttempts,
RandomTrailers, DisableCookies и диапазонный PersistentKeepalive = 25-35.
Узел выглядел настроенным, но хендшейк с сервером, шифрующим заголовок, не мог
пройти никогда. Покрыты все пути импорта: .conf, wireguard:// / awg://,
vpn://, sing-box JSON.
| Было | Стало |
|---|---|
| Девять ключей AWG 3.x молча выбрасывались | Доезжают до ядра одноимёнными snake_case-ключами корня endpoint; N-M — строкой, N — числом, булевы пишутся true только при on |
PersistentKeepalive = 25-35 терялся (int.tryParse) |
persistent_keepalive_interval: "25-35" |
last_config.mtu экспорта не читался; узел только с AWG3-ключами не считался AmneziaWG |
MTU берётся из last_config.mtu, если его нет в [Interface]; AWG3-маркер сам делает узел AmneziaWG — дефолт 1280 и кламп как у AWG 2.0 (на 1376 из экспорта данные через сервер владельца не шли), явно меньшее значение уважается |
| — | Политика ошибок как у десктопного лаунчера (SPEC 123): битый тайминг или булево снимает поле (awg3_field_invalid); ключ защиты заголовка не из 32 байт base64 или s1–s4 меньше 12 рядом с ним выбрасывает узел (awg3_header_key_invalid, awg3_padding_too_short) — ядро отвергло бы конфиг целиком; random_trailers вместе с диапазоном magic-заголовков от 65536 даёт info (awg3_random_trailers_wide_headers) |
Лейблы уровня awg / awg1.5 / awg2 |
Плюс awg3 (любой AWG3-ключ или диапазонный keepalive) и awg3.1 (random_trailers / disable_cookies); суффикс + при masquerade как прежде |
Share-ссылка и экспорт .conf пишут булевы обратно как on; round-trip без
потерь. Ключ защиты заголовка читается с сохранённым + — декодер query
превращал его в пробел. Проверено на устройстве экспортом владельца AWG 3.1:
лейбл awg3.1, завершённый хендшейк, 1.1.1.1/cdn-cgi/trace отвечает адресом
сервера.
🗂 Workspaces — именованные наборы настроек (docs/spec/features/417)
Workspace — сохранённая копия всего, что знает приложение: подписки с кэшем
узлов, Направления, цепочки, правила, DNS, приложения туннеля, режим работы,
настройки. Держите набор «Дом» и набор «Работа» и переключайтесь между ними с
главного экрана — кнопка справа от «L×Box» показывает текущий набор и
открывает меню.
- Load — сначала текущее состояние сохраняется под своим именем, терять
нечего; затем выбранный набор копируется, настройки перечитываются на месте
(без перезапуска приложения) и конфиг пересобирается. Включённый VPN на
время переключения останавливается и поднимается снова с новым конфигом. - Save as… — текущее состояние под новым именем или поверх существующего.
- ⋮ на строке — переименовать или удалить; в строке виден размер слота.
Текущий набор удалить нельзя: в него идёт автосохранение. - Прерванная загрузка (приложение убито посреди копирования) доделывается на
следующем старте по журналу; пока ничего не сохранено, на диске ничего не
меняется.
⚙️ WARP
Get WARP через api.devices.cloudflare.com (docs/spec/tasks/418)
api.cloudflareclient.com из России не отвечает на TCP вовсе, и «Get WARP»
падал по таймауту. Хосты API теперь перечислены в asset
warp_endpoints.json (api.hosts): первый — api.devices.cloudflare.com (тот
же путь, заголовки и ответ, доступен напрямую), при сетевой ошибке или таймауте
— старый хост запасным; таймаут на запрос к одному хосту 5 с (было 15). Все
шаги одной регистрации (MASQUE-enroll, лицензия WARP+) идут на хост, который
ответил. Первый MASQUE POST теперь несёт настоящий X25519-ключ вместо случайных
байт — новый хост его проверяет. Наводка на хост — PR #101 от @eleutherifer.
Точки входа MASQUE по транспортам (docs/spec/tasks/420)
Замер живыми туннелями показал, что в блоках Cloudflare 162.159.198.* и
162.159.199.* адреса .1 дают только h3 (QUIC), .2 — h3 и h2, остальные —
только h2. Список «Endpoint IP» в мастере WARP был один на все транспорты,
поэтому .1 при HTTP/2 давал мёртвый узел, а рекомендуемым стояло имя
consumer-masque.cloudflareclient.com, у которого нет DNS-записи.
| Было | Стало |
|---|---|
| Один список адресов на все транспорты | Список зависит от транспорта; при переключении неподходящий пресет сбрасывается |
| Рекомендуемый — имя без A-записи | Рекомендуемый — 162.159.198.2 (его же отдаёт регистрация) |
| Кубик и генератор могли выдать h3-only адрес для h2 | Не могут; старый JSON эксперимента читается как прежде |
SNI-пулы
В SNI-пулы WARP (WireGuard/AWG и MASQUE) добавлены deepseek.com, mail.ru,
max.ru, vk.ru — домены из «белых» списков, которые DPI режет реже (идея
из PR #101). Рекомендуемый
MASQUE-SNI прежний.
📣 Support-лента и ссылки поддержки
Лента ходит в сеть только с согласия на проверку обновлений (docs/spec/tasks/422)
Ответили «Skip» на вопрос первого запуска — приложение не делает ни одного
запроса за лентой. Повод — вопрос ревьюера F-Droid (#61).
| Было | Стало |
|---|---|
GET raw.githubusercontent.com/…/support.json при каждом запуске процесса с поднятым туннелем, независимо от флага |
Только при auto_check_updates; без него — кэш последней удачной загрузки |
| Без сети и без кэша лента молчит | Лента вшита в APK на момент сборки — очередь и пороги работают с первого запуска; в кэш она не пишется, при появлении согласия придёт свежая |
Источник — docs/support.json, отдельно от приложения |
Единственный файл app/assets/support.json: он же бандлится, он же раздаётся с GitHub; docs/support.json удалён — версии ≤ 2.22.0 дальше живут на своём кэше |
Единственный источник ссылок поддержки (docs/spec/tasks/423)
Та же схема для app/assets/donate.json: бандлится и раздаётся с GitHub,
docs/donate.json удалён (копии успели разойтись). Гейт не нужен — About →
Support читает файл только по тапу, фоновых запросов у него не было.
🧰 Починено
Плашка «Settings changed» больше не висит вечно (docs/spec/tasks/419)
| Было | Стало |
|---|---|
Резольвер (dns.final / default_domain_resolver), указывающий на DNS-сервер пресета, который потом выключили или удалили, ронял каждую пересборку на валидации: конфиг не сохранялся, плашка не снималась, тап по ней ничего не показывал; автосброс срабатывал только при открытии DNS Settings |
Битая ссылка лечится в самой сборке (дефолт шаблона dns_shield, значение записывается в настройки); если пересборка всё же не удалась — причина показывается снеком |
🧬 Ядро: 1.14.0-lx.34
lx.30 → lx.34, два шага в одном релизе.
lx.32/lx.33 — AmneziaWG 3.x. lx.32 — первое ядро с полями AWG 3.x на
endpoint wireguard; lx.33 чинит приём data-пакетов при random_trailers.
Ядро до lx.31 включительно отвергает конфиг с любым AWG3-ключом целиком,
поэтому пин и парсер поехали одним коммитом.
lx.34 — переход на стабильную базу апстрима 1.14.0 (плюс 16 пострелизных
коммитов). Конфиги и формат на проводе не меняются; что заметно:
- URL-тест больше не зависает на узле, который принимает соединение и не
отвечает, — у каждой пробы свой дедлайн 15 с. - Ручной тест группы пробует все узлы (семантика
forceапстрима) и
заходит во вложенные группы; периодический тикер сохраняет ленивую проверку. - DNS-запросы discovery-резолвера (
_dns.*SVCB) получают пустой NOERROR —
браузер не узнаёт внешний DoH-эндпоинт и не уходит мимо туннеля.
Инвертированные DNS-правила с адресными фильтрами из rule-set снова матчатся. - Скорость QUIC на TUIC и naive не проседает после простоя.
- У системного стека своя таблица TCP NAT на каждое семейство адресов, а
переключение Wi-Fi ↔ сотовая больше не блокирует монитор интерфейсов.
Java-поверхность только аддитивна (javap по 253 классам) — правок обвязки нет.
🧪 Тесты
flutter analyze (0 issues), полный набор тестов, все шесть чекеров. Общий
контрактный корпус с десктопным лаунчером зелёный с обеих сторон, включая
новые фикстуры awg3_full_params, amnezia_vpn_awg3 и
awg3_header_key_short_dropped.
Install / Установка
adb install -r LxBox-v2.23.0-arm64-v8a.apkБез uninstall! Поверх существующей установки. Настройки и подписки сохранятся.
No uninstall needed — install over the existing one. Settings and
subscriptions are preserved.
Previous release / Предыдущий релиз: v2.22.0.