Skip to content

Latest commit

 

History

138 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SCVPN

Прозрачный VPN-клиент поверх открытого ядра Xray-core. Делает то же, что Happ / v2rayTun (подписки, VLESS+Reality и др.), но весь код обёртки открыт и лежит здесь — видно, что и куда уходит.

Windows desktop/Windows/ — Python + PySide6, ядро xray.exe рядом
macOS desktop/macOS/ — нативный Swift, TUN через привилегированный демон (Apple Silicon)
Android android/ — Kotlin, то же ядро внутри процесса (libv2ray.aar)
iOS ios/ — SwiftUI, ядро внутри расширения туннеля (NEPacketTunnelProvider). Поддержка прекращена, см. ниже

Что приложение отправляет в сеть (и больше ничего)

  1. Подписку — запрос на твой URL подписки, только когда ты нажал «Обновить». Вместе с запросом уходит идентификатор устройства (x-hwid, ОС, её версия и модель) — этого требуют панели с лимитом устройств, иначе они отдают заглушку вместо серверов. Сам идентификатор машины наружу не уходит: шлём его SHA-256 с солью, см. desktop/Windows/native/hwid.py, desktop/macOS/Sources/SCVPNCore/HWID.swift и android/.../Hwid.kt.
  2. VPN-трафик — через выбранный тобой сервер; этим занимается ядро Xray.
  3. Скачивание ядра (Windows и macOS) — один раз тянет xray + гео-базы с официального GitHub (XTLS/Xray-core), а для TUN — sing-box (SagerNet) и, только на Windows, wintun.dll (wintun.net).
  4. Проверку соединения — при автоподборе отпечатка короткий запрос через твой же сервер (api.ipify.org на десктопе, gstatic.com/generate_204 на Android).

Никакой аналитики, телеметрии, «домашних» серверов и автообновлений нет. Каждый сетевой вызов видно в коде: desktop/Windows/shared/subscription.py, desktop/Windows/native/downloader.py, desktop/Windows/shared/connect.py; android/.../SubscriptionParser.kt, XrayCore.kt.


Архитектура

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

ссылка/подписка → модель Server → конфиг Xray (JSON) → ядро → сервер
                                                        ↑
                                          системный трафик заводится сюда

Общие слои

Общего кода между десктопами больше нет: Windows — это Python и Qt (desktop/Windows/, где shared/ — разбор ссылок и конфигов, native/ — системные вызовы), macOS — нативный Swift (desktop/macOS/) поверх core-swift/, который он делит с iOS. Раньше Qt-версия работала и на macOS, и ради этого Python-код лежал уровнем выше, в общей папке; нативная версия её заменила, и код вернулся туда, где используется.

Одинаковыми остаются не файлы, а решения: одна палитра (её сверяет тест), один набор форм состояний, одна шкала шрифтов и метрик. Что и почему — в docs/ui-design.md.

Слой Windows macOS и iOS Android
Разбор ссылок и подписок shared/subscription.py core-swift/…/Parsing/ SubscriptionParser.kt
Идентификатор устройства native/hwid.py core-swift/…/HWID.swift Hwid.kt
Модель сервера shared/models.py core-swift/…/Models/Server.swift Model.kt
Сборка конфига Xray shared/xray_config.py core-swift/…/XrayConfig/ XrayConfig.kt
Хранение профилей shared/storage.py (JSON в data/) core-swift/…/Storage/ (тот же JSON) Prefs.kt (SharedPreferences)
Туннель AmneziaWG awg_runner.py core-swift/…/AWGRunner.swift AwgProcess.kt

Строка про AmneziaWG в таблице — это запуск общего бинарника awg/, а не три реализации протокола: сам туннель один на все платформы.

macOS и iOS делят один пакет core-swift/ — там же лежат общие проверки. Профили переносятся между всеми четырьмя версиями файлом profiles.json: на десктопе он лежит в папке данных, на телефонах есть «Сохранить в файл» и «Загрузить из файла».

Разбор ссылок покрывает vless://, vmess://, trojan://, ss://, wireguard:// и подписки (обычный список или base64). Модель Server — плоский набор полей ссылки; из неё собирается секция outbounds конфига.

AmneziaWG — единственный протокол не внутри Xray

Ядро Xray умеет wireguard, но не умеет обфускацию Amnezia (Jc, S1…S4, H1…H4). У sing-box то же самое. А H1…H4 подменяют тип сообщения WireGuard, поэтому обычный WireGuard-клиент к такому серверу не подключится вовсе — не «медленнее», а никак, без единой строки в логе.

Поэтому в репозитории есть awg/ — маленький бинарник scvpn-awg на официальном amneziawg-go. Он поднимает туннель юзерспейсным стеком (без TUN и без прав администратора) и отдаёт его локальным SOCKS5:

трафик → инбаунды Xray → outbound socks → scvpn-awg → AmneziaWG → сервер

Xray остаётся впереди и делает всё то же самое: geosite, обход РФ, блокировку рекламы, DNS. Отличие ровно в одном outbound, поэтому маршрутизация, TUN, раздельное туннелирование и системный прокси общие с остальными протоколами. На Windows у этого есть побочный выигрыш: WireGuard работает и в режиме системного прокси, то есть без прав администратора.

Бинарник в репозиторий не кладётся, как и остальные ядра, — собирается из исходников: cd awg && ./build.sh. Подробности — в awg/README.md.

Чем платформы отличаются — тем, как трафик попадает в ядро

Windows (desktop/Windows/) — ядро отдельным процессом, два способа завести трафик:

                    ┌── режим «прокси» ──────────────────────────┐
приложения ─────────┤ системный прокси Windows (реестр)          │
                    │        ↓ 127.0.0.1:HTTP                    │
                    │   xray.exe ── inbound socks+http           │──→ сервер
                    └────────────────────────────────────────────┘

                    ┌── режим «TUN» (нужен админ) ───────────────┐
весь трафик ОС ─────┤ wintun-адаптер ← sing-box.exe              │
                    │        ↓ 127.0.0.1:SOCKS                   │
                    │   xray.exe                                 │──→ сервер
                    └────────────────────────────────────────────┘
  • core_runner.py — запускает и глушит xray.exe, читает его лог;
  • sysproxy.py — правит ключи системного прокси в реестре (без прав админа);
  • tun.py — поднимает sing-box, который создаёт TUN-адаптер и льёт всё в SOCKS-инбаунд Xray; требует администратора, поэтому есть перезапуск с UAC;
  • downloader.py — разовое скачивание ядра и компонентов TUN;
  • connect.py — автоподбор рабочего TLS-отпечатка (см. ниже);
  • ping.py — TCP-замер, запасной путь: основной пинг идёт через ядро (connect.py);
  • paths.py — все пути в одном месте (bin/, data/, %LOCALAPPDATA%\SCVPN);
  • ui/ — интерфейс: main_window.py (экран), widgets.py (кнопка и карточки списка), brandmark.py (фирменный знак), theme.py (палитра и стили).

Важное свойство: и системный прокси, и TUN откатываются при закрытии окна и при падении ядра (closeEvent, _on_state), чтобы не остаться без интернета.

macOS (desktop/macOS/) — тот же Xray отдельным процессом, те же два способа:

                    ┌── режим «прокси» ──────────────────────────┐
приложения ─────────┤ networksetup на активных сервисах          │
                    │        ↓ 127.0.0.1:HTTP                    │
                    │   xray ── inbound socks+http               │──→ сервер
                    └────────────────────────────────────────────┘

                    ┌── режим «TUN» (нужен root) ────────────────┐
весь трафик ОС ─────┤ utun-адаптер ← sing-box (от root)          │
                    │        ↑ поднимает демон по unix-сокету    │
                    │        ↓ 127.0.0.1:SOCKS                   │
                    │   xray (от пользователя)                   │──→ сервер
                    └────────────────────────────────────────────┘

Отличие от Windows одно, и оно про надёжность. TUN требует root, а если приложение упадёт, снять root-овый sing-box будет некому: он останется держать маршруты, и весь трафик системы уйдёт в мёртвый туннель. Поэтому вместо запроса пароля на каждое подключение здесь стоит LaunchDaemon: приложение держит с ним открытый unix-сокет, и обрыв этого соединения демон читает как «приложение мертво» — и снимает туннель сам, через секунду, а не при следующем запуске.

Демон не принимает готовый конфиг: только параметры, каждый проверяет, конфиг собирает сам, и запускает лишь бинарники из своей root-овой папки. Сокет открыт группе admin, и всё, что оттуда приходит, считается недоверенным — см. helper/config.py и helper/daemon.py.

Режим прокси root не требует: networksetup доступен администратору без пароля. Прежние настройки прокси пишутся на диск перед включением, поэтому откат переживает падение приложения. Откатываем при этом только своё: снимок на диске — он же признак «прокси ставили мы», а порт в нём сверяется с тем, что стоит в системе. Иначе SCVPN стирал бы настройки другого клиента (Happ, Tailscale и подобные тоже живут на 127.0.0.1) просто по факту запуска.

Android (android/) — ядро внутри процесса приложения, трафик один способ:

весь трафик ОС → VpnService (TUN, fd) → libhev-socks5-tunnel (.so)
                                              ↓ 127.0.0.1:10808 SOCKS
                                        Xray внутри процесса (libv2ray.aar) ──→ сервер
  • ScVpnService.kt — поднимает TUN через VpnService.Builder, стартует ядро, запускает hev-мост; подъём вынесен в отдельный поток, потому что автоподбор отпечатка ходит в сеть, а onStartCommand выполняется на главном;
  • XrayCore.kt — обёртка над libv2ray.aar (запуск, остановка, замер задержки);
  • TProxyService.kt — JNI-обёртка над libhev-socks5-tunnel.so. Единственный класс в чужом пакете com.v2ray.ang.service: под это имя .so был собран, RegisterNatives ищет именно его;
  • VpnState.kt — состояние туннеля и его рассылка в экран broadcast-ом, чтобы кнопка показывала реальное состояние, а не догадку по таймеру;
  • MainActivity.kt — единственный экран.

Петля исключена через addDisallowedApplication(packageName): трафик самого Xray к серверу идёт мимо TUN, иначе он заворачивал бы сам себя.

Автоподбор TLS-отпечатка

В свежих сборках Xray отпечаток chrome шлёт пост-квантовую кривую, которую часть серверов не понимает, а randomized нестабилен. Поэтому перед подключением клиент быстро пробует firefox/safari/edge/ios и берёт первый рабочий. На десктопе (Windows и macOS) это общий shared/connect.py (можно зафиксировать в меню «Отпечаток TLS»), на Android — sanitizeFingerprint() плюс перебор в XrayCore.pingServer.


Сборка

Подробности — в desktop/Windows/README.md, desktop/macOS/README.md и android/README.md. Коротко:

# Windows: exe + установщик
cd desktop\Windows
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
build.bat            # dist\SCVPN\SCVPN.exe
build_installer.bat  # dist_installer\SCVPN-Setup-*.exe
# macOS (Apple Silicon): SCVPN.app
cd desktop/macOS
./build.sh           # dist/SCVPN.app
# Android: APK
cd android
build_apk.bat        # app\build\outputs\apk\debug\app-debug.apk

Сторонние бинарники (libv2ray.aar, libhev-socks5-tunnel.so, xray/xray.exe, sing-box/sing-box.exe, wintun.dll) в репозиторий не кладутся — откуда их взять написано в README соответствующей папки.

iOS: поддержка прекращена

Версия написана целиком и работает — серверы, подписки, пинг, автоподбор отпечатка, QR, перенос профилей, — кроме самого туннеля.

Причина не в коде. Расширению нужен entitlement com.apple.developer.networking.networkextension, а его Apple выдаёт только участникам Developer Program ($99 в год). Бесплатный Apple ID отвечает прямо: «Personal development teams do not support the Network Extensions capability», и профиль не подписывается. Держать платную подписку и продлевать её каждый год ради одной платформы мы не готовы, поэтому версия дальше не развивается.

Код остаётся в репозитории: он собирается, проверки зелёные, приложение ставится на устройство (ios/run-on-device.sh) и работает без подключения. Если аккаунт появится, доводить нужно с того места, где остановились, — Фаза 0 плана: подпись с entitlement, замер памяти расширения, проверка туннеля на живом устройстве. Сама схема «ядро в процессе + мост + TUN» проверена живьём на Android и работает.

Windows, macOS и Android поддерживаются как прежде.

Фирменный знак

Одна траектория из двух касающихся дуг, обведённая штрихом с круглыми концами.

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

Платформа Файл Что внутри
Windows desktop/Windows/setup/scvpn.ico 16, 32, 48, 64, 128, 256 px
macOS desktop/macOS/Resources/scvpn.icns 16…1024 px, включая @2x
Android res/mipmap-anydpi-v26/ic_launcher.xml + res/mipmap-*/ic_launcher.png адаптивная векторная, PNG 48…192 px — fallback для API 24-25
iOS ios/SCVPN/Assets.xcassets/AppIcon.appiconset/icon-1024.png 1024 px, остальное режет Xcode

Скриптов, которые их рисуют, в проекте нет: знак меняется раз в несколько лет, и держать ради этого генератор, Pillow в зависимостях и шаг в сборке дороже, чем перерисовать иконку руками, когда понадобится. У macOS отличие от Windows-иконки одно: плашка занимает 80 % холста, вокруг прозрачное поле — иначе иконка в доке выглядит крупнее соседних.

В интерфейсе тот же знак рисуется векторно (desktop/Windows/ui/brandmark.py, core-swift/.../BrandmarkShape, res/drawable/ic_launcher_foreground.xml) — это не иконка, а элемент экрана: он перекрашивается по состоянию и тянется на любой размер, поэтому картинкой его заменять нечем.

Геометрия во всех местах одна и та же, поэтому знак нигде не разъезжается.

Планы

  • iOS-клиент
  • Свой транспортный протокол
  • Эксперименты со своим ядром вместо Xray

Лицензии

Обёртка — код этого репозитория. Используемые открытые проекты: Xray-core (MPL-2.0), sing-box (GPL-3.0), hev-socks5-tunnel (MIT), wintun (GPL-2.0).

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages