L×Box: от VPN-клиента до экосистемы управления сетевым трафиком

Я рассказываю тут про развитие VPN-клиента L×Box, его ядра и десктоп-версии.
Новость месяца: L×Box уже доступен в Google Play
Проект быстро развивается: пока я дописывал раздел про форк, ядро проехало три минорных релиза. Пока описывал фичесет приложения, вышло 27 релизов. Так что все технологичекие фишки тут не смогу описать — только главное и интересное, и поговорим про позиционирование!
Как успех порождает цель
Когда я начинал LxBox, у меня была одна простая цель: сделать Android-клиент для sing-box, который устраивает лично меня, и попутно показать, как я умею вести проекты. Первые две статьи (обзор клиента и разбор per-app профайлера) ровно про это: вот фича, вот как она устроена, вот как проверена.
Потом рынок и спрос стал управлять процессом: в июне я форкнул ядро, потому что XHTTP и AmneziaWG в апстрим не войдут никогда, а Xray-серверы и их конфиги набрали популярность. В августе ядро ушло в роутеры, потому что адаптации под телефон и под OpenWrt оказались одними и теми же. Из общения с аудиторией я понял, что люди привыкают к хорошему мгновенно: дал фишку на роутере, уже хотят на телефоне; сделал на телефоне, хотят в десктопе. Это вообще UX тренд такой — мы научились автоматически переносить положительный опыт и ожидания между разными поверхностями взаимодействия.
Оказалось, что классно иметь удобный интерфейс, в котором можно связать воедино трафик из разных источников. Рынок отреагировал быстро: многим нужно управлять всем единообразно, мониторить это все и настраивать, а при этом у всех разные контексты, навыки и ситуации, а потребности схожие. Сделаю пару зарисовок:
Windows 7. Пользователи с семёрки пришли сами, и оказалось, что у них есть удивительно крутые легаси-проекты, от которых зависит их работа, и управляемая сеть нужна им не меньше, чем остальным. Я был удивлён, слушая, какие сложные системы иногда существуют только на семёрке. А про них все забыли: современные клиенты собираются под современные API. Пришлось помочь.
Китай. Пришли китайцы, объяснили мне, что у них вообще принято конфиги в телефоне писать. Сначала не поверил, потом сделал редактор JSON в Android-клиенте. А еще они тут инициировали мне TrustTunnel, так у нас появилась своя реализация.
Вайбкодеры — золотая молодежь, разбираться в технологиях нет времени, они хотят кирпичики, из которых сделают всё что угодно и быстро. Что ж — понимаемо, принимаемо. Для них пришлось создать серию ИИ-документации (для ИИ: люди у них документы уже не читают, читают агенты и им рассказывают) по продукту и API, чтобы их агенты могли сами разобраться или по крайней мере баг-репорт написать автономно.
Европа и Америка этим нужны не прокси, а корпоративная и домашняя сеть. Tailscale и Fortinet SSL VPN, потому что на работе у них популярны эти два. Сделал полную поддержку обоих: узел Tailscale с выбором exit node на лету и вкладкой, где виден путь до каждого устройства, прямой или через ретранслятор (вообще уникальная фишка); FortiGate через OpenConnect со вторым фактором и входом через SAML в браузере. И то и другое встало в общую маршрутизацию рядом с остальными узлами, а не отдельным режимом. По их же запросу WireGuard-узлы научились включаться и выключаться на лету, без перезапуска туннеля: выключенный узел засыпает, его буферы освобождаются, открытые через него потоки рвутся, а включение будит его сразу.

Рынок конкурентный и на нем L×Box конкурирует не только с другими VPN-клиентами. Он объединяет сценарии, для которых пользователь обычно вынужден устанавливать несколько приложений и разбираться в разных моделях настройки. Иными словами я оказался в нужном месте с нужным решением: у меня клиент, который позволяет держать множество разных соединений одновременно, делать их роутинг по приложениям, доменам и зонам, а настраивается всё через интерфейс.
Уникальность L×Box в том, что это одновременно VPN-клиент и прокси-клиент, и всё живёт в одной маршрутизации (оригинальная идея sing-box, которую никто почему-то не раскрывал полностью). VPN-протоколы: WireGuard и AmneziaWG, OpenVPN, SSL VPN через OpenConnect (FortiGate, Cisco AnyConnect, GlobalProtect, F5, Pulse), Tailscale. Прокси-протоколы: VLESS, VMess, Trojan, Shadowsocks, Hysteria2, TUIC, AnyTLS, NaïveProxy, TrustTunnel, SSH, SOCKS, HTTP. Обычно это два разных мира с двумя разными клиентами: корпоративный VPN в одном, домашний прокси в другом. Здесь корпоративный SSL VPN, домашний VLESS и WireGuard до своего роутера управляются в одном месте, типа: «рабочие домены через SSL VPN, остальное через VLESS, домашняя подсеть через WireGuard».
Работа идет по трем направлениям:
sing-box-lx— ядро: глубоко доработанный форк sing-box с AmneziaWG, XHTTP, MASQUE, TrustTunnel, постквантовым VLESS и демономlxdдля роутеров.LxBox — Android-клиент, включая Android TV. Google Play, F-Droid, GitHub.
singbox-launcher — десктопный лаунчер на Go, он же интерфейс к роутеру с
lxd. Windows от 7 до последней, macOS, Linux.
Про ядро позволю себе похвастаться: в августе я писал, что sing-box-lx входит в десятку самых популярных форков sing-box, а сейчас он на четвёртом месте по звёздам. И его берут как основу не только мои клиенты: на нём работают сторонние клиенты и панели, например готовящийся NekoBox 6.
Прогрессивный JPEG
Есть такой способ кодировать картинку: сначала грузится размытый контур, потом он уточняется, и на любом шаге у вас уже целая картинка с разным уровнем детализации, а не её кусочек. Я строю интерфейс и взаимодействие с ним по тем же принципам: пользователь на любом уровне получает рабочий результат, а следующий уровень открывает новый уровень детализации.
Уровень 1. Поставил, нажал Get WARP, нажал Start. Ни ссылки, ни сервера, ни регистрации. Ключ WireGuard генерируется на телефоне, наружу уходит только публичная часть, и через несколько секунд есть рабочий туннель до Cloudflare. Для многих этого достаточно навсегда. Тут автоматически есть и обфускация и выбор оптимальных параметров и новейший протокол MASQUE c нужным диалектом.
Уровень 2. Вставил подписку или ввёл сервер. Ссылка добавляется из буфера, по QR с камеры или файлом. Можно вбить параметры через форму. Пресеты правил стоят с момента установки: российские сайты и приложения напрямую, Tailscale-сети, трафик без владельца и входящие из туннелей закрыты. Направление по умолчанию одно, с автовыбором самого быстрого узла. Настраивать нечего. Большинство пользователей остаются на этом ±уровне.
Уровень 3. Захотел настроить. Экран Routing, четыре вкладки: Directions, Presets, Rules, Tunnel apps. Направление — набор серверов под задачу: «стриминг», «работа», «домой». Правило — исключение из умолчания: «банк напрямую», «рабочие домены через SSL VPN», «дома по Wi-Fi всё напрямую». Tunnel apps решает, какие приложения вообще заходят в L×Box. Три решения, три вкладки, в этом порядке. Опытным сюда же: узел Tailscale добавляется мастером, который сам создаёт DNS-сервер, правило для .ts.net и маршрут в сеть 100.64.0.0/10, а SSL VPN через OpenConnect — со вторым фактором и входом через SAML в браузере. Корпоративная сеть и домашняя подсеть встают в тот же список Направлений, что и обычные серверы.
Уровень 4. Решил разобраться, куда ходит трафик. Профайлер показывает по каждому приложению, какие домены оно открыло, через какой DNS они разрешились и по какому правилу ушли в какое Направление. Это тема второй статьи, повторять не буду. Добавлю только, что с тех пор у каждого узла появилась вкладка Diagnostics: HTTP-проба через конкретный узел с сырым ответом, даже при выключенном VPN.
Уровень 5. Захотел тюнить подписку или узел. У подписки своя вкладка Filters: правила, которые на каждом обновлении сами чинят и отсеивают узлы. Условие по имени или полю узла (содержит, равно, регулярное выражение), действие Disable или Replace с карманами $1/$2. Типичные ситуации, которые решают через фильтр: провайдер переименовал серверы, чуть сломал конфиг, вставил рекламный узел, сломал отпечаток у половины списка, добавить TLS или Jump (relay/detour) Server. Перед сохранением фильтр прогоняется по узлам, и видно, что он зацепил. Через интерфейс можно заставить работать даже бесплатный Proton (WG + AWG 1.5).
Уровень 6. Захотел править конфиг. Это уровень китайцев и вайбкодеров. У каждого узла вкладка Source: то, что приехало из подписки, можно переписать в JSON ядра, и оно уйдёт ядру дословно, с проверкой на Save, диагностика останется, но перейдёт в режим информирования. Для JSON-полей нативный редактор JSON5. Для агентов — Debug API на 127.0.0.1 с Bearer-токеном, выключен по умолчанию, и справочник, написанный так, чтобы его читал агент, а не человек.
Уровень 7. Захотел понять, как это работает. В ядре пишу ликбезы, устроенные по тому же принципу, от «что это» до «какие байты на проводе»: протоколы семейства Xray, AmneziaWG и TrustTunnel. Над ними справочник lx-полей, под ними спеки. Три уровня документации под три уровня погружения читателя: что есть и как включить, как устроено, почему так решили. А над всем этим лежит общий с лаунчером реестр протоколов и корпус разбора и тестирования: снимки 68 публичных подписок, 73 938 узлов, по каждому из которых видно, что приехало из ссылки, во что превратилось для ядра и какое предупреждение получило по дороге. Это и есть полная диагностика разбора, открытая, воспроизводимая, дорабатываемая, проверяемая.
Кручу этот принцип прогрессивного JPEG на все три продукта. Лаунчер и роутер отличаются от телефона по факту лишь тем, что у роутера нет экрана, и первый уровень там — это файл конфига от телефона.
Как защищал пользователей, которые не читают дальше первого уровня
Большинство пользователей не делают различия между VPN-протоколом и прокси. Для них и то и другое — «VPN». Для ядра разница принципиальная, и из неё растут разные угрозы, о которых никто из нормальных людей не думает. Я обнаружил потенциальные проблемы в знакомых сценариях, но не проводил систематического сравнительного аудита клиентов. Думаю, что многие приложения подвержены тем угрозам, которые я ниже опишу.
У прокси каждое соединение открываете и инициализируете вы: телефон → сервер → сайт. Сервер не может позвонить вам обратно, у него нет такого канала.
Врезка для знатоков: имею ввиду, что сервер не получает автоматически IP-маршрут внутрь клиентского устройства, эквивалентный маршруту через поднятый VPN-туннель. Обратные соединения, UDP и другие особенности отдельных протоколов — это все живет в локальном неймспейсе и в общий роутинг не выходит.
У VPN-протокола поднимается туннель, и он двусторонний по определению: WireGuard, OpenVPN, SSL VPN, Tailscale дают серверу или пиру маршрут внутрь вашего устройства. В sing-box этот туннель обслуживает userspace-стек gVisor (ха, а в новой версии его в upstream не будет, так что модель перестроим еще раз), и он принимает входящее соединение на любой адрес. То есть корпоративный шлюз, узел WireGuard из чужой подписки или пир в Tailscale-сети может сам открыть соединение к вашему телефону, к вашей домашней сети за ним или через вас и пойти дальше.
На корпоративном VPN это нормально и даже нужно: так админ ходит на ваш ноутбук. На узле WireGuard из бесплатной подписки это дыра. Управлять этим вручную сложно даже опытному системному администратору: надо знать, какой из двадцати узлов в списке VPN-протокол, а какой прокси, и у каждого решить отдельно. Обычный пользователь этого не сделает никогда.
Поэтому решение принято мной за него. Пресет Tunnel ingress включён всегда по умолчанию, и у существующих установок он добавляется один раз при обновлении. Входящие из любого клиентского туннеля отклоняются, пока у узла не включен один из трех тумблеров явно: Allow access to this device, Expose local network, Allow as gateway. У узла из подписки тумблеров нет вовсе, он всегда закрыт. У своего узла, например WireGuard до домашнего роутера, их можно открыть осознанно. Tailscale пресет не трогает: там входящие управляются правилами Tailscale и о пользователях они заботятся (хотя тоже вопросики, если «друг» дал вам подключение).
Вторая утечка того же класса, про которую тут уже много всего написано утечки через tun0: Приложение, исключённое из VPN, может само привязать сокет к интерфейсу tun0. Такой пакет минует перехват Android, и ядро видит его без владельца: UID неизвестен, имя пакета пустое. Правила по приложению на него не действуют, потому что сравнивать не с чем, и до версии 2.25.13 он уходил на VPN-сервер. Исключённое приложение узнавало адрес вашего сервера и то, что VPN включён. Пресет Unknown traffic ловит такой трафик правилом с инверсией и отправляет напрямую; строгий вариант reject есть, но по умолчанию не стоит (стоит директ): у системных процессов без атрибуции не должно пропадать сетевое соединение.
Проверяется это на устройстве через Termux, исключённый из VPN:
curl --interface tun0 https://ifconfig.me/ipПресет включён — в ответе адрес провайдера. Пресет выключен — адрес VPN-сервера, то есть утечка. Оба пресета появились за последний месяц. Сразу скажу, что не придумал способ не ломать прокси режим. Так что либо прокси режим, либо контроль утечек.
И про то, что уходит с телефона наружу, раз уж речь про утечки. Скрытой телеметрии принципиально — нет, аналитики — нет, никакого деанонимизирования: в APK нет ни одного SDK, который бы это делал, и это проверено перед публикацией на F-Droid. Серверу подписки при обновлении уходят стандартные для панелей заголовки, User-Agent и, если включить, x-hwid. HWID — случайный UUID, сгенерированный при первом запуске, не выведенный ни из IMEI, ни из серийника, ни из чего-либо другого в железе. По умолчанию он не отправляется вовсе; включается, когда панель провайдера без него отказывается отдавать узлы, глобально в App Settings → Subscriptions или у одной подписки в её Fetch identity. У каждой подписки может быть и своя идентичность, свой User-Agent и свой HWID.
Немного о безопасности на роутере: на роутере ядро живёт как демон lxd под root: он поднимает TUN, меняет маршруты, читает логи. Лаунчер ходит к нему по gRPC с mTLS. И тут возникает вопрос, на котором спотыкаются многие панели: если клиент с сертификатом может сказать демону «примени этот конфиг», почему он не может сказать «выполни эту команду»? К несчастью во многих панелях может. А это не правильно, потому что тогда украденный сертификат или скомпрометированный лаунчер равен root-шеллу на роутере. Я провёл границу иначе: демон управляет сетью, и запускает только заранее разрешенные пресеты. Админ при установке перечисляет в daemon.json именованные команды: типа "update": "/usr/bin/sing-box lxd update". Клиент видит список и запускает команду по имени. Передать что-то своё он не может: ни аргумент, ни путь, ни версию. Хочется две вариации — будут две команды, update и update-rc. Это модель sudoers с NOPASSWD: тот, у кого есть root на хосте, перечисляет, что разрешено сетевому клиенту. Варианты «загрузи скрипт и выполни» отсутствуют
Как я веду проект
Это спекдрайвен: каждая фича и каждый нетривиальный баг — спецификация. В приложении счёт задач перевалил за 660, фич в новом формате 31. В ядре отдельный Spec Kit: SPEC → PLAN → TASKS → IMPLEMENTATION_REPORT, с августа по октябрь спеки с 095 по 134. Все цифры в этой статье взяты из отчётов реализации, и там же записано, чего не мерили. Отчёт по режиму авто-группы failover так и говорит: устройство и живые узлы не гонялись, энергоэффект не измерен. Это не скромность, это дисциплина: без строки «не проверено» следующий баг ищется не там.
Значительная часть работы делается в паре с AI-ассистентами по этим спецификациям. Спеку пишу я, ревью делаю я, на устройстве проверяет он и пользователи. Все замеры в статьях снимаю с телефона я. Дисциплина «сначала спека, потом код» во многом и выросла из опыта управления проектами. Контролирую форматы данных, вход выход, контракт, алгоритмы, блоксхемы, логику, приоритеты, обещания. Проблема: читая спеки стал говорить сам как ии, прям реально пугает.
Бывают приколы с тем, что спека фиксирует предположение, вот пример. Когда я прививал AmneziaWG к wireguard-go, в спеке графта я записал как инвариант: «запас в заголовке пакета форк не использует, константу обнуляем». Для WireGuard это было верно (ну я так думал). Спека прошла ревью, код прошёл тесты, и инвариант жил в проекте пока я не стал реализовывать Tailscale! А Tailscale, собирается из того же wireguard-go и на прямом UDP-пути требует этот запас (вот это поворот!). Трафик молчал, DERP работал, неделю я это искал.
Где взять и чего не ждать
sing-box-lx— ядро, GPL-3.0. Спеки, changelog по каждому тегу, справочник lx-полей и три ликбеза лежат в репозитории. Десктопные бинари,libbox.aarдля Android и инсталлятор для OpenWrt — в релизах. Текущая версия, не имеет дрейфа отupstream/stable, что уникально для такого рода форков.singbox-launcher — Windows от 7, macOS, Linux, и он же интерфейс к роутеру.
Чего пока нет и не знаю как сделать (жду подсказок от вас):
режим прокси и контроль утечек из раздела выше несовместимы: либо одно, либо другое (не придумал как и то и то);
приложения внутри Samsung Knox-папки правилам по приложению не видны, без root это не решается;
Известные ограничения:
deny-list приложений вместе с системным «Block connections without VPN» даёт полный блок для приложений из списка: netd отдаёт DNS-сокет под uid приложения, это поведение Android; выход — per-app выключить и завести правило
package_name → direct;режим
failoverне выбирает лучший узел, он просто держит работающее соедиенение, зато не засирает проверками все;
Если у вас есть сеть, где что-то из этого пригодилось — расскажите, если что-то сломалось — issues буду рад решить ваши вопросы!.
Похоже, что следующая задача — выйти за пределы клиентских приложений. Когда на одном устройстве сходятся корпоративный VPN, домашняя инфраструктура и внешние прокси, возникает следующий уровень: централизованное управление конфигурациями, узлами и политиками для целой команды или организации.
Пока это направление, которое я только начинаю исследовать. Мне интересно понять, насколько такая модель востребована у корпоративных пользователей. Если вы решаете похожие задачи, расскажите, как они устроены у вас. Особенно интересно, где заканчиваются возможности обычного VPN-клиента и начинается необходимость в централизованном управлении.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.