diff --git a/README.md b/README.md index b67dfdb75..be281c235 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,18 @@ # NekoBox for Android [![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21) -[![Releases](https://img.shields.io/github/v/release/MatsuriDayo/NekoBoxForAndroid)](https://github.com/MatsuriDayo/NekoBoxForAndroid/releases) [![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-orange.svg)](https://www.gnu.org/licenses/gpl-3.0) -[![Contributors](https://img.shields.io/github/contributors/starifly/NekoBoxForAndroid)](https://github.com/starifly/NekoBoxForAndroid/graphs/contributors) -## 免责声明 +## Disclaimer -> 免责声明:本项目仅用于技术研究与代码学习之目的,不提供任何形式的网络代理服务。请勿将本项目用于违反当地法律法规的任何活动。请勿在生产环境中使用本项目,使用者应自行承担使用本项目可能带来的全部风险。若您下载或引用本项目,请在 24 小时内自行删除相关内容,并避免长期存储、分享或传播本项目的任何部分。**作者保留随时修改、更新或移除本项目及其内容的权利,恕不另行通知。** -> -> Disclaimer: This project is intended solely for technical research and code learning purposes and does not provide any form of network proxy service. Please do not use this project for any activities that violate local laws and regulations. Do not use this project in production environments. Users are fully responsible for any risks that may arise from using this project. If you download or reference this project, please delete all related content within 24 hours and avoid long-term storage, distribution, or dissemination of any part of this project. **The author reserves the right to modify, update, or remove any part of this project or its contents at any time without prior notice.** +> This project is intended solely for technical research and code learning purposes and does not provide any form of network proxy service. -## 下载 / Downloads - -[![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/NekoBoxForAndroid/total?label=downloads-total&logo=github&style=flat-square)](https://github.com/starifly/NekoBoxForAndroid/releases) - -[GitHub Releases 下载](https://github.com/starifly/NekoBoxForAndroid/releases) - -**Google Play 版本自 2024 年 5 月起已被第三方控制,为非开源版本,请不要下载。** +## Downloads **The Google Play version has been controlled by a third party since May 2024 and is a non-open source version. Please do not download it.** -## 更新日志 & Telegram 发布频道 / Changelog & Telegram Channel - -https://t.me/Matsuridayo - -## 项目主页 & 文档 / Homepage & Documents - -https://matsuridayo.github.io - -## 支持的代理协议 / Supported Proxy Protocols +## Supported Proxy Protocols * SOCKS (4/4a/5) * HTTP(S) @@ -45,14 +27,15 @@ https://matsuridayo.github.io * ShadowTLS * TUIC * Juicity -* Hysteria 1/2 +* Hysteria 1/2 (including Gecko obfuscation, bundled) * WireGuard +* MasterDnsVPN (DNS-tunnel, bundled) +* Mieru (bundled) * Trojan-Go (trojan-go-plugin) * NaïveProxy (naive-plugin) -* Mieru (mieru-plugin)
-XHTTP Extra TLS配置示例 +XHTTP Extra TLS configuration example

 {
@@ -127,7 +110,7 @@ https://matsuridayo.github.io
 
-XHTTP Extra Reality配置示例 +XHTTP Extra Reality configuration example

 {
@@ -205,42 +188,15 @@ https://matsuridayo.github.io
 
-请到[这里](https://matsuridayo.github.io/nb4a-plugin/)下载插件以获得完整的代理支持. - -Please visit [here](https://matsuridayo.github.io/nb4a-plugin/) to download plugins for full proxy -supports. - -## 支持的订阅格式 / Supported Subscription Format - -* 一些广泛使用的格式 (如 Shadowsocks, ClashMeta 和 v2rayN) -* sing-box 出站 +Trojan-Go and NaïveProxy require their external plugins for full proxy support. -仅支持解析出站,即节点。分流规则等信息会被忽略。 +## Supported Subscription Format * Some widely used formats (like Shadowsocks, ClashMeta and v2rayN) * sing-box outbound Only resolving outbound, i.e. nodes, is supported. Information such as diversion rules are ignored. -## 捐助 / Donate - -
- -如果这个项目对您有帮助, 可以通过捐赠的方式帮助我们维持这个项目. - -捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里, -欢迎联系我们补充. - -Donations of 50 USD or more can display your avatar on -the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please -contact us to add it. - -USDT TRC20 - -`TFVcx36pVLuCWLbWiMdT5KP2PsfQ2SJVEZ` - -
- ## Credits Core: diff --git a/docs/maintenance/dependency-audit.md b/docs/maintenance/dependency-audit.md deleted file mode 100644 index 5623b7d55..000000000 --- a/docs/maintenance/dependency-audit.md +++ /dev/null @@ -1,210 +0,0 @@ -# Dependency Audit - -Branch: `maintenance/dependency-audit` -Date: 2026-06-15 -Base: `hawkff/NekoBoxForAndroid` (fork of `starifly/NekoBoxForAndroid`, itself a fork of `MatsuriDayo/NekoBoxForAndroid`) -Scope: **documentation only — no functional changes.** This file identifies update targets and risky areas so that follow-up branches can be small and reversible. - -> "Latest safe target" cells marked `VERIFY` could not be confirmed from inside this environment (no package-index access at audit time). Confirm the newest stable release before bumping, then record the exact version in the update branch PR. - ---- - -## Current app identity - -Source: `nb4a.properties`, `buildSrc/src/main/kotlin/Helpers.kt` - -| Field | Value | Source | -|---|---|---| -| Package name | `com.nb4a` | `nb4a.properties:1` | -| Version name | `1.4.2-mod-12` | `nb4a.properties:2` | -| Pre-release version name | `pre-1.4.2-20260214-1` | `nb4a.properties:3` | -| Version code (raw) | `46` | `nb4a.properties:4` | -| Version code (effective) | `230` (raw × 5) | `Helpers.kt:146` | -| Android namespace | `io.nekohasekai.sagernet` | `app/build.gradle.kts:30` | -| Debug app id suffix | `.debug` | `Helpers.kt:97` | -| ABI splits | armeabi-v7a, arm64-v8a, x86, x86_64 (no universal APK) | `Helpers.kt:169-177` | -| Product flavors | oss, fdroid, play, preview | `Helpers.kt:180-191` | - -### Signing behavior -- Release keystore is `release.keystore` at repo root, configured only when `KEYSTORE_PASS` is present (`Helpers.kt:118-139`). -- Credentials come from `local.properties` or env (`KEYSTORE_PASS`, `ALIAS_NAME`, `ALIAS_PASS`). -- **Both** release and debug build types are signed with the release key when it exists (`Helpers.kt:133-138`). -- CI injects `local.properties` via base64 `LOCAL_PROPERTIES` secret and passes keystore secrets per build (`.github/workflows/release.yml:64-68`). -- **Caveat (inherited from starifly):** package name `com.nb4a` (upstream is `moe.nb4a`) and a different signing key mean APKs from this fork will not update over upstream/other-fork installs. - ---- - -## Android build stack - -Sources: `gradle/wrapper/gradle-wrapper.properties`, `buildSrc/build.gradle.kts`, `build.gradle.kts`, `Helpers.kt`, `.github/workflows/*`, `buildScript/init/*` - -| Component | Current | Latest safe target | Risk | Branch | -|---|---|---|---|---| -| Gradle wrapper | 8.10.2 | VERIFY (8.10.x/8.11.x) | Low | update-build-toolchain | -| Android Gradle Plugin | 8.8.1 | VERIFY | Medium (AGP majors change DSL) | update-build-toolchain | -| Kotlin | 2.0.21 | VERIFY | Medium (KSP must match) | update-build-toolchain | -| KSP | 2.0.21-1.0.27 | VERIFY (must pair with Kotlin) | Medium | update-build-toolchain | -| compileSdk / targetSdk | 35 / 35 | 35 (or VERIFY 36) | Medium (targetSdk bump = behavior changes) | update-build-toolchain | -| minSdk | 21 | keep 21 | n/a | — | -| buildToolsVersion | 35.0.1 | VERIFY | Low | update-build-toolchain | -| NDK | 25.0.8775105 | VERIFY (pinned in CI + env) | Medium (core build) | update-build-toolchain / core branch | -| Java (source/target) | 1.8 | keep 1.8 (desugar enabled) | Low | — | -| Java (CI runner / Go build) | 17 implied via runners | VERIFY | Low | update-build-toolchain | -| core library desugaring | desugar_jdk_libs 2.0.3 | VERIFY | Low | update-build-toolchain | - -NDK is pinned in three places that must stay in sync: `.github/workflows/build.yml:63`, `.github/workflows/release.yml:65` (`ndk/25.0.8775105`) and `buildScript/init/env_ndk.sh:13`. - ---- - -## App dependencies - -Source: `app/build.gradle.kts:41-87` - -| Dependency | Current | Target | Reason | Risk | -|---|---|---|---|---| -| kotlinx-coroutines-android | 1.6.4 | VERIFY (1.8.x+) | Old; aligns with Kotlin 2.0 | Medium | -| androidx.core:core-ktx | 1.9.0 | VERIFY | Routine | Low | -| androidx.recyclerview | 1.3.0 | VERIFY | Routine | Low | -| androidx.activity:activity-ktx | 1.10.1 | VERIFY | Already fairly new | Low | -| androidx.fragment:fragment-ktx | 1.5.6 | VERIFY | Lags activity | Low | -| androidx.browser | 1.5.0 | VERIFY | Routine | Low | -| androidx.swiperefreshlayout | 1.1.0 | (latest stable) | Routine | Low | -| androidx.constraintlayout | 2.1.4 | VERIFY | Routine | Low | -| androidx.navigation:*-ktx | 2.5.3 | VERIFY | Routine | Low | -| androidx.preference:preference-ktx | 1.2.0 | VERIFY | Routine | Low | -| androidx.appcompat | 1.6.1 | VERIFY | Routine | Low | -| androidx.work:work-* | 2.8.1 | VERIFY | Routine | Low | -| com.google.android.material | 1.8.0 | VERIFY | Routine | Low | -| com.google.code.gson | 2.9.0 | 2.11.0 (VERIFY) | Maintenance/security | Low | -| zxing-lite | 2.1.1 | VERIFY | QR scanning | Low | -| blacksquircle editorkit/language-* | 2.6.0 | VERIFY | Config editor | Low | -| **com.squareup.okhttp3:okhttp** | **5.0.0-alpha.3** | **5.0.0 stable (VERIFY)** | **On an alpha; move to stable line** | **Medium** | -| **org.yaml:snakeyaml** | **1.30** | **2.x (VERIFY)** | **Security (see below)** | **Medium** | -| material-about-library | 3.2.0-rc01 | VERIFY | About screen | Low | -| process-phoenix | 2.1.2 | VERIFY | Process restart | Low | -| kryo | 5.2.1 | VERIFY (hold — profile serialization) | **Do NOT bump casually**; kryo wire format backs profile blobs | Medium | -| guava | 31.0.1-android | VERIFY | Routine | Low | -| ini4j | 0.5.4 | VERIFY | Config parse | Low | -| recyclerview-fastscroll | 2.0.1 | VERIFY | UI | Low | -| androidx.room:room-* | 2.6.1 | VERIFY (2.6.x) | DB; bump cautiously | Medium | -| Roomigrant (MatrixDev) | 0.3.4 | hold | Schema migration generator | Medium | -| desugar_jdk_libs | 2.0.3 | VERIFY | Desugaring | Low | - -Notes: -- `kryo` and `Room` touch persisted user data. Treat both as **migration-sensitive**, not routine bumps. -- okhttp is on an **alpha** (`5.0.0-alpha.3`); prefer moving to a stable 5.0.0 once verified, in its own commit, because the API surface changed across the 5.0 alphas. - ---- - -## Core dependencies - -Sources: `libcore/go.mod`, `buildScript/lib/core/get_source.sh`, `buildScript/lib/core/get_source_env.sh`, `.github/workflows/*` - -The core is **not a normal Go-module update**. `libcore/go.mod` replaces sing-box and libneko with local checkouts, and `get_source.sh` clones pinned commits of `starifly/sing-box` and `starifly/libneko`. - -```text -go.mod replaces (libcore/go.mod:93-97): - github.com/matsuridayo/libneko => ../../libneko - github.com/sagernet/sing-box => ../../sing-box - github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.8-mod.1 -``` - -| Component | Current | Target | Reason | Risk | -|---|---|---|---|---| -| starifly/sing-box commit | `4998428a136368500428a6a35cdd466a7042c75b` | VERIFY (commit with HY2 Gecko) | **Required for Gecko obfs** | High | -| starifly/libneko commit | `1c47a3af71990a7b2192e03292b4d246c308ef0b` | hold unless needed | Keep separate from sing-box bump | High | -| starifly/sing-vmess | `v0.2.8-mod.1` | hold | Fork pin | Medium | -| Go toolchain (go.mod) | go 1.24.7 / toolchain 1.24.9 | VERIFY | **Mismatch: go.mod declares 1.24.x, CI uses ^1.25 (see next row)** | Medium | -| Go toolchain (CI) | `^1.25` (`build.yml:31`, `release.yml:33`) | align with go.mod | **Mismatch: CI uses ^1.25, go.mod declares 1.24.x** | Medium | -| sagernet/sing | v0.7.18 | follow sing-box | Transitive via fork | Medium | -| sagernet/quic-go | v0.52.0-sing-box-mod.3 | follow sing-box | QUIC/Hysteria path | High | -| sagernet/sing-quic | v0.5.2 | follow sing-box | **Hysteria2 lives here** | High | -| sagernet/sing-tun | v0.7.10 | follow sing-box | TUN | Medium | -| dyhkwong/sing-juicity | v0.0.3 | hold | Juicity protocol | Medium | -| reF1nd/sing-snell | v0.0.6 | hold | Snell protocol | Medium | -| anytls/sing-anytls | v0.0.11 | hold | AnyTLS | Medium | -| metacubex/mihomo | v1.19.13 | hold | Clash compat | Medium | - -> **Gecko finding (confirmed):** at the pinned commit `4998428…`, `option/hysteria2.go` defines `Hysteria2Obfs` with only `Type` and `Password` fields — **no `min_packet_size` / `max_packet_size`.** Gecko obfs is therefore **not supported by the current core**. This validates the plan: `core/singbox-gecko-base` must land (pin bump or backport) before any Android Gecko UI work. - ---- - -## Security-sensitive findings - -### 1. Unsafe YAML deserialization (Medium/High) -- `app/build.gradle.kts:69` — `org.yaml:snakeyaml:1.30`. -- `app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt:255-259` — uses default `Yaml().loadAs(text, Map::class.java)` on **untrusted remote subscription content** (fetched at `RawUpdater.kt:80`). -- The default constructor allows arbitrary Java type instantiation (CVE-2022-1471 class of gadget). The `Map` cast happens *after* dangerous construction, so it does not mitigate. -- **Fix direction (separate security branch):** use `Yaml(SafeConstructor(LoaderOptions()))` and/or upgrade to SnakeYAML 2.x with a safe loader. Not part of the toolchain branch. - -### 2. WebDAV credentials in plaintext + http:// allowed (Medium) -- Keys: `Constants.kt:184-187`. Storage: `DataStore.kt:302-316` — `webdavPassword` stored as plain string in `configurationStore` (no EncryptedSharedPreferences/Keystore). -- Basic auth sent at `BackupFragment.kt:237-239,267-269,287-289,361-363`; `WebDAVSettingsActivity.kt:142-144,180-182`. -- `http://` accepted: `BackupFragment.kt:212-213,342-343` (scheme check accepts both); `WebDAVSettingsActivity.kt:130` uses `URL(server)` with no scheme restriction. -- **Risk:** an `http://` endpoint transmits Basic-auth creds and the full secrets-bearing backup in cleartext, with no warning. -- **Fix direction:** warn/deny on non-TLS; consider encrypting stored creds. - -### 3. LAN-exposed inbound can be unauthenticated (Medium) -- `ConfigBuilder.kt:143` — `bind = if (!forTest && DataStore.allowAccess) "0.0.0.0" else 127.0.0.1`. -- Inbound auth gate `DataStore.kt:155-160` — `mixedInboundNeedsAuth` is false when `appendHttpProxy` is active on Android Q+. -- **Risk:** `allowAccess=true` (bind `0.0.0.0`) **and** `appendHttpProxy` on Q+ ⇒ proxy exposed to LAN with no auth. The bind decision and auth decision are independent/uncoupled. -- **Fix direction:** force auth whenever bind is `0.0.0.0`, regardless of `appendHttpProxy`. Note: starifly already added an `appendHttpProxy` confirmation prompt; this is a separate, deeper coupling fix. - -### 4. Swapped geosite/geoip fallback URLs (Bug, Low-Medium) -- `DataStore.kt:135-136`: - - `rulesGeositeUrl` default → `…/sing-geoip/releases/latest/download/geoip.db` (wrong; points at geoip) - - `rulesGeoipUrl` default → `…/sing-geosite/releases/latest/download/geosite.db` (wrong; points at geosite) -- The two defaults are swapped; resetting to custom defaults would populate geosite with geoip data and vice-versa, breaking routing. -- **Fix direction:** swap the two default URLs back. Small, isolated fix — good candidate for its own tiny PR. - -### 5. DNS/routing defaults (informational) -- `DataStore.kt`: `remoteDns` `https://dns.google/dns-query` (`:124`), `directDns` `https://223.5.5.5/dns-query` (`:125`), `enableDnsRouting=true` (`:126`), `enableFakeDns=true` (`:127`), `ipv6Mode=DISABLE` (`:177`), `bypass=true` (`:181`), `strictRoute=true` (`:188`). Consumed in `ConfigBuilder.kt:144-153`. - ---- - -## Feature-readiness notes (for later branches) - -### Hysteria2 Gecko obfs -- Data model: single `HysteriaBean.obfuscation` String (`HysteriaBean.java:24`); **no obfs-type field**. kryo serialization version is `7` (`HysteriaBean.java:85`) → bumping requires deserialize handling. -- Emission point: `HysteriaFmt.kt:328-333` hardcodes `type = "salamander"`. -- URI parse/build: `HysteriaFmt.kt:95-97` (parse `obfs-password`), `:159-162` (emit `obfs=salamander`). -- UI: `hysteria_preferences.xml:30-35` + `HysteriaSettingsActivity.kt` — single password field, **no type selector**. -- SingBoxOptions Java mirror: `SingBoxOptions.java:588-594` `Hysteria2Obfs{ type, password }` — would need `min_packet_size`/`max_packet_size` added once core supports them. -- **Blocker:** core pin lacks Gecko (see Core section). Do `core/singbox-gecko-base` first. - -### Mieru (already plugin-based) -- Existing: `MieruBean.java`, `MieruFmt.kt` (`buildMieruConfig` emits standalone mieru JSON), `MieruSettingsActivity.kt`, `mieru_preferences.xml`. -- Launch path: external plugin `mieru-plugin` / package `moe.matsuri.exe.mieru` (`PluginEntry.kt:17-26`). -- **Native goal = replace the external plugin** with a bundled binary/sidecar reusing the existing `buildMieruConfig` JSON. Bean fields today: `protocol` (TCP/UDP), `username`, `password`, `mtu` — fewer than the mbox outbound schema; extend as needed. - -### MasterDnsVPN -- Not present in the codebase; greenfield. Plan as a SOCKS5 sidecar (new `MasterDnsVpnBean` + config generator + process runner + packaged binaries). - ---- - -## Recommended update branches (in order) - -1. `maintenance/update-build-toolchain` — Gradle/AGP/Kotlin/KSP/AndroidX/CI only. No core, no protocol, no DB/serialization. Also fix the CI/go.mod Go-version mismatch here (or in the core branch). -2. `fix/geo-fallback-urls` — swap the two default URLs in `DataStore.kt:135-136`. Tiny, isolated. -3. `security/yaml-safe-load` — SnakeYAML safe loader + version bump. -4. `security/webdav-tls-and-secrets` — warn/deny http://, consider credential encryption. -5. `security/inbound-auth-coupling` — require auth when binding 0.0.0.0. -6. `core/singbox-gecko-base` — bump or backport sing-box for HY2 Gecko (blocks Gecko UI). -7. `feature/hy2-gecko-obfs` — model/parse/export/UI/config-builder (depends on #6). -8. `feature/mieru-native-sidecar` — bundle mieru binary, drop external-plugin requirement. -9. `feature/masterdnsvpn-client-sidecar` — new SOCKS5 sidecar client. - ---- - -## Acceptance criteria for this audit branch -- [x] No functional app changes (docs-only). -- [x] Update targets identified; risky areas flagged. -- [x] Core-source pins documented separately from Gradle/Kotlin/app deps. -- [x] Gecko core gap confirmed against the actual pinned commit. -- [ ] CI still builds (run after pushing; this branch only adds a markdown file). - -## Open items to verify before bumping -- Exact latest-stable versions for every `VERIFY` cell (no index access at audit time). -- Whether targetSdk 36 is required by any store deadline. -- The specific starifly/sing-box commit (or upstream sing-box ≥ 1.14.0) that introduces HY2 Gecko `min_packet_size`/`max_packet_size`. -- Resolve CI Go `^1.25` vs `go.mod` `go 1.24.7` mismatch. diff --git a/docs/maintenance/hysteria2-gecko-deferral.md b/docs/maintenance/hysteria2-gecko-deferral.md deleted file mode 100644 index 3e0a4660e..000000000 --- a/docs/maintenance/hysteria2-gecko-deferral.md +++ /dev/null @@ -1,54 +0,0 @@ -# Hysteria2 Gecko obfs — deferral note - -Date: 2026-06-15 -Status: **Deferred** (blocked on upstream availability) - -## Goal -Add Hysteria2 "Gecko" QUIC obfuscation (from Hysteria app/v2.9.2) to this fork — -`obfs.type=gecko` with `min_packet_size` / `max_packet_size`. - -## Why it is deferred -Gecko obfs does **not** exist in any stable sing-box / sing-quic release, nor in this -fork's pinned core. It currently lives only in pre-release code: - -| Component | This fork's pin | Where Gecko actually is | -|---|---|---| -| sing-box | `starifly/sing-box` @ `4998428…` = **1.12.19-neko-1** (Salamander only) | upstream **`v1.14.0-alpha.31`** (`Hysteria2ObfsGecko`, `Hysteria2ObfsTypeGecko`) | -| sing-quic | `v0.5.2` (only `salamander.go`) | untagged commit **`9467ede27fb7`** (`hysteria2/gecko.go`), required by sing-box 1.14.0-alpha.31 (`sing-quic v0.6.2-0.20260525051024-9467ede27fb7`) | - -Verified (2026-06-15): -- `option/hysteria2.go` on sing-box `main` has **no** gecko reference; only `v1.14.0-alpha.31` does. -- sing-quic tags up to `v0.6.1` / `main` contain only `salamander.go`; `gecko.go` appears - only in commit `9467ede27fb7`. -- The runtime obfuscator lives in sing-quic, not sing-box — so this is a sing-quic feature, - not just a sing-box option struct. - -## Options considered -- **A. Pin to upstream sing-box 1.14.0-alpha.31** — rejected. Switches from - `starifly/sing-box` to upstream, dropping all starifly protocol additions (Snell, - Juicity, AnyTLS/AnyReality, ShadowsocksR, XHTTP, mux extensions) and shipping on an alpha. -- **B. Backport `gecko.go` into a forked sing-quic + bump sing-quic 0.5.2→0.6.x inside - starifly/sing-box** — rejected for now. Large cross-repo Go effort (sing-quic 0.5→0.6 - changed QUIC internals), high regression risk to Hysteria2/TUIC, and adds a forked - sing-quic to maintain. -- **C. Defer** — chosen. Wait until Gecko lands in a stable `starifly/sing-box` (or upstream - sing-box stable that starifly rebases onto). Then it becomes a clean pin bump plus a small - Android UI change. - -## When Gecko becomes available (future work) -1. `core/singbox-gecko-base`: bump `COMMIT_SING_BOX` (and transitively sing-quic) in - `buildScript/lib/core/get_source_env.sh` to a starifly commit that includes Gecko; - rebuild `libcore.aar`; confirm a minimal `obfs.type=gecko` Hysteria2 outbound is accepted - and existing Salamander/no-obfs profiles still work. -2. `feature/hy2-gecko-obfs`: Android side — - - `HysteriaBean`: add an obfs-type field + `geckoMinPacketSize` / `geckoMaxPacketSize` - (bump the kryo serialization version, currently 7, with deserialize handling). - - `HysteriaFmt.kt`: stop hardcoding `type="salamander"` (line ~330); branch on obfs type; - emit `min_packet_size`/`max_packet_size` for gecko. - - `SingBoxOptions.java`: extend `Hysteria2Obfs` (currently `type`+`password`) to carry the - gecko packet-size fields, matching the core's schema at that version. - - URI parse/build: handle `obfs=gecko`, `obfs-min-packet-size`, `obfs-max-packet-size`. - - UI (`hysteria_preferences.xml` + `HysteriaSettingsActivity.kt`): add an obfs-type - selector; show min/max only for gecko. - - Validation: password required for salamander/gecko; `1 <= min <= max <= 2048`; - defaults 512 / 1200. diff --git a/docs/maintenance/hysteria2-gecko-sidecar.md b/docs/maintenance/hysteria2-gecko-sidecar.md deleted file mode 100644 index f7c95ab90..000000000 --- a/docs/maintenance/hysteria2-gecko-sidecar.md +++ /dev/null @@ -1,101 +0,0 @@ -# Hysteria2 Gecko obfs via bundled official-binary sidecar - -Date: 2026-06-15 -Status: Implementing. Supersedes `hysteria2-gecko-deferral.md` (Gecko is no longer blocked -on a sing-box re-platform). - -## Decision -Ship Hysteria2 "Gecko" obfuscation by bundling the **official `apernet/hysteria`** Android -client binary as a sidecar (the same pattern as Mieru), instead of re-platforming the -sing-box core. This keeps the starifly core (and SSR/Snell/Juicity) intact and is a -days-scale change rather than a weeks-scale core migration. (Endorsed by external review.) - -## Why this works cleanly -- Official hysteria ships Android binaries for all 4 ABIs (`hysteria-android-{arm64,armv7,amd64,386}`) - in release `app/v2.9.2` — no cross-compile needed. -- Official hysteria client config has `obfs.type: gecko` with `obfs.gecko.{password,minPacketSize,maxPacketSize}`. -- Official hysteria has a built-in Android FD-protect hook: - `quic.sockopts.fdControlUnixSocket` — it connects to a unix socket and sends each QUIC - socket fd for protection. NekoBox **already** runs exactly such a server: libcore's - `protect_server.ServeProtect("protect_path", ...)` (box.go:245) listens on the relative - socket `protect_path` in the process working dir (`noBackupFilesDir`) and calls - `VpnService.protect(fd)`. Sidecars run with that cwd. So we point hysteria's - `fdControlUnixSocket` at that socket — no upstream patch, no new bridge. - -## Routing model (which HY2 profiles use the sidecar) -- Non-obfs and **Salamander** Hysteria2 keep using the native starifly sing-box `hysteria2` - outbound (unchanged — `canUseSingBox()` stays true). -- **Gecko** Hysteria2 routes to the bundled official-binary sidecar: `canUseSingBox()` - returns false for Gecko, so the existing `needExternal()` machinery builds a sing-box - `socks` outbound + dokodemo mapping pointing at the sidecar's local SOCKS5, exactly like - Mieru/Naive/HY1. - -## Data model (HysteriaBean) -Keep `obfuscation` (the obfs password — backward compatible; existing Salamander profiles -keep working). Add: -- `hysteria2ObfsType: Int` — 0=NONE, 1=SALAMANDER, 2=GECKO (default derived: if - `obfuscation` non-blank and type unset on old data => SALAMANDER, else NONE). -- `geckoMinPacketSize: Int` (default 512), `geckoMaxPacketSize: Int` (default 1200). -- Bump kryo serialize version 7 -> 8; deserialize reads the new fields only when - `version >= 8`, defaulting otherwise (old profiles => SALAMANDER if they had obfuscation). -Validation: gecko requires password; `1 <= min <= max <= 2048`. - -## Config generation (new `buildHysteria2SidecarConfig`) -Emit hysteria client YAML (the official schema): -```yaml -server: ":" -auth: "" -tls: { sni: "", insecure: } # ca via caText if set -obfs: - type: gecko - gecko: { password: "", minPacketSize: , maxPacketSize: } -quic: - sockopts: - fdControlUnixSocket: "/protect_path" -socks5: - listen: "127.0.0.1:" - username: "" - password: "" - disableUDP: false -lazy: true -``` -- Pre-resolve the server domain to an IP in NekoBox (set `server` to IP, `tls.sni` to the - original host) so the sidecar's own DNS doesn't loop into the VPN — the one extra - correctness item beyond fd-protect. (Hysteria mapping already routes the server via the - dokodemo `direct` inbound through the core, which handles this; verify.) -- The sing-box `socks` outbound must carry the same random SOCKS user/pass. - -## Binary packaging -- `buildScript/lib/hysteria2.sh` (`./run lib hysteria2`): download official - `hysteria-android-{arm64,armv7,amd64,386}` from `app/v2.9.2`, install as - `app/executableSo//libhysteria2.so`. (Mirror `buildScript/lib/mieru.sh`; here we can - download prebuilt instead of cross-compiling.) -- `PluginManager.initNativeInternal` already maps `hysteria2-plugin -> libhysteria2.so`. -- CI: the existing Mieru job was generalized into a combined `Native Build (Sidecars)` job - that builds/caches both `libmieru.so` and `libhysteria2.so` (one `app/executableSo` cache), - with a verify step covering all 4 ABIs and both binaries, across all 4 workflows. -- `Executable.EXECUTABLES` already lists `libhysteria.so`; add `libhysteria2.so`. - -## BoxInstance wiring -- `init()`: for HysteriaBean with protocolVersion==2 && Gecko, `initPlugin("hysteria2-plugin")` - and store `buildHysteria2SidecarConfig(port, protectPath)` (JSON config; viper detects the - format from the `.json` extension). -- `launch()`: new branch — write the config to cache and invoke the official binary as - `libhysteria2.so --disable-update-check --config --log-level client`. - -## UI (hysteria_preferences.xml + HysteriaSettingsActivity) -- Replace the single obfs password field with an obfs **type selector** (None/Salamander/Gecko) - + password + gecko min/max packet-size (shown only for Gecko, HY2 only). -- DataStore wiring for the new fields. - -## Tests / verification -- Build core + APK; confirm `libhysteria2.so` packaged for all 4 ABIs. -- Existing Salamander HY2 profile still uses native outbound, round-trips unchanged. -- Gecko profile: import/export URI (`obfs=gecko&obfs-password=&obfs-min/max-packet-size`), - generated YAML correctness, sidecar launches, sing-box socks outbound points at it. -- Runtime connect test deferred to the emulator QA pass (x86 metal, quota now approved). - -## Scope guard -Touches: HysteriaBean (+serialization), HysteriaFmt (canUseSingBox/parse/export/new YAML -builder), ConfigBuilder (Gecko obfs already handled by needExternal path — verify pluginId), -BoxInstance (init+launch branches), UI, build script + CI, Executable. No core/Go change. diff --git a/docs/maintenance/masterdnsvpn-deferral.md b/docs/maintenance/masterdnsvpn-deferral.md deleted file mode 100644 index a2f8a288a..000000000 --- a/docs/maintenance/masterdnsvpn-deferral.md +++ /dev/null @@ -1,101 +0,0 @@ -# MasterDnsVPN integration — deferral note and design - -Date: 2026-06-15 -Status: **Deferred** (blocked on upstream Android socket protection) - -## Goal -Add `masterking32/MasterDnsVPN` (a DNS-tunneling VPN that carries TCP/UDP over DNS -queries) as a client profile in NekoBox, using the same bundled-binary sidecar pattern -that now powers Mieru. - -## Why it is deferred -MasterDnsVPN has **no Android `VpnService.protect()` mechanism**. Its client -(`cmd/client`, Go 1.25, TOML config) sends its tunnel carrier as DNS queries to -configured resolver IPs (e.g. `8.8.8.8:53`). When NekoBox's `VpnService` is active and -captures all traffic into the TUN, those upstream DNS sockets would be captured by the VPN -and loop back into sing-box instead of egressing on the real underlying network — breaking -the tunnel. - -Repo inspection (2026-06-15) found no protect-path env, no `SO_MARK`/fwmark/`Dialer.Control` -socket-control hook, and no Android-specific code. By contrast, Mieru integrates cleanly -because upstream Mieru honors `MIERU_PROTECT_PATH` and lets NekoBox protect its upstream -sockets. - -## Architecture analysis (confirmed with an external model review) -- **sing-box `direct`/bypass routing rule for the resolver IPs — does NOT work.** A sing-box - route rule only affects sing-box's own outbound sockets. The MasterDnsVPN sidecar is a - separate process; its sockets are not sing-box's, so a `direct` rule cannot keep them off - the VPN. At best it would hairpin captured sidecar packets back through sing-box, which is - fragile and semantically wrong for a DNS tunnel (sing-box may treat the carrier DNS as - ordinary DNS and rewrite/route it). -- **Android `VpnService.Builder.excludeRoute()` (API 33+) — works by destination but is the - wrong tool.** It would exclude *all* traffic to the resolver IPs for the whole covered UID - (not just MasterDnsVPN's DNS carrier), is IP-prefix based (not per-socket/session), can't - follow dynamic resolver changes without rebuilding the VPN, is awkward pre-API 33, and - interacts badly with per-app/lockdown VPN expectations. Leak-prone; not production-grade. -- **Per-socket `VpnService.protect(fd)` is the only reliable way** to keep a separate - sidecar's upstream sockets off the VPN. Raw `SO_MARK` from app code is gated by - Android/netd permission checks and is not a substitute. - -## Recommended sequence (when work resumes) - -### Step 1 — Ship Proxy-mode-only sidecar (interim, usable) -Full feature scaffolding, gated to NekoBox proxy mode (no TUN, so no VPN loop): -- `MasterDnsVpnBean` (new `ProxyEntity.TYPE_*`, `TypeMap`, `needExternal()=true`). -- Fields: `domains`, `dataEncryptionMethod`, `encryptionKey`, `resolvers`, - `resolverBalancingStrategy`, `localSocksPort`, `socks5Auth`/`user`/`pass`, - `protocolType` (SOCKS5), plus an `advancedToml` override for the long tail of knobs. -- TOML generator writing `client_config.toml` (mirror `client_config.toml.simple`) + - a resolvers file. -- Process runner via the existing `GuardedProcessPool` (like Mieru), local SOCKS5 on - `127.0.0.1:`; sing-box `socks` outbound -> that port. -- Bundled binaries: cross-compile `cmd/client` for all 4 ABIs into - `app/executableSo//libmasterdnsvpn.so`, built in CI (mirror `buildScript/lib/mieru.sh` - and the per-workflow Mieru job). - - **Go toolchain decision:** MasterDnsVPN requires `go 1.25.0`, while libcore's CI is - pinned to `1.24.9` (matching `libcore/go.mod`'s `toolchain` directive). Use a **separate - Go setup step scoped to the MasterDnsVPN build job** (its own `actions/setup-go` with - `go-version: '1.25.x'`), leaving the libcore toolchain pin untouched. Do **not** raise the - whole-repo Go pin just for this binary — libcore is reproducibility-sensitive and was - aligned to 1.24.9 deliberately. The MasterDnsVPN build is an independent native binary, so - an isolated newer Go for that one job is the lower-risk choice. -- In VPN mode, disable the profile with a clear message: - "MasterDnsVPN requires Android socket protection for VPN mode. Proxy mode is supported; - VPN mode will be enabled after upstream resolver sockets can be protected." -- Security defaults: `LISTEN_IP=127.0.0.1`, SOCKS5 auth on with random user/pass, never log - `ENCRYPTION_KEY` or SOCKS creds, bind loopback only. - -### Step 2 — Upstream a socket-protect hook to MasterDnsVPN (fork/PR) -- Add a `MASTERDNSVPN_PROTECT_PATH` env (unset = normal desktop behavior). -- Wire every upstream resolver socket through `net.Dialer.ControlContext` (connected - TCP/UDP) and `net.ListenConfig.Control` (unconnected UDP `PacketConn`), protecting the fd - *after creation, before connect/bind*. -- Audit ALL upstream socket creation sites: UDP query, TCP fallback, IPv4+IPv6, resolver - health checks, MTU/probe, validation, any future DoH/DoT. Do NOT protect the local SOCKS - listener. -- Fail fast in VPN mode if protect fails. - -### Step 3 — NekoBox protect bridge (reuse Mieru's) -- NekoBox creates a private unix domain socket under app-private storage, starts the protect - server before the sidecar, launches the client with `MASTERDNSVPN_PROTECT_PATH`. -- The client passes the real fd via `SCM_RIGHTS`; NekoBox calls `VpnService.protect(fd)` and - returns a status byte. (Pass the actual fd, not a numeric string.) - -### Step 4 — Enable VPN mode; remove any interim workaround -- App -> TUN -> sing-box -> SOCKS outbound -> 127.0.0.1:client -> protected resolver sockets. -- No sing-box "direct resolver IP" rule needed — the carrier DNS never enters sing-box. - -### Step 5 — Verification that matters -- Full-route VPN (`0.0.0.0/0` + `::/0`), no excludeRoute, no sing-box direct rule: - MasterDnsVPN works AND the TUN shows no `sidecar -> resolver:53` packets; protect-server - logs show every resolver socket protected. -- Matrix: UDP + TCP fallback, multiple resolvers, failover/health checks, IPv6 resolvers, - Wi-Fi<->cellular switch, VPN restart, always-on/lockdown, target Android versions. - -## Why not do it all now -- VPN-mode support (the headline use case) depends on an upstream code change that doesn't - exist yet; shipping a sing-box-routing workaround would be leak-prone and architecturally - wrong. -- The proxy-mode-only interim is a real option (Step 1) but was deferred together with the - rest pending a decision to invest in the upstream protect hook. This note captures the full - design so the work can start cleanly later. diff --git a/docs/maintenance/official-singbox-feasibility.md b/docs/maintenance/official-singbox-feasibility.md deleted file mode 100644 index c0dbb7b5d..000000000 --- a/docs/maintenance/official-singbox-feasibility.md +++ /dev/null @@ -1,83 +0,0 @@ -# Feasibility: switching the core to official SagerNet/sing-box - -Date: 2026-06-15 -Status: **Investigated (read-only). Not recommended as a drop-in switch.** - -## What was asked -Switch this fork's core from `starifly/sing-box` to the official -`SagerNet/sing-box`. - -## The actual fork chain -```text -SagerNet/sing-box (official, latest stable v1.13.13) - └── MatsuriDayo/sing-box (NekoBox lineage; heavy Android/gomobile + extra protocols) - └── starifly/sing-box (this fork's pinned core; ~v1.12.19-neko-1) -``` -`starifly/sing-box`'s GitHub parent is `MatsuriDayo/sing-box`, **not** official directly. -So "switch to official" means leaving the entire Matsuri/NekoBox core lineage that this -app is built on — not a remote/URL change. - -## Why it is not a config swap -`libcore/` (the gomobile bridge, 8 Go files) imports sing-box **internal** packages -directly, including several that **do not exist in official sing-box**: - -| libcore import | In official v1.13.x? | -|---|---| -| `sing-box/nekoutils` | NO (Matsuri-only glue) | -| `sing-box/protocol/shadowsocksr` | NO (SSR not in official) | -| `sing-box/protocol/snell` | NO (Snell not in official) | -| `sing-box/protocol/anytls` | Present, but fork/official APIs differ | -| `experimental/libbox/platform` | Present, but the fork's platform API diverges | -| `adapter`, `adapter/{inbound,outbound,endpoint,service}`, `option`, `dns/...` | Present, but 1.12-fork vs 1.13-official APIs differ | - -Official `protocol/` dir (v1.13.13, confirmed) has: anytls, block, cloudflare, direct, -dns, group, http, hysteria, hysteria2, mixed, naive, redirect, shadowsocks, shadowtls, -socks, ssh, tailscale, tor, trojan, tuic, tun, vless, vmess, wireguard. - -**Missing vs this fork:** `shadowsocksr`, `snell`, `juicity` (juicity is wired via the -separate `dyhkwong/sing-juicity` dep), plus the `nekoutils` glue. - -## App-side coupling (what would break) -- `app/.../SingBoxOptions.java` is a **4830-line** hand-maintained mirror of the fork's - `option` structs. Official's option schema differs (1.13 vs 1.12 fork) and would need - re-deriving. -- The app exposes fork-only protocols as first-class profile types: - - `TYPE_SSR = 3` (ShadowsocksR) — 8 files - - `TYPE_SNELL = 24` — 11 files - - `TYPE_JUICITY = 23` — 9 files - These would all break (no core support in official) and the profile types/DB enums, - fmt parsers/exporters, and UI editors would need removal or re-sourcing. -- `box_include.go` registers the fork's protocol set; official's registration API differs. -- `BoxInstance.kt` + platform interface target the Matsuri `libbox/platform` API. - -## Cost / risk -This is **re-platforming the app onto a different core**, not an upgrade: -1. Rewrite all 8 libcore Go bridge files against official 1.13.x adapter/option/platform APIs. -2. Re-derive the 4830-line `SingBoxOptions.java` schema. -3. Lose ShadowsocksR, Snell, Juicity (and re-source or drop them) — visible feature loss. -4. Re-implement/verify the external-plugin mapping (mieru/naive/hysteria/etc.). -5. Re-validate every remaining protocol end to end. -Estimated: weeks of work, high regression risk across most protocols. Result would be a -materially different app from the NekoBox/Matsuri lineage. - -## Recommendation -**Do not switch wholesale.** The fork exists precisely because of the Matsuri/starifly -core additions; official sing-box can't host this app's feature set without major loss and -rework. - -If the underlying motivation is **newer upstream sing-box features** (e.g. the Gecko obfs -that started this), the realistic paths are, in order of preference: -1. **Wait for `starifly`/Matsuri to rebase onto newer official sing-box** (they track - upstream), then bump the pin — keeps all fork protocols. -2. **Cherry-pick/backport** a specific upstream feature into the starifly fork (a fork of - the fork) when a single feature is needed. -3. Full re-platform to official only if the project intentionally abandons the - Matsuri-lineage protocol set — a separate product decision, out of scope for a - maintenance/feature pass. - -## Verification performed (read-only) -- Enumerated libcore's sing-box imports and matched them against official v1.13.13's - package layout. -- Confirmed SSR/Snell/Juicity/nekoutils absent from official. -- Measured app-side coupling (SingBoxOptions.java size, ProxyEntity types, file counts). -- No core or app code was changed.