github Leadaxe/LxBox v2.19.4
LxBox v2.19.4

latest releases: v2.25.20, v2.25.19, v2.25.19-rc.3...
3 months ago

L×Box v2.19.4

Moving over from sing-box no longer means setting everything up by hand: a
whole config now imports — nodes, auto-select groups, detour chains. Rule sets
stopped going stale in silence, each one having its own expiry the app
re-checks on launch and on connect. Plus three bugs from one scenario — switch
a subscription and see the result: the change could fail to reach the config,
the tunnel could freeze at "Connected" with a dead Stop button, and the new
nodes could sit there with no latency figures at all.

None of the three were new. What they had in common is that each one hid behind
a workaround the user had already learned: pull down to refresh, or press the
button again, or force-close the app. This release removes the need for all
three.

Переход с sing-box больше не значит «настроить всё заново»: конфиг импортируется
целиком — узлы, группы автовыбора, цепочки detour. Rule-set'ы перестали
устаревать молча: у каждого свой срок годности, и приложение само перепроверяет
его при запуске и при подключении. Плюс три бага одного сценария — сменить
подписку и увидеть результат: изменение могло не доехать до конфига, туннель —
залипнуть в «Подключено» с мёртвой кнопкой «Остановить», а новые узлы —
остаться без цифр задержки.

Ни один из трёх не новый. Общее у них то, что каждый прятался за обходным
путём, к которому пользователь уже привык: потянуть список вниз, нажать кнопку
ещё раз, выгрузить приложение. Этот релиз убирает необходимость во всех трёх.


English

📥 New — sing-box configs import whole: nodes, groups, chains

Pasting a sing-box config answered "Input is not a subscription URL, proxy
link, or outbound JSON". A single outbound had been parsed for a long time; a
whole config — the shape with log, dns, inbounds, outbounds, route —
was not recognised at all. That shape is exactly what people paste when they
move over from sing-box.

Four shapes are now accepted: a single outbound, an array of outbounds, a whole
config, and an array of configs (a subscription where every element is a
self-contained config). What comes across is the transport layer:

in the config what you get
outbounds / endpoints nodes, all 13 supported types
urltest / selector an auto-select group with its members and settings
detour a node chain; any type can be a hop
route, dns, inbounds not imported

Routing and DNS are generated by the app from its own settings, so foreign
rules do not come along — the confirmation dialog lists which sections were
left behind, rather than dropping them quietly.

The reading follows the same rules as Xray configs: one server described
several times across a subscription collapses into a single node; the name goes
to the entry where it means something; an unsupported type does not vanish
silently but leaves a warning on a neighbouring node. A loop in the chains
(A → B → A) is broken on import — a config from someone else's file is not
something you can go and fix, and losing nodes over a loop would be the wrong
trade.

Pasted JSON with more than one node now lands as a set rather than as a single
server entry: full node list, per-node disable, import rules. Same threshold as
importing a file — one node is a server, several are a set. Previously a
ten-node config went into a "one server" container and was labelled in the list
after whichever protocol came first.

One side effect worth naming: an array of sing-box outbounds in a subscription
body used to yield zero nodes, silently — shape detection only worked for
clipboard pastes. The shape is now detected the same way everywhere, and the
preview shows exactly what will arrive: it could previously promise "Outbound
JSON" where the import then refused.

🕒 New — rule sets keep themselves up to date

A rule set downloaded once and then sat there unchanged: the app never
re-fetched it, the core never goes out to the network for one on its own, and
the interface did not even show a date — there was no way to tell how stale
your copy had become.

Each rule now carries its own expiry. The rule editor has a Check for
updates
setting: never, daily, weekly (default), every 2 weeks, monthly,
every 6 months, yearly. Preset rule sets come with an interval already set.

The check runs half a minute after the app starts and half a minute after the
VPN connects, and only while the app is open. If it fails, it retries once
after twenty seconds, then waits for the next connect. A successful pass goes
quiet until the app is restarted. There is no permanent background timer:
waking the device every hour for a file that changes once a week costs more
battery than it is worth.

It is nearly free in traffic. The app asks the server whether the file has
changed at all, and an unchanged rule set comes back as a bare "not modified"
with no body.

A failed check breaks nothing. The downloaded file stays where it is and the
rule keeps working — even if the list is gone from the server entirely. A
stale working list beats a disabled rule.

The rule editor also shows when the file was last updated, with an Update
now
button next to it. The rule list itself stays as it was: just the cloud
icon, downloaded or not.

🔄 Fixed — enabling a subscription could fail to reach the config

You enabled a subscription on the Servers screen, went back to the main screen —
and the new nodes were not there. The "rebuild config and restart" option did not
help either: it triggers on the "there are unsaved changes" mark, and in this
case the mark was never set.

The cause was a race with the rebuild. Returning to the main screen starts a
config rebuild on its own, and toggling the subscription landed inside its
window: the change was applied to the list, but the rebuild had already read the
composition before it — and on finishing cleared the "changes pending" mark, for
a change that never made it into the built config. The new set of nodes only
arrived with the next incidental edit.

The same path also lost a server added while another subscription was updating.

The mark is now cleared only if the composition did not change under the
rebuild. Pulling the list down to refresh was the workaround — it worked because
it started a fresh rebuild, which then read the current composition.

🧊 Fixed — "Connected" with nothing connected, and a dead Stop button

Start the VPN on a node that does not answer, then press Stop without waiting —
and the app could freeze: "Connected" in the header, the timer running, no
traffic, an empty node list, and a Stop button that did nothing. The only way
out was to force-close the app.

The cause was ordering. Connecting to an unresponsive node holds an internal
call for seconds. A stop in that window tore the tunnel down completely — and
then the late reply from the connect attempt arrived and marked the tunnel as
running, when there was nothing left to stop. From that point the stop command
went out to nobody and timed out after five seconds.

The late reply now checks whether the start was cancelled and no longer
overrides the status. The stop path additionally recognises the mismatch and
finishes the tunnel immediately instead of waiting for a reply that will never
come.

🔁 A false "Another VPN is active" prompt

Fixed in the same place. If a VPN interface of ours survived from a previous
session, the app mistook it for a foreign one and offered to switch from itself
to itself — on every start. The owner of the connection is now checked
explicitly, so the prompt only appears when the VPN really belongs to another
app.

📊 Fixed — new nodes had no latency figures after a subscription change

With auto-apply enabled, changing a subscription restarts the tunnel and redraws
the list — but the latency figures for the new set of nodes never appeared until
the check was started by hand. On an ordinary VPN start the ping runs by itself.

The automatic ping was tied to the moment of connecting. Applying changes
happens without tearing the tunnel down, so connecting does not happen again —
and there was nobody left to start the measurements. On top of that the node
list the ping works from is filled by the core, and after an in-place reload
that update could fail to arrive at all: the screen showed the new set (drawn
from the saved config), while the ping had an empty list to work with.

Both halves are fixed: the node list is now refreshed from the core after a
reload, and the ping is scheduled after that. The auto-check setting is
respected on this path exactly as it is on start.

✅ Tests

2838 tests green, project-wide analyze clean. The new regression tests fail on
the pre-fix code — each was verified by reverting its fix. Rule-set updating
adds 29 tests of its own: conditional requests and "not modified" handling,
expiry arithmetic per interval, and the guarantee that a failed check leaves
the cached file and the rule alone.

Русский

📥 Новое — конфиги sing-box импортируются целиком: узлы, группы, цепочки

Вставка конфига sing-box отвечала «Input is not a subscription URL, proxy link,
or outbound JSON». Отдельный outbound приложение разбирало давно, а конфиг
целиком — форму с log, dns, inbounds, outbounds, route — не
опознавало вовсе. Именно её и вставляют при переходе с sing-box.

Теперь берутся четыре формы: одиночный outbound, массив outbound'ов, полный
конфиг и массив конфигов (подписка, где каждый элемент — самостоятельный
конфиг). Из конфига приезжает транспортный слой:

что в конфиге что приезжает
outbounds / endpoints узлы всех 13 поддерживаемых типов
urltest / selector группа автовыбора с составом и параметрами
detour цепочка узлов, звеном может быть любой тип
route, dns, inbounds не переносится

Маршрутизацию и DNS приложение генерирует само из своих настроек, поэтому чужие
правила не приезжают — диалог подтверждения перечисляет, какие секции остались
за бортом, а не выбрасывает их молча.

Разбор идёт по тем же правилам, что и разбор конфигов Xray: один и тот же
сервер, описанный в подписке несколько раз, схлопывается в один узел; имя
достаётся той записи, где оно осмысленное; неподдержанный тип не исчезает
молча, а оставляет предупреждение на соседнем узле. Кольцо в цепочках
(A → B → A) разрывается на импорте — конфиг из чужого файла пользователь
чинить не может, и терять из-за кольца узлы было бы неверным разменом.

Вставленный JSON с несколькими узлами теперь становится записью-набором, а не
записью-сервером: с полным списком узлов, выключением по одному и правилами
импорта. Порог тот же, что при импорте файла: одна нода — сервер, несколько —
набор. Раньше конфиг на десяток узлов попадал в контейнер «один сервер» и
подписывался в списке по протоколу первого из них.

Отдельно стоит назвать побочное: массив outbound'ов sing-box в теле подписки
раньше давал ноль узлов молча — распознавание формы работало только для
вставки из буфера. Теперь форма распознаётся одинаково на всех путях, а
предпросмотр показывает ровно то, что приедет: раньше он мог обещать «Outbound
JSON» там, где импорт затем отказывал.

🕒 Новое — rule-set'ы поддерживают себя в актуальном состоянии

Rule-set скачивался один раз и дальше лежал неизменным: приложение его не
перекачивало, ядро в сеть за ним не ходит, а в интерфейсе не было даже даты —
понять, насколько устарела твоя копия, было нельзя.

Теперь у каждого правила свой срок годности. В редакторе правила появилась
настройка «Проверять обновления»: никогда, раз в день, раз в неделю (по
умолчанию), раз в две недели, раз в месяц, раз в полгода, раз в год. У готовых
пресетов интервал уже задан.

Проверка идёт через полминуты после запуска приложения и через полминуты после
подключения VPN — и только пока приложение открыто. Если не удалось, одна
повторная попытка через двадцать секунд, дальше ждём следующего подключения.
Удачная проверка успокаивает механику до перезапуска приложения. Постоянного
фонового таймера нет: будить устройство каждый час ради файла, который меняется
раз в неделю, дороже для батареи, чем оправдано.

Трафика проверка почти не тратит. Приложение спрашивает у сервера, менялся ли
файл вообще, и на неизменившийся rule-set приходит сухое «не менялся» без
содержимого.

Неудачная проверка ничего не ломает. Скачанный файл остаётся на месте, правило
продолжает работать — даже если списка на сервере больше нет вовсе. Устаревший
рабочий список полезнее выключенного правила.

В редакторе правила видно и время последнего обновления, рядом кнопка
«Обновить сейчас». Сам список правил остался как был: только значок
облака — скачано или нет.

🔄 Исправлено — включение подписки могло не доехать до конфига

Включаешь подписку на экране серверов, возвращаешься на главный — а нового
набора нод там нет. Галка «пересобрать конфиг и перезапустить» тоже не спасала:
она срабатывает по отметке «есть незаписанные изменения», а отметка в этом
случае не ставилась.

Причина — гонка с пересборкой. Возврат на главный экран сам запускает
пересборку конфига, и переключение галки попадало в её окно: изменение
применялось к списку, но пересборка успевала прочитать состав до него — а по
завершении снимала отметку «есть изменения» за то изменение, которое в
собранный конфиг не попало. Новый состав доезжал только со следующей случайной
правкой.

Тем же путём терялся и сервер, добавленный во время обновления другой подписки.

Теперь отметка снимается, только если состав под пересборкой не менялся.
Потянуть список вниз было обходным путём — он работал потому, что запускал
пересборку заново, и та читала уже актуальный состав.

🧊 Исправлено — «Подключено» без подключения и мёртвая кнопка «Остановить»

Запускаешь VPN на узле, который не отвечает, и не дождавшись жмёшь
«Остановить» — приложение могло залипнуть: в шапке «Подключено», таймер идёт,
трафика нет, список узлов пуст, а кнопка «Остановить» нажимается впустую. Выйти
можно было только выгрузив приложение из недавних.

Причина в порядке событий. Подключение к неотвечающему узлу удерживает
внутренний вызов на секунды. Остановка в этом окне успевала полностью свернуть
туннель — а следом приходил запоздавший ответ подключения и помечал туннель
работающим, хотя останавливать уже было нечего. Дальше команда «остановить»
уходила в пустоту и через пять секунд отваливалась по таймауту.

Теперь запоздавший ответ проверяет, не отменили ли запуск, и статус не
подменяет. Дополнительно сама остановка распознаёт рассинхрон и завершает
туннель сразу, а не ждёт ответа, которого не будет.

🔁 Ложное окно «Активен другой VPN»

Исправлено там же. Если от прошлой сессии в системе оставался наш собственный
VPN-интерфейс, приложение принимало его за чужой и предлагало переключиться
само на себя — на каждый запуск. Теперь владелец подключения проверяется явно,
и вопрос задаётся только когда VPN действительно принадлежит другому
приложению.

📊 Исправлено — новые узлы оставались без замеров после смены подписки

При включённой галке автоприменения смена подписки перезапускает туннель и
перерисовывает список — но задержки у нового набора узлов не появлялись, пока
проверку не запустишь вручную. При обычном запуске VPN пинг стартует сам.

Автоматический пинг был привязан к моменту подключения. Применение изменений
идёт без разрыва туннеля, подключение при этом заново не происходит — и
запускать замеры оказывалось некому. Вдобавок список узлов, по которому идёт
пинг, наполняет ядро, а после применения изменений на месте это обновление
могло не прийти вовсе: экран показывал новый состав (его рисует сохранённый
конфиг), а пингу доставался пустой список.

Исправлены обе половины: после применения изменений список узлов обновляется от
ядра, и уже затем планируется пинг. Настройка автопроверки узлов уважается на
этом пути ровно так же, как при запуске.

✅ Тесты

2838 тестов зелёные, analyze по всему проекту чист. Новые регрессионные тесты
падают на коде до фикса — каждый проверен откатом своей правки. На обновление
rule-set'ов приходится 29 тестов: условные запросы и обработка ответа «не
менялся», арифметика сроков по каждому интервалу и гарантия, что неудачная
проверка не трогает ни скачанный файл, ни правило.


📲 Install

adb install -r LxBox-v2.19.4-arm64-v8a.apk

Без uninstall! Поверх существующей установки. Настройки и подписки сохранятся.

No uninstall needed — install over the existing one. Settings and
subscriptions are preserved.


Previous release / Предыдущий релиз: v2.19.3.

Don't miss a new LxBox release

NewReleases is sending notifications on new releases.