Я устал от VLESS-клиентов и написал свой. Rust, EvertyUI, Mihomo и немного экспериментов с ИИ - QubStore

Я устал от VLESS-клиентов и написал свой. Rust, EvertyUI, Mihomo и немного экспериментов с ИИ

Содержание статьи

Привет, Хабр!

Меня зовут Артур Валиев и в какой-то момент я поймал себя на мысли, что для подключения к VPN мне приходится открывать приложение, в котором настроек едва ли не больше, чем в самом Windows.

Все проекты с открытым исходным кодом, их можно скачать, изменять, тестировать и делиться. Лицензия MIT, ссылки в конце статьи.

Серверы, группы, правила, режимы, маршруты, переключатели, вкладки, ещё вкладки, настройки вкладок…

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

Не хочу каждый раз вспоминать, где находится нужный переключатель.

Так появился Everty MultiLink — мой клиент для VLESS, который я написал на Rust с использованием собственного интерфейса EvertyUI и сетевого ядра Mihomo. Позже появилась и Android-версия, включая Android TV.

Everty MultiLink

А ещё в процессе я попытался применить к маршрутизации собственный экспериментальный оркестратор ZERO (z3r0). Я провел немало испытаний, и результат получился довольно забавным: сложный алгоритм проиграл простому.

Но обо всём по порядку.

Сначала был телефон. И проект, который я забросил

Некоторое время назад я уже писал на Хабре о своём VLESS-клиенте для Android.

У него была интересная возможность: пользователи могли передавать друг другу конфигурации. Правда, для этого использовался внешний сервер.

Идея мне нравилась, но сам проект постепенно забросил.

В какой-то момент понял, что не хочу возвращаться к этой архитектуре. Зачем мне внешний сервер только для того, чтобы отправить настройки с собственного телефона на собственный телевизор, стоящий в соседней комнате?

Но к этому вопросу я вернулся значительно позже.

Сначала захотелось сделать нормальный клиент для компьютера.

Почему ещё один VLESS-клиент?

Я много работаю за Windows и довольно часто пользуюсь VPN.

Пробовал разные клиенты, включая v2rayN, Furious и другие решения. Ничего плохого о них сказать не хочу: это функциональные программы, за которыми стоит большая работа.

Но меня раздражает сама тенденция.

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

А я люблю, когда наоборот.

Хороший интерфейс — это когда ты один раз указал, чего хочешь, и больше о нём не вспоминаешь.

Например:

Хочу, чтобы programma.exe работала через сервер в Египте.

Указал программу, выбрал Египет. Готово.

Хочу, чтобы Spotify.exe использовал сервер в Нигерии.

Выбрал соответствующий маршрут. Готово.

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

Настроил правила. Забыл.

При этом мне совершенно не хочется, чтобы само приложение постоянно висело огромным окном на рабочем столе или потребляло сотни мегабайт только ради отображения нескольких кнопок.

Так и сформировалось главное требование к MultiLink:

Максимально простой интерфейс поверх достаточно сложной сетевой логики.

Не наоборот.

EvertyUI: когда не хочется тащить браузер ради кнопки

Незадолго до MultiLink я написал EvertySSH — собственный SSH-клиент на Rust и статью про него.

Именно после работы над EvertySSH я окончательно убедился, что хочу продолжать использовать EvertyUI.

Это моя библиотека пользовательского интерфейса, которую я развиваю для собственных приложений.

Её идея достаточно простая: мне не нужен встроенный браузер, JavaScript-движок или большой стек компонентов, чтобы нарисовать окно, несколько карточек, кнопки и список серверов.

В десктопной части MultiLink используются Rust, winit, softbuffer и собственные элементы EvertyUI.

Отрисовка выполняется программно, а интерфейс обновляется по событиям. Нет необходимости непрерывно перерисовывать статичное окно с максимальной частотой кадров.

Я вообще люблю компактные интерфейсы. Особенно в служебных программах.

MultiLink большую часть времени находится в системном трее. Когда нужно что-то изменить, открывается небольшое окно с настройками подключений, серверов и правил.

Я устал от VLESS-клиентов и написал свой. Rust, EvertyUI, Mihomo и немного экспериментов с ИИ

Компактный режим

По моим измерениям, само приложение занимает около 10 МБ оперативной памяти, а вместе с сетевым ядром и сопутствующими службами — порядка 40 МБ.

Конечно, потребление памяти зависит от конфигурации, количества соединений и других условий. Это не универсальная константа.

Но для постоянно работающей фоновой программы результат меня устраивает.

И дело даже не в борьбе за каждый мегабайт.

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

Зачем писать собственный сетевой движок, если существует Mihomo?

А вот здесь я решил не изобретать велосипед.

В MultiLink используется Mihomo — развитое сетевое ядро, известное ранее как Clash Meta.

Оно уже умеет работать с современными прокси-протоколами, группами серверов, правилами маршрутизации и проверками доступности.

Собственно, моя задача состояла не в том, чтобы с нуля переписать VLESS и всю сетевую инфраструктуру.

Мне хотелось построить удобный продукт вокруг возможностей существующего ядра.

Архитектурно десктопный проект разделён на несколько частей:

Everty MultiLink │ ├── multilink-app │ ├── EvertyUI │ ├── Системный трей │ ├── Управление серверами │ └── QR и локальная передача │ ├── multilink-core │ ├── Подписки │ ├── Настройки │ ├── Качество серверов │ └── Правила маршрутизации │ ├── multilink-mihomo │ ├── Генерация конфигурации │ ├── Управление ядром │ ├── REST API │ └── Проверки соединений │ └── multilink-platform ├── Windows API ├── Системный прокси ├── TUN └── Защищённое хранение

Для Windows используются системные механизмы настройки прокси, а для работы в TUN-режиме — соответствующий сетевой адаптер.

Сам Mihomo управляет маршрутизацией, а MultiLink предоставляет более понятный способ описать, что именно пользователь хочет получить.

Маршрутизация: не один VPN для всего интернета

Наверное, это моя любимая часть MultiLink.

Я не хочу постоянно включать и выключать VPN.

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

И можно создавать исключения для конкретных программ.

Например:

Что открываем

Маршрут

Российские сайты

DIRECT

GitHub

Нидерланды

Отдельный зарубежный сервис

Германия

programma.exe

Египет

Spotify

Нигерия

Остальной трафик

Умная группа

Это пример настройки, а не обязательный набор правил приложения.

Здесь важно понимать: выбор сервера определяет сетевой маршрут, но сам по себе не гарантирует доступность сервиса или работу его региональных функций.

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

А в умном режиме MultiLink использует возможности Mihomo по автоматическому выбору доступных узлов.

Для отдельных доменов могут формироваться собственные группы проверки, чтобы проблема с доступностью одного сайта не приводила к ненужному переключению остального трафика.

И всё это скрыто за довольно простым интерфейсом.

Я обещал сделать передачу настроек через QR. Сделал

Однажды в комментариях на Хабре мне предложили сделать удобную передачу конфигураций между устройствами внутри локальной сети.

Я эту мысль запомнил.

Потому что действительно: представьте Android TV.

У вас пульт, экран телевизора и длинная VLESS-подписка.

Нужно либо подключать клавиатуру, либо пытаться вводить ссылку вручную, либо придумывать промежуточный способ передачи.

А ведь рядом лежит телефон, на котором всё уже настроено.

Поэтому в MultiLink я реализовал локальную передачу конфигураций.

Работает это следующим образом.

На телевизоре:

Открываем MultiLink и нажимаем «Принять по QR».

На экране появляется QR-код.

На телефоне:

Выбираем, чем хотим поделиться, сканируем QR-код и отправляем настройки.

На телевизоре:

Получаем конфигурацию.

Всё.

Не нужно подключать мышку. Не нужно вручную набирать длинные ссылки. Не нужно отправлять себе данные через мессенджер.

Передача работает внутри локальной сети.

Причём это пригодится не только для телевизора.

Можно передавать настройки между компьютерами, ноутбуками, телефоном и другими устройствами. Или поделиться конфигурацией с другом, если оба устройства находятся в одной сети.

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

Технически принимающее устройство поднимает временный локальный HTTP-обработчик, а QR содержит адрес сеанса передачи.

При этом локальная передача не равнозначна сквозному шифрованию. Поэтому такой механизм предназначен для доверенной локальной сети, а не для публичного Wi-Fi. Защиту и подтверждение принимающей стороны можно развивать отдельно.

Мне нравится, что здесь интерфейс решает реальную бытовую проблему.

Не очередная функция ради галочки, а вещь, которой действительно удобно пользоваться.

И да, обещание из комментариев я выполнил.

Android и Android TV

Раз уж появилась передача конфигураций на телевизор, нужен был и сам клиент.

Для Android я выбрал Kotlin и нативные компоненты платформы.

Вместо переноса десктопного интерфейса целиком используется отдельная мобильная реализация, учитывающая особенности Android.

Здесь уже другая архитектура: VpnService, TUN, tun2socks, управление жизненным циклом VPN и системными уведомлениями.

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

Для Android TV добавлено управление с пульта.

На телефоне — системные элементы управления подключением, включая плитку быстрых настроек.

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

Нажал несколько кнопок на пульте, принял настройки по QR и пользуешься.

А теперь немного о ZERO. Мой эксперимент с обучаемым оркестратором

До работы над MultiLink я экспериментировал с одной довольно странной идеей.

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

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

Не хотелось просто взять готовую языковую модель, передать ей список инструментов и попросить выбирать подходящий.

Хотелось разобраться в самом принципе.

А что, если система будет учиться выбирать исполнитель не по заранее прописанному правилу, а по стоимости предыдущих решений и их результатам?

Так появился ZERO (z3r0).

Первоначально это вообще не было связано с VPN.

Я экспериментировал с разными исполнителями, стоимостью вычислений, ошибками, восстановлением и ограничениями возможностей.

Это не попытка придумать колесо, просто исследовательский опыт. Похожих проектов в сети много.

Постепенно появились:

  • Модель выбора инструментов по признакам задачи.

  • Многомерные границы применимости.

  • Оценка стоимости ошибочного выбора.

  • Глобальное и контекстное состояние доступности.

  • Повторные проверки после отказов.

  • Планирование измерений с ограниченным бюджетом.

По сути, ZERO стал небольшим обучаемым оркестратором.

Он не генерирует ответы как большая языковая модель. Его задача гораздо уже: решить, какой инструмент использовать и стоит ли тратить ресурсы на получение дополнительной информации.

И когда у меня уже был MultiLink, я подумал: а почему бы не проверить ZERO на реальных сетевых задачах?

Сначала всё выглядело многообещающе

В синтетических экспериментах ZERO научился довольно быстро выбирать серверы.

На принятие одного решения требовалось около микросекунды.

Это, конечно, не рекорд по сравнению с простой эвристикой, которая выполняется за десятки наносекунд. Но для обучаемого маршрутизатора результат вполне приемлемый.

Более того, ZERO учитывал качество серверов, историю отказов и контекст назначения.

После этого построил сравнительный тест с текущим Smart Mode на основе Mihomo.

И тут случилось самое интересное.

Сложный алгоритм проиграл простому

Сравнивал четыре варианта в синтетическом сетевом стенде:

Стратегия

Средняя задержка

Smart Mode, tolerance 100 мс

111,0 мс

Min RTT + Hysteresis 20 мс

84,3 мс

ZERO Multidim

84,4 мс

ZERO Dual Health

85,0 мс

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

ZERO не получил преимущества от своей сложности.

И это был хороший урок.

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

Продолжил исследование и проверил, насколько изменение tolerance помогает на физических VPN-серверах.

Там выигрыш от снижения порога со 100 до 30 мс оказался меньше половины процента.

Рабочую конфигурацию я менять не стал.

А потом ZERO показал кое-что интересное

Вместо выбора сервера я решил поставить для ZERO другую задачу.

Представим, что у нас несколько узлов, которые периодически выходят из строя и восстанавливаются.

Чтобы узнать их состояние, нужно выполнять сетевые проверки.

Но каждая проверка — это время, трафик и дополнительная работа.

Можно ли научиться проверять только тогда, когда информация действительно полезна?

В одном из синтетических экспериментов я сравнил ZERO с обычным периодическим опросом и простой адаптивной эвристикой.

При примерно одинаковом числе проверок получили:

Стратегия

Проверок

Средняя задержка

Simple Adaptive

11,2

64,5 мс

ZERO Recovery-Aware

11,1

56,5 мс

В условиях очень ограниченного бюджета ZERO оказался эффективнее простой эвристики.

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

И вот здесь я принял для себя решение.

ZERO не нужно внедрять в MultiLink только потому, что я его написал.

Уже существующая автоматика Mihomo достаточно хорошо решает свою задачу.

А экспериментальный оркестратор пока лучше оставить исследовательским проектом.

Зато обнаружил интересное свойство: ZERO способен экономнее распределять ограниченный бюджет наблюдений.

Возможно, в будущем это пригодится для других задач — например, для адаптивного выбора вычислительных исполнителей, кодеков или обработки событий.

Но это уже другая история.

А что показало настоящее сетевое профилирование?

После экспериментов с ZERO я решил проверить кое-что более приземлённое.

Почему первый запрос через VPN иногда занимает сотни миллисекунд, а следующий выполняется значительно быстрее?

Для этого я написал отдельные бенчмарки и провёл 100 парных измерений через реальный VPN-сервер.

Получилось следующее:

Метрика

Холодный запрос

Повторный запрос

Среднее время

421,9 мс

80,8 мс

p50

419 мс

81 мс

p95

497 мс

95 мс

Разница огромная — около 341 мс.

Но это не означает, что сам VLESS добавляет 341 мс задержки.

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

Для понимания реального поведения я проверил и Chromium/Edge.

В тесте с последовательными переходами между страницами Wikipedia браузер повторно использовал уже установленные соединения, не создавая новых записей о соединениях в Mihomo на каждом переходе.

Это вполне ожидаемо для современного браузера с HTTP/2.

И здесь я снова отказался от соблазна написать собственный дополнительный пул соединений.

Потому что сначала нужно доказать, что существующее повторное использование соединений действительно недостаточно эффективно.

Отдельно проверил распределение запросов между несколькими VPN-серверами.

В наборе тестов одиночный быстрый сервер оказался лучше Round Robin и Consistent Hashing. Более медленные узлы увеличивали время завершения загрузки страницы.

Но это не универсальный приговор многоканальной маршрутизации: при других ограничениях пропускной способности результаты могут быть другими.

В общем, иногда лучший способ ускорить программу — вовремя перестать добавлять в неё новые алгоритмы =)

Что получилось в итоге

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

Она не пытается стать универсальным центром управления всеми сетевыми технологиями.

Нужно добавить подписку — добавляю.

Нужно направить конкретный сайт через другой сервер — создаю правило.

Нужно исключить программу из VPN или привязать к нужному маршруту — настраиваю один раз.

Нужно перенести конфигурацию на телефон, ноутбук или Android TV — использую QR и локальную сеть.

А большую часть времени приложение просто находится в трее и не мешает работать.

Технически в проекте используются Rust, EvertyUI, Mihomo, Kotlin и штатные сетевые механизмы Windows и Android.

Мне нравится, что всё это можно собрать в довольно компактный продукт.

При этом я не стал переписывать уже существующее сетевое ядро, а сосредоточился на том, что действительно хотелось изменить: пользовательском опыте.

Немного о разработке

За последние годы я пришёл к довольно простому выводу.

Не обязательно изобретать всё с нуля. Но и бояться собственных экспериментов не нужно.

EvertyUI появился потому, что мне хотелось иначе строить интерфейсы.

MultiLink — потому, что мне надоело пользоваться перегруженными VPN-клиентами.

ZERO (z3r0) — потому, что мне стало интересно, может ли небольшая система самостоятельно обучаться выбору подходящих инструментов.

Где-то собственные решения оказываются полезными.

Где-то побеждают уже существующие технологии.

И в этом нет ничего страшного.

Мне даже нравится результат исследования ZERO: я не стал насильно встраивать его в продукт, а честно оставил Mihomo делать то, что он уже делает хорошо.

В конце концов, пользователь не должен платить производительностью или стабильностью за моё желание применить очередной экспериментальный алгоритм.

Everty MultiLink развивается как открытый проект.

Исходный код: https://github.com/vaalimusic/EvertyMultiLink

EvertySSH, с которого для меня начался этот этап работы с EvertyUI: https://github.com/vaalimusic/EvertySSH

ZERO (z3ro) https://github.com/vaalimusic/z3r0 вы можете подробно ознакомиться что это и зачем, так же с массивом тестов и бенчмарков, там не только маршрутизация. В репозитории на данный момент 10 автономных исследовательских работ для изучения адаптивного принятия решений, возможно следующая статья будет как раз про ZERO.

• src/bin/ • ├── zero_context_recovery.rs — Фаза 5: Двойное здоровье (Глобальные vs Контекстуальные автоматические выключатели / предохранители) • ├── zero_multidim_composition.rs — Фаза 4: Многомерная композиция + Автоматические выключатели + Защита от данных вне распределения (OOD Guards) • ├── zero_multidim_robustness.rs — Фаза 4: Стресс-тест на 30+ начальных значениях (seeds) под воздействием нелинейных сдвигов • ├── zero_multidimensional.rs — Фаза 3: Двумерные нелинейные границы и нестабильные вероятностные инструменты • ├── zero_feature_discovery.rs — Фаза 1: Автономное обнаружение признаков (a, b, min, max, sum) • ├── zero_capability_bounds.rs — Фаза 1: Обучение условной компетентности на основе обратной связи от выполнения • ├── zero_adaptive_curiosity.rs — Фаза 2: Зондирование на основе любопытства в условиях нестационарных инструментов • ├── zero_staleness.rs — Фаза 2: Затухание устаревших знаний против исследования, основанного на времени • ├── zero_adaptation.rs — Фаза 2: Онлайн-адаптация после необъявленного отказа инструментов • └── zero_real_tools.rs — Продакшн: Реальные операции на Rust за анонимными интерфейсами

На этом всё, буду рад замечаниям, идеям и предложениям.

Особенно интересно, каких простых возможностей вам не хватает в существующих VLESS-клиентах.

Спасибо, что прочитали.

Артур Валиев, EVERTY.

Ссылка на основную публикацию