Repository navigation
LxBox v2.20.4
L×Box v2.20.4
The VPN core is updated. Three crashes that killed the app outright are gone,
node checks no longer hang forever on a silent server, and the core is back in
step with upstream sing-box after two months of drift. Nothing in the app
itself changed — the entire release is the core underneath.
Обновлено VPN-ядро. Ушли три краша, при которых приложение падало целиком;
проверки нод больше не залипают навсегда на молчащем сервере; ядро снова в ногу
с upstream sing-box после двух месяцев отставания. В самом приложении не
изменилось ничего — весь релиз это ядро под ним.
🇬🇧 English
🐛 Fixed — three crashes
All three came from the field with crash dumps attached, and all three are
older than the release that fixes them.
Switching WiFi↔mobile while connecting killed the app. Whenever the default
network changes, the app performs a routine "reset network" action — that is
what keeps connections alive as you walk out of WiFi range. It was allowed to
run as soon as the core object existed, but a tunnel needs a moment longer to
finish assembling, and during that moment the parts it reaches for are not
there yet. A network change landing inside that window — a few hundred
milliseconds around tunnel start, wider when the core was opening its cache
file — killed the process. Tapping connect at the exact moment the network
switched was enough. Such commands now wait for the tunnel to be genuinely up
and are ignored before that; on a running tunnel behaviour is unchanged.
A connection to a dead node could take down the whole tunnel. A TCP
connection that never completes is an everyday event: subscriptions accumulate
dead nodes, latency tests probe them, a reconnect retries them all at once.
Under a narrow race, cleaning up such a connection released part of its state a
fraction of a second before the connection was formally closed — and a packet
arriving in that gap reached code that assumed the state was still there. The
result was not one dropped connection but a dead VPN. It struck rarely and
unpredictably, and grew far more likely exactly when many connections were
failing at once. A late packet is now ignored instead of crashing.
The tunnel could crash on start. This one was introduced and fixed between
pre-releases, so no shipped version carried it — it is named here because it
explains the release procedure change below.
📡 Fixed — "the ping is lost"
Some servers accept a connection and then go quiet: the handshake is never
answered, but nothing is refused either. A node check that ran into one simply
stopped, with no way to interrupt it — and stopping the VPN did not clear it.
The check kept running in the background, still holding the whole node list it
had started with; the reported dump had two of them alive, aged 100 and 43
minutes, holding 2806 nodes from a subscription that was no longer even loaded.
Because a group waits for all its checks before publishing results, the ping
column stayed empty, and memory climbed until the system started shooting the
app down. Restarting the VPN did not help — only rebuilding the config did.
Three things had to coincide, so all three were fixed: connections of that
transport type now support timeouts at all, cancelling a check now reaches the
handshake it is stuck in, and stopping a group now stops the checks it started.
Nodes that are merely slow are unaffected — the timeout ends only a check that
has genuinely stalled.
🔄 Changed — the core is back in step with upstream
The fork had drifted two months behind SagerNet's sing-box. That gap is now
closed completely: 217 commits, base moved to v1.14.0-beta.8, every
dependency on the exact revision upstream expects. Visible in daily use: a
deadlock in the TUN packet dispatcher, more consistent URLTest results, a race
in DNS rule matching where one rule could block another, several WireGuard
corrections, and refreshed gvisor/QUIC. Where upstream had since solved
something this fork had solved on its own, the upstream version was preferred —
but only after checking it covered the whole case.
Everything the fork adds is intact: the detour chain, the DNS query stream,
URLTest pooling, idle-suspend, AmneziaWG obfuscation, and all eight extended
client commands.
OpenVPN and OpenConnect arrived upstream and are compiled in
(openvpn-client, openconnect). These are new — the fork has no field
experience with them. Treat them as untested until you have run them yourself.
🔧 Process
Two lessons were written into the core's release procedure, both paid for:
- A dependency is verified by machine against the exact revision the build
expects, and its history is taken whole — never cherry-picked by what the
compiler happens to complain about. Taking 3 of 14 commits that way produced
a combination that never existed upstream, and a tunnel that crashed on
start. - Dependencies are brought in step before the merge, not after.
Two places in the core kept breaking in the same way on every upstream merge —
twice in a row, invisibly, caught only by the build and by tests. The cause was
not upstream removing anything: that code does not exist on their side. It was
the shape of our own additions, each sitting where both sides append, so the
merge tool combined them into something neither side wrote, without reporting a
conflict. Both were restructured rather than patched again, so a future
upstream change collides loudly instead of being absorbed silently.
🧪 Verification
The Java surface of the core did not move across this bump: 226 classes at every
step, javap diff of every signature — 0 differing lines, both rc.6→rc.7
and rc.7→rc.8. No client-side change was required. flutter analyze is
clean, 2947 tests pass, all four l10n checkers pass. The build was verified on
device on rc.7; rc.8 on top of it is an internal restructuring with no
behaviour change.
🇷🇺 Русский
🐛 Исправлено — три краша
Все три пришли с поля вместе с крашдампами, и все три старше того релиза,
который их чинит.
Смена WiFi↔мобильной сети в момент подключения роняла приложение. При
каждой смене сети по умолчанию приложение выполняет штатное действие «сброс
сети» — именно оно сохраняет соединения живыми, когда вы выходите из зоны WiFi.
Ему разрешалось выполняться, как только появлялся объект ядра, — но туннелю
нужно ещё время, чтобы собраться до конца, и в эти мгновения тех частей, к
которым действие обращается, ещё не существует. Смена сети, попавшая в это окно
(несколько сотен миллисекунд вокруг старта, шире — когда ядро открывает свой
файл кеша), убивала процесс. Достаточно было нажать «подключить» ровно в момент
переключения сети. Теперь такие команды дожидаются, пока туннель действительно
поднимется, а до того игнорируются; на работающем туннеле поведение прежнее.
Соединение с мёртвой нодой могло уронить весь туннель. TCP-соединение,
которое так и не устанавливается, — событие ежедневное: в подписках копятся
мёртвые ноды, их простукивают тесты задержки, переподключение пробует их все
разом. В узкой гонке при уборке такого соединения часть его состояния
освобождалась на доли секунды раньше, чем соединение формально закрывалось, — и
пришедший в этот зазор пакет попадал в код, считавший, что состояние ещё на
месте. Итогом был не разрыв одного соединения, а мёртвый VPN. Срабатывало редко
и непредсказуемо, а вероятность росла ровно тогда, когда неудачных соединений
было много сразу. Опоздавший пакет теперь игнорируется, а не роняет ядро.
Туннель мог падать при запуске. Этот дефект появился и был исправлен между
пререлизами, так что ни в одну выпущенную версию не попал, — назван здесь
потому, что объясняет изменение процедуры ниже.
📡 Исправлено — «утерялся пинг»
Некоторые серверы принимают соединение и замолкают: рукопожатие остаётся без
ответа, но и отказа нет. Проверка ноды, наткнувшаяся на такой сервер, просто
останавливалась, и прервать её было нечем — а остановка VPN её не убирала.
Проверка продолжала жить в фоне, держа весь список нод, с которым начинала: в
присланном дампе их было две, возрастом 100 и 43 минуты, на 2806 нод от
подписки, которая уже даже не была загружена. Группа перед публикацией
результатов ждёт завершения всех своих проверок — оттого колонка пинга
оставалась пустой, а память росла до того, что система начинала убивать
приложение. Перезапуск VPN не помогал, лечил только пересбор конфига.
Совпасть должны были три вещи, поэтому исправлены все три: соединения этого
типа транспорта теперь вообще умеют таймауты, отмена проверки достигает
рукопожатия, в котором та застряла, а остановка группы останавливает и
запущенные ею проверки. На просто медленные ноды это не влияет — таймаут
завершает только по-настоящему зависшую проверку.
🔄 Изменено — ядро снова в ногу с upstream
Форк отстал от sing-box на два месяца. Отставание закрыто полностью: 217
коммитов, база переставлена на v1.14.0-beta.8, все зависимости — ровно на тех
ревизиях, которых ждёт upstream. Из заметного в повседневной работе:
взаимоблокировка в диспетчере пакетов TUN, более стабильные результаты URLTest,
гонка в сопоставлении DNS-правил, где одно правило могло блокировать другое,
несколько исправлений WireGuard, обновлённые gvisor и QUIC. Там, где upstream к
этому времени исправил то же, что форк решал сам, предпочтён вариант upstream —
но только после проверки, что он покрывает случай целиком.
Всё, что добавляет форк, на месте: цепочка detour, поток DNS-запросов, пул
URLTest, засыпание простаивающих подключений, обфускация AmneziaWG и все восемь
расширенных клиентских команд.
OpenVPN и OpenConnect появились в upstream и включены в сборку
(openvpn-client, openconnect). Это новинка, полевого опыта с ней у форка
нет — считайте её непроверенной, пока не прогоните сами.
🔧 Процесс
В процедуру выпуска ядра записаны два урока, оба оплаченных:
- Зависимость сверяется машинно с той самой ревизией, которую ожидает сборка, а
её история берётся целиком — без выборки по тому, на что ругается
компилятор. Взятые так 3 коммита из 14 дали сочетание, которого у upstream
никогда не было, и туннель, падающий при старте. - Зависимости приводятся в порядок до слияния, а не после.
Два места в ядре ломались одинаково при каждом слиянии с upstream — дважды
подряд, незаметно, ловили только сборка и тесты. Причина не в том, что upstream
что-то удалял: этого кода у него нет. Ломалась форма наших добавок — обе
сидели там, где дописывают обе стороны, и merge склеивал их в то, чего не писал
никто, не сообщая о конфликте. Оба места перестроены, а не залатаны ещё раз:
теперь будущее изменение upstream столкнётся с ними громко, а не поглотит
молча.
🧪 Проверка
Java-поверхность ядра при этом бампе не сдвинулась: 226 классов на каждом шаге,
javap-diff всех сигнатур — 0 строк расхождений, и на rc.6→rc.7, и на
rc.7→rc.8. Правок на стороне клиента не потребовалось. flutter analyze
чист, 2947 тестов проходят, все четыре l10n-чекера зелёные. Сборка проверена на
устройстве на rc.7; rc.8 поверх него — внутренняя перестройка без изменения
поведения.
Install / Установка
adb install -r LxBox-v2.20.4-arm64-v8a.apkБез uninstall! Поверх существующей установки. Настройки и подписки сохранятся.
No uninstall needed — install over the existing one. Settings and
subscriptions are preserved.
Previous release / Предыдущий релиз: v2.20.3.